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