Skip to main content

solana_runtime/
serde_snapshot.rs

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