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