Skip to main content

solana_runtime/
serde_snapshot.rs

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