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