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 status_cache;
72mod storage;
73mod storages_list;
74mod tests;
75mod types;
76
77pub(crate) use {
78 obsolete_accounts::{SerdeObsoleteAccounts, SerdeObsoleteAccountsMap},
79 status_cache::{deserialize_status_cache, serialize_status_cache},
80 storage::{SerializableAccountStorageEntry, SerializedAccountsFileId},
81 storages_list::{StorageListItem, StoragesList},
82};
83
84const MAX_STREAM_SIZE: usize = 32 * 1024 * 1024 * 1024;
85type MaxStreamSizeConfig = wincode::config::Configuration<true, MAX_STREAM_SIZE>;
86
87#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
90#[derive(Debug, Serialize, Deserialize, SchemaRead, SchemaWrite)]
91pub(crate) struct SlotAccountStorageEntries {
92 slot: Slot,
93 #[cfg_attr(
97 feature = "frozen-abi",
98 stable_abi_sample(with = "solana_frozen_abi::stable_abi::sample_collection_sized(rng, \
99 solana_frozen_abi::stable_abi::context::SequenceLenRange::new(0.\
100 .=5))")
101 )]
102 entries: SmallVec<[SerializableAccountStorageEntry; 1]>,
103}
104
105#[cfg_attr(
106 feature = "frozen-abi",
107 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
108)]
109#[derive(Debug, Deserialize, SchemaRead)]
110pub(crate) struct AccountsDbFields(
111 #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
114 #[wincode(with = "DiscardSeq<SlotAccountStorageEntries, BincodeLen>")]
115 Vec<SlotAccountStorageEntries>,
116 u64, Slot,
118 BankHashInfo,
119 #[serde(deserialize_with = "default_on_eof")]
121 #[wincode(with = "DefaultOnEmptyRead<Vec<Slot>>")]
122 Vec<Slot>,
123 #[serde(deserialize_with = "default_on_eof")]
125 #[wincode(with = "DefaultOnEmptyRead<Vec<(Slot, Hash)>>")]
126 Vec<(Slot, Hash)>,
127);
128
129#[repr(C)]
130#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
131#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
132#[derive(Serialize, Deserialize, Clone, Debug, SchemaRead, SchemaWrite)]
133pub struct UnusedIncrementalSnapshotPersistence {
134 pub full_slot: u64,
135 pub full_hash: [u8; 32],
136 pub full_capitalization: u64,
137 pub incremental_hash: [u8; 32],
138 pub incremental_capitalization: u64,
139}
140
141#[repr(C)]
142#[cfg_attr(
143 feature = "frozen-abi",
144 derive(AbiExample, StableAbi, StableAbiSample),
145 frozen_abi(
146 abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
147 abi_serializer = ["bincode", "wincode"],
148 test_roundtrip = "eq_and_wire"
149 )
150)]
151#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, SchemaRead, SchemaWrite)]
152struct BankHashInfo {
153 unused_accounts_delta_hash: [u8; 32],
154 unused_accounts_hash: [u8; 32],
155 stats: BankHashStats,
156}
157
158#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
159#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, SchemaRead, SchemaWrite)]
160struct UnusedAccounts {
161 unused1: HashSet<Pubkey>,
162 unused2: HashSet<Pubkey>,
163 unused3: HashMap<Pubkey, u64>,
164}
165
166#[cfg_attr(
170 feature = "frozen-abi",
171 derive(Serialize, SchemaWrite, StableAbi, StableAbiSample)
172)]
173#[derive(Clone, Deserialize, SchemaRead)]
174struct DeserializableVersionedBank {
175 blockhash_queue: BlockhashQueue,
176 _unused_ancestors: HashMap<Slot, usize>,
177 hash: Hash,
178 parent_hash: Hash,
179 parent_slot: Slot,
180 hard_forks: HardForks,
181 transaction_count: u64,
182 tick_height: u64,
183 signature_count: u64,
184 capitalization: u64,
185 max_tick_height: u64,
186 hashes_per_tick: Option<u64>,
187 ticks_per_slot: u64,
188 ns_per_slot: u128,
189 genesis_creation_time: UnixTimestamp,
190 slots_per_year: f64,
191 accounts_data_len: u64,
192 slot: Slot,
193 _unused_epoch: Epoch,
194 block_height: u64,
195 leader_id: Pubkey,
196 _unused_collector_fees: u64,
197 _unused_fee_calculator: u64,
198 fee_rate_governor: FeeRateGovernor,
199 _unused_collected_rent: u64,
200 _unused_rent_collector: UnusedRentCollector,
201 epoch_schedule: EpochSchedule,
202 inflation: Inflation,
203 stakes: DeserializableDelegationStakes,
204 _unused_accounts: UnusedAccounts,
205 unused_epoch_stakes: HashMap<Epoch, ()>,
206 is_delta: bool,
207}
208
209impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
210 fn from(dvb: DeserializableVersionedBank) -> Self {
211 const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
214 let mut blockhash_queue = dvb.blockhash_queue;
216 blockhash_queue.refresh_durable_nonce();
217 BankFieldsToDeserialize {
218 blockhash_queue,
219 hash: dvb.hash,
220 parent_hash: dvb.parent_hash,
221 parent_slot: dvb.parent_slot,
222 hard_forks: dvb.hard_forks,
223 transaction_count: dvb.transaction_count,
224 tick_height: dvb.tick_height,
225 signature_count: dvb.signature_count,
226 capitalization: dvb.capitalization,
227 max_tick_height: dvb.max_tick_height,
228 hashes_per_tick: dvb.hashes_per_tick,
229 ticks_per_slot: dvb.ticks_per_slot,
230 ns_per_slot: dvb.ns_per_slot,
231 genesis_creation_time: dvb.genesis_creation_time,
232 slots_per_year: dvb.slots_per_year,
233 accounts_data_len: dvb.accounts_data_len,
234 slot: dvb.slot,
235 block_height: dvb.block_height,
236 leader_id: dvb.leader_id,
237 fee_rate_governor: dvb.fee_rate_governor,
238 epoch_schedule: dvb.epoch_schedule,
239 inflation: dvb.inflation,
240 stakes: dvb.stakes,
241 is_delta: dvb.is_delta,
242 versioned_epoch_stakes: vec![], accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), bank_hash_stats: BankHashStats::default(), block_id: None, }
247 }
248}
249
250#[cfg_attr(
253 feature = "frozen-abi",
254 derive(StableAbi, StableAbiSample),
255 frozen_abi(
258 abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
259 abi_serializer = ["bincode", "wincode"],
260 test_roundtrip = "no"
261 )
262)]
263#[derive(Serialize, SchemaWrite)]
264struct SerializableVersionedBank {
265 blockhash_queue: BlockhashQueue,
266 unused_ancestors: HashMap<Slot, usize>,
267 hash: Hash,
268 parent_hash: Hash,
269 parent_slot: Slot,
270 hard_forks: HardForks,
271 transaction_count: u64,
272 tick_height: u64,
273 signature_count: u64,
274 capitalization: u64,
275 max_tick_height: u64,
276 hashes_per_tick: Option<u64>,
277 ticks_per_slot: u64,
278 ns_per_slot: u128,
279 genesis_creation_time: UnixTimestamp,
280 slots_per_year: f64,
281 accounts_data_len: u64,
282 slot: Slot,
283 unused_epoch: Epoch,
284 block_height: u64,
285 leader_id: Pubkey,
286 unused_collector_fees: u64,
287 unused_fee_calculator: u64,
288 fee_rate_governor: FeeRateGovernor,
289 unused_collected_rent: u64,
290 unused_rent_collector: UnusedRentCollector,
291 epoch_schedule: EpochSchedule,
292 inflation: Inflation,
293 #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
294 stakes: Stakes<StakeAccount<Delegation>>,
295 unused_accounts: UnusedAccounts,
296 unused_epoch_stakes: HashMap<Epoch, ()>,
297 is_delta: bool,
298}
299
300impl From<BankFieldsToSerialize> for SerializableVersionedBank {
301 fn from(rhs: BankFieldsToSerialize) -> Self {
302 Self {
303 blockhash_queue: rhs.blockhash_queue,
304 unused_ancestors: HashMap::default(),
305 hash: rhs.hash,
306 parent_hash: rhs.parent_hash,
307 parent_slot: rhs.parent_slot,
308 hard_forks: rhs.hard_forks,
309 transaction_count: rhs.transaction_count,
310 tick_height: rhs.tick_height,
311 signature_count: rhs.signature_count,
312 capitalization: rhs.capitalization,
313 max_tick_height: rhs.max_tick_height,
314 hashes_per_tick: rhs.hashes_per_tick,
315 ticks_per_slot: rhs.ticks_per_slot,
316 ns_per_slot: rhs.ns_per_slot,
317 genesis_creation_time: rhs.genesis_creation_time,
318 slots_per_year: rhs.slots_per_year,
319 accounts_data_len: rhs.accounts_data_len,
320 slot: rhs.slot,
321 unused_epoch: 0,
322 block_height: rhs.block_height,
323 leader_id: rhs.leader_id,
324 unused_collector_fees: 0,
325 unused_fee_calculator: 0,
326 fee_rate_governor: rhs.fee_rate_governor,
327 unused_collected_rent: u64::default(),
328 unused_rent_collector: UnusedRentCollector::zeroed(),
329 epoch_schedule: rhs.epoch_schedule,
330 inflation: rhs.inflation,
331 stakes: rhs.stakes,
332 unused_accounts: UnusedAccounts::default(),
333 unused_epoch_stakes: HashMap::default(),
334 is_delta: rhs.is_delta,
335 }
336 }
337}
338
339pub struct SnapshotStreams<'a, R> {
342 pub full_snapshot_stream: &'a mut BufReader<R>,
343 pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
344}
345
346#[derive(Debug)]
349pub struct SnapshotBankFields {
350 full: BankFieldsToDeserialize,
351 incremental: Option<BankFieldsToDeserialize>,
352}
353
354impl SnapshotBankFields {
355 pub fn new(
356 full: BankFieldsToDeserialize,
357 incremental: Option<BankFieldsToDeserialize>,
358 ) -> Self {
359 Self { full, incremental }
360 }
361
362 pub fn collapse_into(self) -> BankFieldsToDeserialize {
364 self.incremental.unwrap_or(self.full)
365 }
366}
367
368#[derive(Debug)]
371pub struct SnapshotAccountsDbFields {
372 full_snapshot_accounts_db_fields: AccountsDbFields,
373 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
374}
375
376impl SnapshotAccountsDbFields {
377 pub(crate) fn new(
378 full_snapshot_accounts_db_fields: AccountsDbFields,
379 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
380 ) -> Self {
381 Self {
382 full_snapshot_accounts_db_fields,
383 incremental_snapshot_accounts_db_fields,
384 }
385 }
386
387 fn into_bank_hash_info(self) -> BankHashInfo {
392 let AccountsDbFields(
393 _snapshot_storages,
394 _snapshot_write_version,
395 _snapshot_slot,
396 snapshot_bank_hash_info,
397 _snapshot_historical_roots,
398 _snapshot_historical_roots_with_hash,
399 ) = self
400 .incremental_snapshot_accounts_db_fields
401 .unwrap_or(self.full_snapshot_accounts_db_fields);
402 snapshot_bank_hash_info
403 }
404}
405
406pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
407where
408 W: Write,
409 T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
410{
411 wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
412}
413
414pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
415where
416 R: Reader<'a>,
417 T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
418{
419 wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
420}
421
422#[cfg_attr(
429 feature = "frozen-abi",
430 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
431)]
432#[derive(Clone, Debug, Deserialize, SchemaRead)]
433struct ExtraFieldsToDeserialize {
434 #[serde(deserialize_with = "default_on_eof")]
435 #[wincode(with = "DefaultOnEmptyRead<u64>")]
436 lamports_per_signature: u64,
437 #[serde(deserialize_with = "default_on_eof")]
438 #[wincode(with = "DefaultOnEmptyRead<Option<UnusedIncrementalSnapshotPersistence>>")]
439 _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
440 #[serde(deserialize_with = "default_on_eof")]
441 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
442 _unused_epoch_accounts_hash: Option<Hash>,
443 #[serde(deserialize_with = "default_on_eof")]
444 #[wincode(with = "DefaultOnEmptyRead<Vec<(u64, DeserializableVersionedEpochStakes)>>")]
445 #[cfg_attr(
447 feature = "frozen-abi",
448 stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
449 stable_abi::context::SequenceLenMax(1))")
450 )]
451 versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
452 #[serde(deserialize_with = "default_on_eof")]
453 #[wincode(with = "DefaultOnEmptyRead<Option<SerdeAccountsLtHash>>")]
454 accounts_lt_hash: Option<SerdeAccountsLtHash>,
455 #[serde(deserialize_with = "default_on_eof")]
456 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
457 block_id: Option<Hash>,
458}
459
460#[cfg_attr(
467 feature = "frozen-abi",
468 derive(AbiExample, StableAbi, StableAbiSample),
469 frozen_abi(
472 abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
473 abi_serializer = ["bincode", "wincode"],
474 test_roundtrip = "no"
475 )
476)]
477#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
478#[derive(Debug, Serialize, SchemaWrite)]
479pub struct ExtraFieldsToSerialize {
480 pub lamports_per_signature: u64,
481 pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
482 pub unused_epoch_accounts_hash: Option<Hash>,
483 pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
484 pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
485 pub block_id: Option<Hash>,
486}
487
488#[cfg_attr(
494 feature = "frozen-abi",
495 derive(Deserialize, Serialize, SchemaWrite, StableAbi, StableAbiSample),
496 frozen_abi(
497 abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
498 abi_serializer = ["bincode", "wincode"],
499 test_roundtrip = "wire_only"
500 )
501)]
502#[derive(SchemaRead)]
503struct DeserializableBankSnapshot {
504 bank: DeserializableVersionedBank,
505 accounts_db: AccountsDbFields,
506 extra_fields: ExtraFieldsToDeserialize,
507}
508
509impl DeserializableBankSnapshot {
510 fn into_fields(self) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
512 let Self {
513 bank,
514 accounts_db,
515 extra_fields,
516 } = self;
517 if !bank.unused_epoch_stakes.is_empty() {
518 return Err(wincode::ReadError::InvalidValue(
519 "Expected deserialized bank's unused_epoch_stakes field to be empty",
520 ));
521 }
522 let mut bank_fields = BankFieldsToDeserialize::from(bank);
523 let ExtraFieldsToDeserialize {
524 lamports_per_signature,
525 _unused_incremental_snapshot_persistence,
526 _unused_epoch_accounts_hash,
527 versioned_epoch_stakes,
528 accounts_lt_hash,
529 block_id,
530 } = extra_fields;
531
532 bank_fields.fee_rate_governor = bank_fields
533 .fee_rate_governor
534 .clone_with_lamports_per_signature(lamports_per_signature);
535 bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
536 bank_fields.accounts_lt_hash = accounts_lt_hash
537 .expect("snapshot must have accounts_lt_hash")
538 .into();
539 bank_fields.block_id = block_id;
540
541 Ok((bank_fields, accounts_db))
542 }
543}
544
545pub(crate) fn fields_from_stream<R: Read>(
546 snapshot_stream: &mut BufReader<R>,
547) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
548 deserialize_wincode_from::<_, DeserializableBankSnapshot>(snapshot_stream)?.into_fields()
549}
550
551#[cfg(feature = "dev-context-only-utils")]
552pub(crate) fn fields_from_streams(
553 snapshot_streams: &mut SnapshotStreams<impl Read>,
554) -> wincode::ReadResult<(SnapshotBankFields, SnapshotAccountsDbFields)> {
555 let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
556 fields_from_stream(snapshot_streams.full_snapshot_stream)?;
557 let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
558 snapshot_streams
559 .incremental_snapshot_stream
560 .as_mut()
561 .map(|stream| fields_from_stream(stream))
562 .transpose()?
563 .unzip();
564
565 let snapshot_bank_fields = SnapshotBankFields {
566 full: full_snapshot_bank_fields,
567 incremental: incremental_snapshot_bank_fields,
568 };
569 let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
570 full_snapshot_accounts_db_fields,
571 incremental_snapshot_accounts_db_fields,
572 };
573 Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
574}
575
576#[derive(Debug)]
578pub struct BankFromStreamsInfo {
579 pub calculated_accounts_lt_hash: AccountsLtHash,
582}
583
584#[allow(clippy::too_many_arguments)]
585#[cfg(test)]
586pub(crate) fn bank_from_streams<R>(
587 snapshot_streams: &mut SnapshotStreams<R>,
588 account_paths: &[PathBuf],
589 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
590 genesis_config: &GenesisConfig,
591 runtime_config: &RuntimeConfig,
592 debug_keys: Option<Arc<HashSet<Pubkey>>>,
593 limit_load_slot_count_from_snapshot: Option<usize>,
594 verify_index: bool,
595 accounts_db_config: AccountsDbConfig,
596 accounts_update_notifier: Option<AccountsUpdateNotifier>,
597 exit: Arc<AtomicBool>,
598) -> std::result::Result<(Bank, BankFromStreamsInfo), SnapshotError>
599where
600 R: Read,
601{
602 let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
603 let (bank, info) = reconstruct_bank_from_fields(
604 bank_fields,
605 accounts_db_fields,
606 genesis_config,
607 runtime_config,
608 account_paths,
609 storage_and_next_append_vec_id,
610 debug_keys,
611 None, limit_load_slot_count_from_snapshot,
613 verify_index,
614 accounts_db_config,
615 accounts_update_notifier,
616 exit,
617 )?;
618 Ok((
619 bank,
620 BankFromStreamsInfo {
621 calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
622 },
623 ))
624}
625
626#[cfg(test)]
627pub(crate) fn bank_to_stream<W>(
628 stream: &mut io::BufWriter<W>,
629 bank: &Bank,
630) -> wincode::WriteResult<()>
631where
632 W: Write,
633{
634 let mut bank_fields = bank.get_fields_to_serialize();
635 let bank_hash_stats = bank.get_bank_hash_stats();
636 let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
637 let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
638 let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
639 let block_id = Some(bank_fields.block_id);
640 serialize_bank_snapshot_into_wincode(
641 stream,
642 bank_fields,
643 bank_hash_stats,
644 ExtraFieldsToSerialize {
645 lamports_per_signature,
646 unused_incremental_snapshot_persistence: None,
647 unused_epoch_accounts_hash: None,
648 versioned_epoch_stakes,
649 accounts_lt_hash,
650 block_id,
651 },
652 )
653}
654
655pub fn serialize_bank_snapshot_into(
657 stream: &mut dyn Write,
658 bank_fields: BankFieldsToSerialize,
659 bank_hash_stats: BankHashStats,
660 extra_fields: ExtraFieldsToSerialize,
661) -> Result<(), Error> {
662 let mut serializer = bincode::Serializer::new(
663 stream,
664 bincode::DefaultOptions::new().with_fixint_encoding(),
665 );
666 serialize_bank_snapshot_with(&mut serializer, bank_fields, bank_hash_stats, extra_fields)
667}
668
669#[cfg_attr(
672 feature = "frozen-abi",
673 derive(StableAbi, StableAbiSample),
674 frozen_abi(
677 abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
678 abi_serializer = ["bincode", "wincode"],
679 test_roundtrip = "no"
680 )
681)]
682#[derive(Serialize, SchemaWrite)]
683struct SerializableBankSnapshot {
684 bank: SerializableVersionedBank,
685 accounts_db: SerializableAccountsDb,
686 extra_fields: ExtraFieldsToSerialize,
687}
688
689pub fn serialize_bank_snapshot_with<S>(
691 serializer: S,
692 bank_fields: BankFieldsToSerialize,
693 bank_hash_stats: BankHashStats,
694 extra_fields: ExtraFieldsToSerialize,
695) -> Result<S::Ok, S::Error>
696where
697 S: serde::Serializer,
698{
699 let slot = bank_fields.slot;
700 let snapshot = SerializableBankSnapshot {
701 bank: SerializableVersionedBank::from(bank_fields),
702 accounts_db: SerializableAccountsDb::new(slot, bank_hash_stats),
703 extra_fields,
704 };
705 snapshot.serialize(serializer)
708}
709
710pub fn serialize_bank_snapshot_into_wincode(
715 stream: &mut dyn Write,
716 bank_fields: BankFieldsToSerialize,
717 bank_hash_stats: BankHashStats,
718 extra_fields: ExtraFieldsToSerialize,
719) -> wincode::WriteResult<()> {
720 let slot = bank_fields.slot;
721 let snapshot = SerializableBankSnapshot {
722 bank: SerializableVersionedBank::from(bank_fields),
723 accounts_db: SerializableAccountsDb::new(slot, bank_hash_stats),
724 extra_fields,
725 };
726 serialize_into(stream, &snapshot)
727}
728
729#[cfg_attr(
731 feature = "frozen-abi",
732 derive(StableAbi, StableAbiSample),
733 frozen_abi(
736 abi_digest = "6d9LgxwkMTVHRKGtF8QSFFn3rYG8MSmyT9wPrL1HESu1",
737 abi_serializer = ["bincode", "wincode"],
738 test_roundtrip = "no"
739 )
740)]
741#[derive(Serialize, SchemaWrite)]
742struct SerializableAccountsDb {
743 #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
746 unused_accounts_storage_entries: Vec<SlotAccountStorageEntries>,
747 unused_write_version: u64, slot: Slot,
749 bank_hash_info: BankHashInfo,
750 historical_roots: Vec<Slot>,
752 historical_roots_with_hash: Vec<(Slot, Hash)>,
754}
755
756impl SerializableAccountsDb {
757 fn new(slot: Slot, bank_hash_stats: BankHashStats) -> Self {
758 let bank_hash_info = BankHashInfo {
759 unused_accounts_delta_hash: [0; 32],
760 unused_accounts_hash: [0; 32],
761 stats: bank_hash_stats,
762 };
763 SerializableAccountsDb {
764 unused_accounts_storage_entries: Vec::default(),
765 unused_write_version: 0,
766 slot,
767 bank_hash_info,
768 historical_roots: Vec::default(),
769 historical_roots_with_hash: Vec::default(),
770 }
771 }
772}
773
774#[derive(Debug)]
776pub(crate) struct ReconstructedBankInfo {
777 pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
780 pub(crate) calculated_capitalization: u64,
782}
783
784#[expect(clippy::too_many_arguments)]
785pub(crate) fn reconstruct_bank_from_fields(
786 bank_fields: SnapshotBankFields,
787 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
788 genesis_config: &GenesisConfig,
789 runtime_config: &RuntimeConfig,
790 account_paths: &[PathBuf],
791 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
792 debug_keys: Option<Arc<HashSet<Pubkey>>>,
793 leader_for_tests: Option<SlotLeader>,
794 limit_load_slot_count_from_snapshot: Option<usize>,
795 verify_index: bool,
796 accounts_db_config: AccountsDbConfig,
797 accounts_update_notifier: Option<AccountsUpdateNotifier>,
798 exit: Arc<AtomicBool>,
799) -> Result<(Bank, ReconstructedBankInfo), SnapshotError> {
800 let mut bank_fields = bank_fields.collapse_into();
801 let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
803 let epoch_stakes_handle = thread::Builder::new()
804 .name("solRctEpochStk".into())
805 .spawn(|| {
806 deserializable_epoch_stakes
807 .into_iter()
808 .map(|(epoch, stakes)| (epoch, stakes.into()))
809 .collect()
810 })?;
811 let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
812 snapshot_accounts_db_fields,
813 account_paths,
814 storage_and_next_append_vec_id,
815 limit_load_slot_count_from_snapshot,
816 verify_index,
817 accounts_db_config,
818 accounts_update_notifier,
819 exit,
820 )?;
821 bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
822
823 let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
824 let runtime_config = Arc::new(runtime_config.clone());
825 let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
826
827 let bank = Bank::new_from_snapshot(
828 bank_rc,
829 genesis_config,
830 runtime_config,
831 bank_fields,
832 leader_for_tests,
833 debug_keys,
834 reconstructed_accounts_db_info.accounts_data_len,
835 epoch_stakes,
836 );
837
838 Ok((
839 bank,
840 ReconstructedBankInfo {
841 calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
842 calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
843 },
844 ))
845}
846
847pub(crate) fn reconstruct_single_storage(
848 slot: &Slot,
849 append_vec_file_info: FileInfo,
850 id: AccountsFileId,
851 obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
852) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
853 let obsolete_accounts =
864 if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
865 if obsolete_id != id {
866 return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
867 }
868
869 obsolete_accounts
870 } else {
871 ObsoleteAccounts::default()
872 };
873
874 let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
875 Ok(Arc::new(AccountStorageEntry::new_existing(
876 *slot,
877 id,
878 accounts_file,
879 obsolete_accounts,
880 )))
881}
882
883pub(crate) fn remap_append_vec_file(
887 slot: Slot,
888 old_append_vec_id: SerializedAccountsFileId,
889 append_vec_file_info: FileInfo,
890 next_append_vec_id: &AtomicAccountsFileId,
891 num_collisions: &mut usize,
892) -> io::Result<(AccountsFileId, FileInfo)> {
893 #[cfg(all(target_os = "linux", target_env = "gnu"))]
894 let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
895
896 let mut remapped_append_vec_path = append_vec_file_info.path.clone();
897
898 let (remapped_append_vec_id, remapped_append_vec_path) = loop {
904 let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
905
906 if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
908 break (remapped_append_vec_id, remapped_append_vec_path);
909 }
910
911 let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
912 remapped_append_vec_path = remapped_append_vec_path
913 .parent()
914 .unwrap()
915 .join(remapped_file_name);
916
917 #[cfg(all(target_os = "linux", target_env = "gnu"))]
918 {
919 let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
920
921 match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
924 Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
926 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
929 Err(e) => return Err(e),
930 }
931 }
932
933 #[cfg(any(
934 not(target_os = "linux"),
935 all(target_os = "linux", not(target_env = "gnu"))
936 ))]
937 if std::fs::metadata(&remapped_append_vec_path).is_err() {
938 break (remapped_append_vec_id, remapped_append_vec_path);
939 }
940
941 *num_collisions += 1;
944 };
945
946 #[cfg(any(
949 not(target_os = "linux"),
950 all(target_os = "linux", not(target_env = "gnu"))
951 ))]
952 if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
953 std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
954 }
955
956 Ok((
957 remapped_append_vec_id,
958 FileInfo {
959 path: remapped_append_vec_path,
960 ..append_vec_file_info
961 },
962 ))
963}
964
965pub(crate) fn remap_and_reconstruct_single_storage(
966 slot: Slot,
967 old_append_vec_id: SerializedAccountsFileId,
968 append_vec_file_info: FileInfo,
969 next_append_vec_id: &AtomicAccountsFileId,
970 num_collisions: &mut usize,
971) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
972 let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
973 slot,
974 old_append_vec_id,
975 append_vec_file_info,
976 next_append_vec_id,
977 num_collisions,
978 )?;
979 let storage = reconstruct_single_storage(
980 &slot,
981 remapped_append_vec_file_info,
982 remapped_append_vec_id,
983 None,
984 )?;
985 Ok(storage)
986}
987
988#[derive(Debug)]
990pub struct ReconstructedAccountsDbInfo {
991 pub accounts_data_len: u64,
992 pub calculated_accounts_lt_hash: AccountsLtHash,
995 pub calculated_capitalization: u64,
997 pub bank_hash_stats: BankHashStats,
998}
999
1000fn reconstruct_accountsdb_from_fields(
1001 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
1002 account_paths: &[PathBuf],
1003 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
1004 limit_load_slot_count_from_snapshot: Option<usize>,
1005 verify_index: bool,
1006 accounts_db_config: AccountsDbConfig,
1007 accounts_update_notifier: Option<AccountsUpdateNotifier>,
1008 exit: Arc<AtomicBool>,
1009) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), SnapshotError> {
1010 let mut accounts_db = AccountsDb::new_with_config(
1011 account_paths.to_vec(),
1012 accounts_db_config,
1013 accounts_update_notifier,
1014 exit,
1015 );
1016
1017 let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
1018
1019 for path in &accounts_db.paths {
1021 std::fs::create_dir_all(path)
1022 .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
1023 }
1024
1025 let StorageAndNextAccountsFileId {
1026 storage,
1027 next_append_vec_id,
1028 } = storage_and_next_append_vec_id;
1029
1030 assert!(
1031 !storage.is_empty(),
1032 "At least one storage entry must exist from deserializing stream"
1033 );
1034
1035 let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
1036 let max_append_vec_id = next_append_vec_id - 1;
1037 assert!(
1038 max_append_vec_id <= AccountsFileId::MAX / 2,
1039 "Storage id {max_append_vec_id} larger than allowed max"
1040 );
1041
1042 accounts_db.storage.initialize(storage);
1044 accounts_db
1045 .next_id
1046 .store(next_append_vec_id, Ordering::Release);
1047
1048 info!("Building accounts index...");
1049 let start = Instant::now();
1050 let IndexGenerationInfo {
1051 accounts_data_len,
1052 calculated_accounts_lt_hash,
1053 calculated_capitalization,
1054 } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1055 info!("Building accounts index... Done in {:?}", start.elapsed());
1056
1057 Ok((
1058 accounts_db,
1059 ReconstructedAccountsDbInfo {
1060 accounts_data_len,
1061 calculated_accounts_lt_hash,
1062 calculated_capitalization,
1063 bank_hash_stats: snapshot_bank_hash_info.stats,
1064 },
1065 ))
1066}
1067
1068#[cfg(all(target_os = "linux", target_env = "gnu"))]
1070fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1071 let ret = unsafe {
1072 libc::renameat2(
1073 libc::AT_FDCWD,
1074 src.as_ptr() as *const _,
1075 libc::AT_FDCWD,
1076 dest.as_ptr() as *const _,
1077 libc::RENAME_NOREPLACE,
1078 )
1079 };
1080 if ret == -1 {
1081 return Err(io::Error::last_os_error());
1082 }
1083
1084 Ok(())
1085}
1086
1087#[cfg(all(target_os = "linux", target_env = "gnu"))]
1088fn cstring_from_path(path: &Path) -> io::Result<CString> {
1089 CString::new(path.as_os_str().as_encoded_bytes())
1094 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1095}