Skip to main content

solana_runtime/
serde_snapshot.rs

1#[cfg(feature = "frozen-abi")]
2use solana_frozen_abi::stable_abi;
3#[cfg(all(target_os = "linux", target_env = "gnu"))]
4use std::{
5    ffi::{CStr, CString},
6    path::Path,
7};
8use {
9    crate::{
10        bank::{Bank, BankFieldsToDeserialize, BankFieldsToSerialize, BankHashStats, BankRc},
11        epoch_stakes::{DeserializableVersionedEpochStakes, VersionedEpochStakes},
12        runtime_config::RuntimeConfig,
13        snapshot_utils::StorageAndNextAccountsFileId,
14        stake_account::StakeAccount,
15        stakes::{
16            DeserializableDelegationStakes, Stakes, serialize_stake_accounts_to_delegation_format,
17        },
18    },
19    agave_fs::FileInfo,
20    agave_snapshots::error::SnapshotError,
21    bincode::{self, Error, config::Options},
22    log::*,
23    serde::{Deserialize, Serialize},
24    smallvec::SmallVec,
25    solana_accounts_db::{
26        ObsoleteAccounts,
27        account_storage_entry::AccountStorageEntry,
28        accounts::Accounts,
29        accounts_db::{
30            AccountsDb, AccountsDbConfig, AccountsFileId, AtomicAccountsFileId, IndexGenerationInfo,
31        },
32        accounts_file::AccountsFile,
33        accounts_hash::AccountsLtHash,
34        accounts_update_notifier_interface::AccountsUpdateNotifier,
35        blockhash_queue::BlockhashQueue,
36    },
37    solana_clock::{Epoch, Slot, UnixTimestamp},
38    solana_epoch_schedule::EpochSchedule,
39    solana_fee_calculator::FeeRateGovernor,
40    solana_genesis_config::GenesisConfig,
41    solana_hard_forks::HardForks,
42    solana_hash::Hash,
43    solana_inflation::Inflation,
44    solana_lattice_hash::lt_hash::LtHash,
45    solana_leader_schedule::SlotLeader,
46    solana_pubkey::Pubkey,
47    solana_serde::default_on_eof,
48    solana_stake_interface::state::Delegation,
49    std::{
50        collections::{HashMap, HashSet},
51        io::{self, BufReader, Read, Write},
52        path::PathBuf,
53        result::Result,
54        sync::{
55            Arc,
56            atomic::{AtomicBool, Ordering},
57        },
58        thread,
59        time::Instant,
60    },
61    types::{SerdeAccountsLtHash, UnusedRentCollector},
62    wincode::{
63        ReadResult, SchemaRead, SchemaReadOwned, SchemaWrite, WriteResult,
64        adapter::{DefaultOnEmptyRead, DiscardSeq},
65        io::{Reader, std_write::WriteAdapter},
66        len::BincodeLen,
67    },
68};
69
70mod obsolete_accounts;
71mod status_cache;
72mod storage;
73mod storages_list;
74mod tests;
75mod types;
76
77pub(crate) use {
78    obsolete_accounts::{SerdeObsoleteAccounts, SerdeObsoleteAccountsMap},
79    status_cache::{deserialize_status_cache, serialize_status_cache},
80    storage::{SerializableAccountStorageEntry, SerializedAccountsFileId},
81    storages_list::{StorageListItem, StoragesList},
82};
83
84const MAX_STREAM_SIZE: usize = 32 * 1024 * 1024 * 1024;
85type MaxStreamSizeConfig = wincode::config::Configuration<true, MAX_STREAM_SIZE>;
86
87/// A slot paired with its account storage entries; only kept to name the wire shape of the
88/// no-longer-used storage entries map in [`AccountsDbFields`] and [`SerializableAccountsDb`].
89#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
90#[derive(Debug, Serialize, Deserialize, SchemaRead, SchemaWrite)]
91pub(crate) struct SlotAccountStorageEntries {
92    slot: Slot,
93    /// In a real snapshot this always holds exactly one entry; sampling is spelled out because
94    /// `SmallVec` has no `StableAbi` impl (the length range no longer matters, as the map holding
95    /// these entries is itself sampled empty).
96    #[cfg_attr(
97        feature = "frozen-abi",
98        stable_abi_sample(with = "solana_frozen_abi::stable_abi::sample_collection_sized(rng, \
99                                  solana_frozen_abi::stable_abi::context::SequenceLenRange::new(0.\
100                                  .=5))")
101    )]
102    entries: SmallVec<[SerializableAccountStorageEntry; 1]>,
103}
104
105#[cfg_attr(
106    feature = "frozen-abi",
107    derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
108)]
109#[derive(Debug, Deserialize, SchemaRead)]
110pub(crate) struct AccountsDbFields(
111    /// unused storage entries map, skipped without allocating a backing `Vec`, as old snapshots
112    /// still carry a populated one; sampled empty, so the abi digests pin its wire shape only
113    #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
114    #[wincode(with = "DiscardSeq<SlotAccountStorageEntries, BincodeLen>")]
115    Vec<SlotAccountStorageEntries>,
116    u64, // unused, formerly write_version
117    Slot,
118    BankHashInfo,
119    /// all slots that were roots within the last epoch
120    #[serde(deserialize_with = "default_on_eof")]
121    #[wincode(with = "DefaultOnEmptyRead<Vec<Slot>>")]
122    Vec<Slot>,
123    /// slots that were roots within the last epoch for which we care about the hash value
124    #[serde(deserialize_with = "default_on_eof")]
125    #[wincode(with = "DefaultOnEmptyRead<Vec<(Slot, Hash)>>")]
126    Vec<(Slot, Hash)>,
127);
128
129#[repr(C)]
130#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
131#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
132#[derive(Serialize, Deserialize, Clone, Debug, SchemaRead, SchemaWrite)]
133pub struct UnusedIncrementalSnapshotPersistence {
134    pub full_slot: u64,
135    pub full_hash: [u8; 32],
136    pub full_capitalization: u64,
137    pub incremental_hash: [u8; 32],
138    pub incremental_capitalization: u64,
139}
140
141#[repr(C)]
142#[cfg_attr(
143    feature = "frozen-abi",
144    derive(AbiExample, StableAbi, StableAbiSample),
145    frozen_abi(
146        abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
147        abi_serializer = ["bincode", "wincode"],
148        test_roundtrip = "eq_and_wire"
149    )
150)]
151#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, SchemaRead, SchemaWrite)]
152struct BankHashInfo {
153    unused_accounts_delta_hash: [u8; 32],
154    unused_accounts_hash: [u8; 32],
155    stats: BankHashStats,
156}
157
158#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
159#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, SchemaRead, SchemaWrite)]
160struct UnusedAccounts {
161    unused1: HashSet<Pubkey>,
162    unused2: HashSet<Pubkey>,
163    unused3: HashMap<Pubkey, u64>,
164}
165
166// Deserializable version of Bank; keep fields synced with SerializableVersionedBank.
167// frozen-abi Serialize/SchemaWrite exist only to pin the read-path abi digest (see
168// DeserializableBankSnapshot).
169#[cfg_attr(
170    feature = "frozen-abi",
171    derive(Serialize, SchemaWrite, StableAbi, StableAbiSample)
172)]
173#[derive(Clone, Deserialize, SchemaRead)]
174struct DeserializableVersionedBank {
175    blockhash_queue: BlockhashQueue,
176    _unused_ancestors: HashMap<Slot, usize>,
177    hash: Hash,
178    parent_hash: Hash,
179    parent_slot: Slot,
180    hard_forks: HardForks,
181    transaction_count: u64,
182    tick_height: u64,
183    signature_count: u64,
184    capitalization: u64,
185    max_tick_height: u64,
186    hashes_per_tick: Option<u64>,
187    ticks_per_slot: u64,
188    ns_per_slot: u128,
189    genesis_creation_time: UnixTimestamp,
190    slots_per_year: f64,
191    accounts_data_len: u64,
192    slot: Slot,
193    _unused_epoch: Epoch,
194    block_height: u64,
195    leader_id: Pubkey,
196    _unused_collector_fees: u64,
197    _unused_fee_calculator: u64,
198    fee_rate_governor: FeeRateGovernor,
199    _unused_collected_rent: u64,
200    _unused_rent_collector: UnusedRentCollector,
201    epoch_schedule: EpochSchedule,
202    inflation: Inflation,
203    stakes: DeserializableDelegationStakes,
204    _unused_accounts: UnusedAccounts,
205    unused_epoch_stakes: HashMap<Epoch, ()>,
206    is_delta: bool,
207}
208
209impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
210    fn from(dvb: DeserializableVersionedBank) -> Self {
211        // This serves as a canary for the LtHash.
212        // If it is not replaced during deserialization, it indicates a bug.
213        const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
214        // `durable_nonce` is skipped from the wire; recompute it from the last hash.
215        let mut blockhash_queue = dvb.blockhash_queue;
216        blockhash_queue.refresh_durable_nonce();
217        BankFieldsToDeserialize {
218            blockhash_queue,
219            hash: dvb.hash,
220            parent_hash: dvb.parent_hash,
221            parent_slot: dvb.parent_slot,
222            hard_forks: dvb.hard_forks,
223            transaction_count: dvb.transaction_count,
224            tick_height: dvb.tick_height,
225            signature_count: dvb.signature_count,
226            capitalization: dvb.capitalization,
227            max_tick_height: dvb.max_tick_height,
228            hashes_per_tick: dvb.hashes_per_tick,
229            ticks_per_slot: dvb.ticks_per_slot,
230            ns_per_slot: dvb.ns_per_slot,
231            genesis_creation_time: dvb.genesis_creation_time,
232            slots_per_year: dvb.slots_per_year,
233            accounts_data_len: dvb.accounts_data_len,
234            slot: dvb.slot,
235            block_height: dvb.block_height,
236            leader_id: dvb.leader_id,
237            fee_rate_governor: dvb.fee_rate_governor,
238            epoch_schedule: dvb.epoch_schedule,
239            inflation: dvb.inflation,
240            stakes: dvb.stakes,
241            is_delta: dvb.is_delta,
242            versioned_epoch_stakes: vec![], // populated from ExtraFieldsToDeserialize
243            accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), // populated from ExtraFieldsToDeserialize
244            bank_hash_stats: BankHashStats::default(),        // populated from AccountsDbFields
245            block_id: None, // populated from ExtraFieldsToDeserialize
246        }
247    }
248}
249
250// Serializable version of Bank, not Deserializable to avoid cloning by using refs.
251// Sync fields with DeserializableVersionedBank!
252#[cfg_attr(
253    feature = "frozen-abi",
254    derive(StableAbi, StableAbiSample),
255    // Write-only type (its deserialize counterpart is `DeserializableVersionedBank`), so the abi
256    // digest only verifies the serialized wire format; there is no roundtrip.
257    frozen_abi(
258        abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
259        abi_serializer = ["bincode", "wincode"],
260        test_roundtrip = "no"
261    )
262)]
263#[derive(Serialize, SchemaWrite)]
264struct SerializableVersionedBank {
265    blockhash_queue: BlockhashQueue,
266    unused_ancestors: HashMap<Slot, usize>,
267    hash: Hash,
268    parent_hash: Hash,
269    parent_slot: Slot,
270    hard_forks: HardForks,
271    transaction_count: u64,
272    tick_height: u64,
273    signature_count: u64,
274    capitalization: u64,
275    max_tick_height: u64,
276    hashes_per_tick: Option<u64>,
277    ticks_per_slot: u64,
278    ns_per_slot: u128,
279    genesis_creation_time: UnixTimestamp,
280    slots_per_year: f64,
281    accounts_data_len: u64,
282    slot: Slot,
283    unused_epoch: Epoch,
284    block_height: u64,
285    leader_id: Pubkey,
286    unused_collector_fees: u64,
287    unused_fee_calculator: u64,
288    fee_rate_governor: FeeRateGovernor,
289    unused_collected_rent: u64,
290    unused_rent_collector: UnusedRentCollector,
291    epoch_schedule: EpochSchedule,
292    inflation: Inflation,
293    #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
294    stakes: Stakes<StakeAccount<Delegation>>,
295    unused_accounts: UnusedAccounts,
296    unused_epoch_stakes: HashMap<Epoch, ()>,
297    is_delta: bool,
298}
299
300impl From<BankFieldsToSerialize> for SerializableVersionedBank {
301    fn from(rhs: BankFieldsToSerialize) -> Self {
302        Self {
303            blockhash_queue: rhs.blockhash_queue,
304            unused_ancestors: HashMap::default(),
305            hash: rhs.hash,
306            parent_hash: rhs.parent_hash,
307            parent_slot: rhs.parent_slot,
308            hard_forks: rhs.hard_forks,
309            transaction_count: rhs.transaction_count,
310            tick_height: rhs.tick_height,
311            signature_count: rhs.signature_count,
312            capitalization: rhs.capitalization,
313            max_tick_height: rhs.max_tick_height,
314            hashes_per_tick: rhs.hashes_per_tick,
315            ticks_per_slot: rhs.ticks_per_slot,
316            ns_per_slot: rhs.ns_per_slot,
317            genesis_creation_time: rhs.genesis_creation_time,
318            slots_per_year: rhs.slots_per_year,
319            accounts_data_len: rhs.accounts_data_len,
320            slot: rhs.slot,
321            unused_epoch: 0,
322            block_height: rhs.block_height,
323            leader_id: rhs.leader_id,
324            unused_collector_fees: 0,
325            unused_fee_calculator: 0,
326            fee_rate_governor: rhs.fee_rate_governor,
327            unused_collected_rent: u64::default(),
328            unused_rent_collector: UnusedRentCollector::zeroed(),
329            epoch_schedule: rhs.epoch_schedule,
330            inflation: rhs.inflation,
331            stakes: rhs.stakes,
332            unused_accounts: UnusedAccounts::default(),
333            unused_epoch_stakes: HashMap::default(),
334            is_delta: rhs.is_delta,
335        }
336    }
337}
338
339/// Helper type to wrap BufReader streams when deserializing and reconstructing from either just a
340/// full snapshot, or both a full and incremental snapshot
341pub struct SnapshotStreams<'a, R> {
342    pub full_snapshot_stream: &'a mut BufReader<R>,
343    pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
344}
345
346/// Helper type to wrap BankFields when reconstructing Bank from either just a full
347/// snapshot, or both a full and incremental snapshot
348#[derive(Debug)]
349pub struct SnapshotBankFields {
350    full: BankFieldsToDeserialize,
351    incremental: Option<BankFieldsToDeserialize>,
352}
353
354impl SnapshotBankFields {
355    pub fn new(
356        full: BankFieldsToDeserialize,
357        incremental: Option<BankFieldsToDeserialize>,
358    ) -> Self {
359        Self { full, incremental }
360    }
361
362    /// Collapse the SnapshotBankFields into a single (the latest) BankFieldsToDeserialize.
363    pub fn collapse_into(self) -> BankFieldsToDeserialize {
364        self.incremental.unwrap_or(self.full)
365    }
366}
367
368/// Helper type to wrap AccountsDbFields when reconstructing AccountsDb from either just a full
369/// snapshot, or both a full and incremental snapshot
370#[derive(Debug)]
371pub struct SnapshotAccountsDbFields {
372    full_snapshot_accounts_db_fields: AccountsDbFields,
373    incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
374}
375
376impl SnapshotAccountsDbFields {
377    pub(crate) fn new(
378        full_snapshot_accounts_db_fields: AccountsDbFields,
379        incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
380    ) -> Self {
381        Self {
382            full_snapshot_accounts_db_fields,
383            incremental_snapshot_accounts_db_fields,
384        }
385    }
386
387    /// Extract final bank hash info from full and incremental accounts db fields.
388    ///
389    /// If there is no incremental snapshot, this returns the field from the full snapshot.
390    /// Otherwise, gets it from the incremental snapshot.
391    fn into_bank_hash_info(self) -> BankHashInfo {
392        let AccountsDbFields(
393            _snapshot_storages,
394            _snapshot_write_version,
395            _snapshot_slot,
396            snapshot_bank_hash_info,
397            _snapshot_historical_roots,
398            _snapshot_historical_roots_with_hash,
399        ) = self
400            .incremental_snapshot_accounts_db_fields
401            .unwrap_or(self.full_snapshot_accounts_db_fields);
402        snapshot_bank_hash_info
403    }
404}
405
406pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
407where
408    W: Write,
409    T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
410{
411    wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
412}
413
414pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
415where
416    R: Reader<'a>,
417    T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
418{
419    wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
420}
421
422/// Extra fields that are deserialized from the end of snapshots.
423///
424/// Note that this struct's fields should stay synced with the fields in
425/// ExtraFieldsToSerialize with the exception that new "extra fields" should be
426/// added to this struct a minor release before they are added to the serialize
427/// struct.
428#[cfg_attr(
429    feature = "frozen-abi",
430    derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
431)]
432#[derive(Clone, Debug, Deserialize, SchemaRead)]
433struct ExtraFieldsToDeserialize {
434    #[serde(deserialize_with = "default_on_eof")]
435    #[wincode(with = "DefaultOnEmptyRead<u64>")]
436    lamports_per_signature: u64,
437    #[serde(deserialize_with = "default_on_eof")]
438    #[wincode(with = "DefaultOnEmptyRead<Option<UnusedIncrementalSnapshotPersistence>>")]
439    _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
440    #[serde(deserialize_with = "default_on_eof")]
441    #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
442    _unused_epoch_accounts_hash: Option<Hash>,
443    #[serde(deserialize_with = "default_on_eof")]
444    #[wincode(with = "DefaultOnEmptyRead<Vec<(u64, DeserializableVersionedEpochStakes)>>")]
445    // Match the serialize side's `HashMap<u64, VersionedEpochStakes>`, which samples `0..=1` entries.
446    #[cfg_attr(
447        feature = "frozen-abi",
448        stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
449                                  stable_abi::context::SequenceLenMax(1))")
450    )]
451    versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
452    #[serde(deserialize_with = "default_on_eof")]
453    #[wincode(with = "DefaultOnEmptyRead<Option<SerdeAccountsLtHash>>")]
454    accounts_lt_hash: Option<SerdeAccountsLtHash>,
455    #[serde(deserialize_with = "default_on_eof")]
456    #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
457    block_id: Option<Hash>,
458}
459
460/// Extra fields that are serialized at the end of snapshots.
461///
462/// Note that this struct's fields should stay synced with the fields in
463/// ExtraFieldsToDeserialize with the exception that new "extra fields" should
464/// be added to the deserialize struct a minor release before they are added to
465/// this one.
466#[cfg_attr(
467    feature = "frozen-abi",
468    derive(AbiExample, StableAbi, StableAbiSample),
469    // Write-only type (its deserialize counterpart is `ExtraFieldsToDeserialize`), so the abi digest
470    // only verifies the serialized wire format; there is no roundtrip.
471    frozen_abi(
472        abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
473        abi_serializer = ["bincode", "wincode"],
474        test_roundtrip = "no"
475    )
476)]
477#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
478#[derive(Debug, Serialize, SchemaWrite)]
479pub struct ExtraFieldsToSerialize {
480    pub lamports_per_signature: u64,
481    pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
482    pub unused_epoch_accounts_hash: Option<Hash>,
483    pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
484    pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
485    pub block_id: Option<Hash>,
486}
487
488/// Deserializable counterpart of [`SerializableBankSnapshot`], read as one struct (wincode reads
489/// the parts sequentially, matching separate reads).
490///
491/// Its frozen-abi digest must equal [`SerializableBankSnapshotForAbi`]'s, so the read and write
492/// wire formats can't diverge.
493#[cfg_attr(
494    feature = "frozen-abi",
495    derive(Deserialize, Serialize, SchemaWrite, StableAbi, StableAbiSample),
496    frozen_abi(
497        abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
498        abi_serializer = ["bincode", "wincode"],
499        test_roundtrip = "wire_only"
500    )
501)]
502#[derive(SchemaRead)]
503struct DeserializableBankSnapshot {
504    bank: DeserializableVersionedBank,
505    accounts_db: AccountsDbFields,
506    extra_fields: ExtraFieldsToDeserialize,
507}
508
509impl DeserializableBankSnapshot {
510    /// Folds the extra fields into the bank fields; errors if `unused_epoch_stakes` is non-empty.
511    fn into_fields(self) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
512        let Self {
513            bank,
514            accounts_db,
515            extra_fields,
516        } = self;
517        if !bank.unused_epoch_stakes.is_empty() {
518            return Err(wincode::ReadError::InvalidValue(
519                "Expected deserialized bank's unused_epoch_stakes field to be empty",
520            ));
521        }
522        let mut bank_fields = BankFieldsToDeserialize::from(bank);
523        let ExtraFieldsToDeserialize {
524            lamports_per_signature,
525            _unused_incremental_snapshot_persistence,
526            _unused_epoch_accounts_hash,
527            versioned_epoch_stakes,
528            accounts_lt_hash,
529            block_id,
530        } = extra_fields;
531
532        bank_fields.fee_rate_governor = bank_fields
533            .fee_rate_governor
534            .clone_with_lamports_per_signature(lamports_per_signature);
535        bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
536        bank_fields.accounts_lt_hash = accounts_lt_hash
537            .expect("snapshot must have accounts_lt_hash")
538            .into();
539        bank_fields.block_id = block_id;
540
541        Ok((bank_fields, accounts_db))
542    }
543}
544
545pub(crate) fn fields_from_stream<R: Read>(
546    snapshot_stream: &mut BufReader<R>,
547) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
548    deserialize_wincode_from::<_, DeserializableBankSnapshot>(snapshot_stream)?.into_fields()
549}
550
551#[cfg(feature = "dev-context-only-utils")]
552pub(crate) fn fields_from_streams(
553    snapshot_streams: &mut SnapshotStreams<impl Read>,
554) -> wincode::ReadResult<(SnapshotBankFields, SnapshotAccountsDbFields)> {
555    let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
556        fields_from_stream(snapshot_streams.full_snapshot_stream)?;
557    let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
558        snapshot_streams
559            .incremental_snapshot_stream
560            .as_mut()
561            .map(|stream| fields_from_stream(stream))
562            .transpose()?
563            .unzip();
564
565    let snapshot_bank_fields = SnapshotBankFields {
566        full: full_snapshot_bank_fields,
567        incremental: incremental_snapshot_bank_fields,
568    };
569    let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
570        full_snapshot_accounts_db_fields,
571        incremental_snapshot_accounts_db_fields,
572    };
573    Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
574}
575
576/// This struct contains side-info while reconstructing the bank from streams
577#[derive(Debug)]
578pub struct BankFromStreamsInfo {
579    /// The accounts lt hash calculated during index generation.
580    /// Will be used when verifying accounts, after rebuilding a Bank.
581    pub calculated_accounts_lt_hash: AccountsLtHash,
582}
583
584#[allow(clippy::too_many_arguments)]
585#[cfg(test)]
586pub(crate) fn bank_from_streams<R>(
587    snapshot_streams: &mut SnapshotStreams<R>,
588    account_paths: &[PathBuf],
589    storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
590    genesis_config: &GenesisConfig,
591    runtime_config: &RuntimeConfig,
592    debug_keys: Option<Arc<HashSet<Pubkey>>>,
593    limit_load_slot_count_from_snapshot: Option<usize>,
594    verify_index: bool,
595    accounts_db_config: AccountsDbConfig,
596    accounts_update_notifier: Option<AccountsUpdateNotifier>,
597    exit: Arc<AtomicBool>,
598) -> std::result::Result<(Bank, BankFromStreamsInfo), SnapshotError>
599where
600    R: Read,
601{
602    let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
603    let (bank, info) = reconstruct_bank_from_fields(
604        bank_fields,
605        accounts_db_fields,
606        genesis_config,
607        runtime_config,
608        account_paths,
609        storage_and_next_append_vec_id,
610        debug_keys,
611        None, // leader_for_tests
612        limit_load_slot_count_from_snapshot,
613        verify_index,
614        accounts_db_config,
615        accounts_update_notifier,
616        exit,
617    )?;
618    Ok((
619        bank,
620        BankFromStreamsInfo {
621            calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
622        },
623    ))
624}
625
626#[cfg(test)]
627pub(crate) fn bank_to_stream<W>(
628    stream: &mut io::BufWriter<W>,
629    bank: &Bank,
630) -> wincode::WriteResult<()>
631where
632    W: Write,
633{
634    let mut bank_fields = bank.get_fields_to_serialize();
635    let bank_hash_stats = bank.get_bank_hash_stats();
636    let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
637    let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
638    let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
639    let block_id = Some(bank_fields.block_id);
640    serialize_bank_snapshot_into_wincode(
641        stream,
642        bank_fields,
643        bank_hash_stats,
644        ExtraFieldsToSerialize {
645            lamports_per_signature,
646            unused_incremental_snapshot_persistence: None,
647            unused_epoch_accounts_hash: None,
648            versioned_epoch_stakes,
649            accounts_lt_hash,
650            block_id,
651        },
652    )
653}
654
655/// Serializes bank snapshot into `stream` with bincode
656pub fn serialize_bank_snapshot_into(
657    stream: &mut dyn Write,
658    bank_fields: BankFieldsToSerialize,
659    bank_hash_stats: BankHashStats,
660    extra_fields: ExtraFieldsToSerialize,
661) -> Result<(), Error> {
662    let mut serializer = bincode::Serializer::new(
663        stream,
664        bincode::DefaultOptions::new().with_fixint_encoding(),
665    );
666    serialize_bank_snapshot_with(&mut serializer, bank_fields, bank_hash_stats, extra_fields)
667}
668
669// The full serialized form of a bank snapshot: the bank fields, the accounts db fields, and the
670// extra fields, in wire order.
671#[cfg_attr(
672    feature = "frozen-abi",
673    derive(StableAbi, StableAbiSample),
674    // Write-only type (its deserialize counterparts are `DeserializableVersionedBank`,
675    // `AccountsDbFields` and `ExtraFieldsToDeserialize`), so there is no roundtrip.
676    frozen_abi(
677        abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
678        abi_serializer = ["bincode", "wincode"],
679        test_roundtrip = "no"
680    )
681)]
682#[derive(Serialize, SchemaWrite)]
683struct SerializableBankSnapshot {
684    bank: SerializableVersionedBank,
685    accounts_db: SerializableAccountsDb,
686    extra_fields: ExtraFieldsToSerialize,
687}
688
689/// Serializes bank snapshot with `serializer`
690pub fn serialize_bank_snapshot_with<S>(
691    serializer: S,
692    bank_fields: BankFieldsToSerialize,
693    bank_hash_stats: BankHashStats,
694    extra_fields: ExtraFieldsToSerialize,
695) -> Result<S::Ok, S::Error>
696where
697    S: serde::Serializer,
698{
699    let slot = bank_fields.slot;
700    let snapshot = SerializableBankSnapshot {
701        bank: SerializableVersionedBank::from(bank_fields),
702        accounts_db: SerializableAccountsDb::new(slot, bank_hash_stats),
703        extra_fields,
704    };
705    // Note: the time spent here is reported by the caller (e.g. as `bank_serialize_us` in the
706    // `snapshot_bank` datapoint).
707    snapshot.serialize(serializer)
708}
709
710/// Serializes bank snapshot into `stream` with wincode.
711///
712/// Produces byte-for-byte the same output as [`serialize_bank_snapshot_into`] (which uses bincode),
713/// just through the wincode serializer.
714pub fn serialize_bank_snapshot_into_wincode(
715    stream: &mut dyn Write,
716    bank_fields: BankFieldsToSerialize,
717    bank_hash_stats: BankHashStats,
718    extra_fields: ExtraFieldsToSerialize,
719) -> wincode::WriteResult<()> {
720    let slot = bank_fields.slot;
721    let snapshot = SerializableBankSnapshot {
722        bank: SerializableVersionedBank::from(bank_fields),
723        accounts_db: SerializableAccountsDb::new(slot, bank_hash_stats),
724        extra_fields,
725    };
726    serialize_into(stream, &snapshot)
727}
728
729// Serializable counterpart of `AccountsDbFields`. Sync fields with `AccountsDbFields`!
730#[cfg_attr(
731    feature = "frozen-abi",
732    derive(StableAbi, StableAbiSample),
733    // Write-only type (its deserialize counterpart is `AccountsDbFields`), so there is no
734    // roundtrip.
735    frozen_abi(
736        abi_digest = "6d9LgxwkMTVHRKGtF8QSFFn3rYG8MSmyT9wPrL1HESu1",
737        abi_serializer = ["bincode", "wincode"],
738        test_roundtrip = "no"
739    )
740)]
741#[derive(Serialize, SchemaWrite)]
742struct SerializableAccountsDb {
743    /// unused storage entries map, always written empty; sampled empty, so the abi digests pin its
744    /// wire shape only
745    #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
746    unused_accounts_storage_entries: Vec<SlotAccountStorageEntries>,
747    unused_write_version: u64, // unused, formerly write_version
748    slot: Slot,
749    bank_hash_info: BankHashInfo,
750    /// all slots that were roots within the last epoch
751    historical_roots: Vec<Slot>,
752    /// slots that were roots within the last epoch for which we care about the hash value
753    historical_roots_with_hash: Vec<(Slot, Hash)>,
754}
755
756impl SerializableAccountsDb {
757    fn new(slot: Slot, bank_hash_stats: BankHashStats) -> Self {
758        let bank_hash_info = BankHashInfo {
759            unused_accounts_delta_hash: [0; 32],
760            unused_accounts_hash: [0; 32],
761            stats: bank_hash_stats,
762        };
763        SerializableAccountsDb {
764            unused_accounts_storage_entries: Vec::default(),
765            unused_write_version: 0,
766            slot,
767            bank_hash_info,
768            historical_roots: Vec::default(),
769            historical_roots_with_hash: Vec::default(),
770        }
771    }
772}
773
774/// This struct contains side-info while reconstructing the bank from fields
775#[derive(Debug)]
776pub(crate) struct ReconstructedBankInfo {
777    /// The accounts lt hash calculated during index generation.
778    /// Will be used when verifying accounts, after rebuilding a Bank.
779    pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
780    /// The capitalization, in lamports, calculated during index generation.
781    pub(crate) calculated_capitalization: u64,
782}
783
784#[expect(clippy::too_many_arguments)]
785pub(crate) fn reconstruct_bank_from_fields(
786    bank_fields: SnapshotBankFields,
787    snapshot_accounts_db_fields: SnapshotAccountsDbFields,
788    genesis_config: &GenesisConfig,
789    runtime_config: &RuntimeConfig,
790    account_paths: &[PathBuf],
791    storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
792    debug_keys: Option<Arc<HashSet<Pubkey>>>,
793    leader_for_tests: Option<SlotLeader>,
794    limit_load_slot_count_from_snapshot: Option<usize>,
795    verify_index: bool,
796    accounts_db_config: AccountsDbConfig,
797    accounts_update_notifier: Option<AccountsUpdateNotifier>,
798    exit: Arc<AtomicBool>,
799) -> Result<(Bank, ReconstructedBankInfo), SnapshotError> {
800    let mut bank_fields = bank_fields.collapse_into();
801    // Epoch stakes take several seconds to reconstruct, do it in parallel with loading accountsdb
802    let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
803    let epoch_stakes_handle = thread::Builder::new()
804        .name("solRctEpochStk".into())
805        .spawn(|| {
806            deserializable_epoch_stakes
807                .into_iter()
808                .map(|(epoch, stakes)| (epoch, stakes.into()))
809                .collect()
810        })?;
811    let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
812        snapshot_accounts_db_fields,
813        account_paths,
814        storage_and_next_append_vec_id,
815        limit_load_slot_count_from_snapshot,
816        verify_index,
817        accounts_db_config,
818        accounts_update_notifier,
819        exit,
820    )?;
821    bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
822
823    let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
824    let runtime_config = Arc::new(runtime_config.clone());
825    let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
826
827    let bank = Bank::new_from_snapshot(
828        bank_rc,
829        genesis_config,
830        runtime_config,
831        bank_fields,
832        leader_for_tests,
833        debug_keys,
834        reconstructed_accounts_db_info.accounts_data_len,
835        epoch_stakes,
836    );
837
838    Ok((
839        bank,
840        ReconstructedBankInfo {
841            calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
842            calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
843        },
844    ))
845}
846
847pub(crate) fn reconstruct_single_storage(
848    slot: &Slot,
849    append_vec_file_info: FileInfo,
850    id: AccountsFileId,
851    obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
852) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
853    // The storage length is taken directly from the on-disk file size (see
854    // `AccountsFile::new_for_startup`). When restoring from an archive the obsolete accounts have
855    // been physically removed during serialization, and when restoring from a snapshot directory
856    // they are still present in the file. In both cases the file size already reflects the exact
857    // number of bytes the storage spans, so there is no need to carry the length separately in the
858    // snapshot fields.
859    //
860    // When restoring from an archive, obsolete accounts will always be `None`.
861    // When restoring from fastboot, obsolete accounts will be 'Some' if the storage contained
862    // accounts marked obsolete at the time the snapshot was taken.
863    let obsolete_accounts =
864        if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
865            if obsolete_id != id {
866                return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
867            }
868
869            obsolete_accounts
870        } else {
871            ObsoleteAccounts::default()
872        };
873
874    let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
875    Ok(Arc::new(AccountStorageEntry::new_existing(
876        *slot,
877        id,
878        accounts_file,
879        obsolete_accounts,
880    )))
881}
882
883// Remap the AppendVec ID to handle any duplicate IDs that may previously existed
884// due to full snapshots and incremental snapshots generated from different
885// nodes
886pub(crate) fn remap_append_vec_file(
887    slot: Slot,
888    old_append_vec_id: SerializedAccountsFileId,
889    append_vec_file_info: FileInfo,
890    next_append_vec_id: &AtomicAccountsFileId,
891    num_collisions: &mut usize,
892) -> io::Result<(AccountsFileId, FileInfo)> {
893    #[cfg(all(target_os = "linux", target_env = "gnu"))]
894    let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
895
896    let mut remapped_append_vec_path = append_vec_file_info.path.clone();
897
898    // Break out of the loop in the following situations:
899    // 1. The new ID is the same as the original ID.  This means we do not need to
900    //    rename the file, since the ID is the "correct" one already.
901    // 2. There is not a file already at the new path.  This means it is safe to
902    //    rename the file to this new path.
903    let (remapped_append_vec_id, remapped_append_vec_path) = loop {
904        let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
905
906        // this can only happen in the first iteration of the loop
907        if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
908            break (remapped_append_vec_id, remapped_append_vec_path);
909        }
910
911        let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
912        remapped_append_vec_path = remapped_append_vec_path
913            .parent()
914            .unwrap()
915            .join(remapped_file_name);
916
917        #[cfg(all(target_os = "linux", target_env = "gnu"))]
918        {
919            let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
920
921            // On linux we use renameat2(NO_REPLACE) instead of IF metadata(path).is_err() THEN
922            // rename() in order to save a statx() syscall.
923            match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
924                // If the file was successfully renamed, break out of the loop
925                Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
926                // If there's already a file at the new path, continue so we try
927                // the next ID
928                Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
929                Err(e) => return Err(e),
930            }
931        }
932
933        #[cfg(any(
934            not(target_os = "linux"),
935            all(target_os = "linux", not(target_env = "gnu"))
936        ))]
937        if std::fs::metadata(&remapped_append_vec_path).is_err() {
938            break (remapped_append_vec_id, remapped_append_vec_path);
939        }
940
941        // If we made it this far, a file exists at the new path.  Record the collision
942        // and try again.
943        *num_collisions += 1;
944    };
945
946    // Only rename the file if the new ID is actually different from the original. In the target_os
947    // = linux case, we have already renamed if necessary.
948    #[cfg(any(
949        not(target_os = "linux"),
950        all(target_os = "linux", not(target_env = "gnu"))
951    ))]
952    if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
953        std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
954    }
955
956    Ok((
957        remapped_append_vec_id,
958        FileInfo {
959            path: remapped_append_vec_path,
960            ..append_vec_file_info
961        },
962    ))
963}
964
965pub(crate) fn remap_and_reconstruct_single_storage(
966    slot: Slot,
967    old_append_vec_id: SerializedAccountsFileId,
968    append_vec_file_info: FileInfo,
969    next_append_vec_id: &AtomicAccountsFileId,
970    num_collisions: &mut usize,
971) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
972    let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
973        slot,
974        old_append_vec_id,
975        append_vec_file_info,
976        next_append_vec_id,
977        num_collisions,
978    )?;
979    let storage = reconstruct_single_storage(
980        &slot,
981        remapped_append_vec_file_info,
982        remapped_append_vec_id,
983        None,
984    )?;
985    Ok(storage)
986}
987
988/// This struct contains side-info while reconstructing the accounts DB from fields.
989#[derive(Debug)]
990pub struct ReconstructedAccountsDbInfo {
991    pub accounts_data_len: u64,
992    /// The accounts lt hash calculated during index generation.
993    /// Will be used when verifying accounts, after rebuilding a Bank.
994    pub calculated_accounts_lt_hash: AccountsLtHash,
995    /// The capitalization, in lamports, calculated during index generation.
996    pub calculated_capitalization: u64,
997    pub bank_hash_stats: BankHashStats,
998}
999
1000fn reconstruct_accountsdb_from_fields(
1001    snapshot_accounts_db_fields: SnapshotAccountsDbFields,
1002    account_paths: &[PathBuf],
1003    storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
1004    limit_load_slot_count_from_snapshot: Option<usize>,
1005    verify_index: bool,
1006    accounts_db_config: AccountsDbConfig,
1007    accounts_update_notifier: Option<AccountsUpdateNotifier>,
1008    exit: Arc<AtomicBool>,
1009) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), SnapshotError> {
1010    let mut accounts_db = AccountsDb::new_with_config(
1011        account_paths.to_vec(),
1012        accounts_db_config,
1013        accounts_update_notifier,
1014        exit,
1015    );
1016
1017    let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
1018
1019    // Ensure all account paths exist
1020    for path in &accounts_db.paths {
1021        std::fs::create_dir_all(path)
1022            .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
1023    }
1024
1025    let StorageAndNextAccountsFileId {
1026        storage,
1027        next_append_vec_id,
1028    } = storage_and_next_append_vec_id;
1029
1030    assert!(
1031        !storage.is_empty(),
1032        "At least one storage entry must exist from deserializing stream"
1033    );
1034
1035    let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
1036    let max_append_vec_id = next_append_vec_id - 1;
1037    assert!(
1038        max_append_vec_id <= AccountsFileId::MAX / 2,
1039        "Storage id {max_append_vec_id} larger than allowed max"
1040    );
1041
1042    // Process deserialized data, set necessary fields in self
1043    accounts_db.storage.initialize(storage);
1044    accounts_db
1045        .next_id
1046        .store(next_append_vec_id, Ordering::Release);
1047
1048    info!("Building accounts index...");
1049    let start = Instant::now();
1050    let IndexGenerationInfo {
1051        accounts_data_len,
1052        calculated_accounts_lt_hash,
1053        calculated_capitalization,
1054    } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1055    info!("Building accounts index... Done in {:?}", start.elapsed());
1056
1057    Ok((
1058        accounts_db,
1059        ReconstructedAccountsDbInfo {
1060            accounts_data_len,
1061            calculated_accounts_lt_hash,
1062            calculated_capitalization,
1063            bank_hash_stats: snapshot_bank_hash_info.stats,
1064        },
1065    ))
1066}
1067
1068// Rename `src` to `dest` only if `dest` doesn't already exist.
1069#[cfg(all(target_os = "linux", target_env = "gnu"))]
1070fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1071    let ret = unsafe {
1072        libc::renameat2(
1073            libc::AT_FDCWD,
1074            src.as_ptr() as *const _,
1075            libc::AT_FDCWD,
1076            dest.as_ptr() as *const _,
1077            libc::RENAME_NOREPLACE,
1078        )
1079    };
1080    if ret == -1 {
1081        return Err(io::Error::last_os_error());
1082    }
1083
1084    Ok(())
1085}
1086
1087#[cfg(all(target_os = "linux", target_env = "gnu"))]
1088fn cstring_from_path(path: &Path) -> io::Result<CString> {
1089    // It is better to allocate here than use the stack. Jemalloc is going to give us a chunk of a
1090    // preallocated small arena anyway. Instead if we used the stack since PATH_MAX=4096 it would
1091    // result in LLVM inserting a stack probe, see
1092    // https://docs.rs/compiler_builtins/latest/compiler_builtins/probestack/index.html.
1093    CString::new(path.as_os_str().as_encoded_bytes())
1094        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1095}