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