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, smallvec},
25 solana_accounts_db::{
26 ObsoleteAccounts,
27 account_storage_entry::AccountStorageEntry,
28 accounts::Accounts,
29 accounts_db::{
30 AccountsDb, AccountsDbConfig, AccountsFileId, AtomicAccountsFileId, IndexGenerationInfo,
31 },
32 accounts_file::AccountsFile,
33 accounts_hash::AccountsLtHash,
34 accounts_update_notifier_interface::AccountsUpdateNotifier,
35 blockhash_queue::BlockhashQueue,
36 },
37 solana_clock::{Epoch, Slot, UnixTimestamp},
38 solana_epoch_schedule::EpochSchedule,
39 solana_fee_calculator::FeeRateGovernor,
40 solana_genesis_config::GenesisConfig,
41 solana_hard_forks::HardForks,
42 solana_hash::Hash,
43 solana_inflation::Inflation,
44 solana_lattice_hash::lt_hash::LtHash,
45 solana_leader_schedule::SlotLeader,
46 solana_pubkey::Pubkey,
47 solana_serde::default_on_eof,
48 solana_stake_interface::state::Delegation,
49 std::{
50 borrow::Borrow,
51 collections::{HashMap, HashSet},
52 io::{self, BufReader, Read, Write},
53 path::PathBuf,
54 result::Result,
55 sync::{
56 Arc,
57 atomic::{AtomicBool, Ordering},
58 },
59 thread,
60 time::Instant,
61 },
62 types::{SerdeAccountsLtHash, UnusedRentCollector},
63 wincode::{
64 ReadResult, SchemaRead, SchemaReadOwned, SchemaWrite, WriteResult,
65 containers::FromIntoIterator,
66 io::{Reader, std_write::WriteAdapter},
67 len::BincodeLen,
68 },
69};
70
71mod obsolete_accounts;
72mod status_cache;
73mod storage;
74mod storages_list;
75mod tests;
76mod types;
77
78pub(crate) use {
79 obsolete_accounts::{SerdeObsoleteAccounts, SerdeObsoleteAccountsMap},
80 status_cache::{deserialize_status_cache, serialize_status_cache},
81 storage::{SerializableAccountStorageEntry, SerializedAccountsFileId},
82 storages_list::{StorageListItem, StoragesList},
83};
84
85const MAX_STREAM_SIZE: usize = 32 * 1024 * 1024 * 1024;
86type MaxStreamSizeConfig = wincode::config::Configuration<true, MAX_STREAM_SIZE>;
87
88mod wincode_compat {
90 use {
91 std::{marker::PhantomData, mem::MaybeUninit},
92 wincode::{
93 ReadError, ReadResult, SchemaRead, SchemaWrite, WriteResult,
94 config::Config,
95 io::{ReadError as IoReadError, Reader, Writer},
96 },
97 };
98
99 pub(super) struct DefaultOnEmptyRead<T>(PhantomData<T>);
103
104 unsafe impl<'de, C: Config, T> SchemaRead<'de, C> for DefaultOnEmptyRead<T>
107 where
108 T: SchemaRead<'de, C>,
109 T::Dst: Default,
110 {
111 type Dst = T::Dst;
112
113 fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
114 match <T as SchemaRead<'de, C>>::read(reader, dst) {
115 Ok(()) => Ok(()),
116 Err(ReadError::Io(IoReadError::ReadSizeLimit(_))) => {
117 dst.write(Self::Dst::default());
118 Ok(())
119 }
120 Err(e) => Err(e),
121 }
122 }
123 }
124
125 unsafe impl<C: Config, T> SchemaWrite<C> for DefaultOnEmptyRead<T>
126 where
127 T: SchemaWrite<C>,
128 {
129 type Src = T::Src;
130
131 const TYPE_META: wincode::TypeMeta = T::TYPE_META;
132
133 fn size_of(src: &Self::Src) -> WriteResult<usize> {
134 <T as SchemaWrite<C>>::size_of(src)
135 }
136
137 fn write(writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
138 <T as SchemaWrite<C>>::write(writer, src)
139 }
140 }
141}
142
143#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
146#[derive(Debug, Serialize, Deserialize, SchemaRead, SchemaWrite)]
147pub(crate) struct SlotAccountStorageEntries {
148 slot: Slot,
149 #[cfg_attr(
152 feature = "frozen-abi",
153 stable_abi_sample(with = "solana_frozen_abi::stable_abi::sample_collection_sized(rng, \
154 solana_frozen_abi::stable_abi::context::SequenceLenRange::new(0.\
155 .=5))")
156 )]
157 entries: SmallVec<[SerializableAccountStorageEntry; 1]>,
158}
159
160#[cfg_attr(
161 feature = "frozen-abi",
162 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
163)]
164#[derive(Debug, Deserialize, SchemaRead)]
165pub(crate) struct AccountsDbFields(
166 Vec<SlotAccountStorageEntries>,
167 u64, Slot,
169 BankHashInfo,
170 #[serde(deserialize_with = "default_on_eof")]
172 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<Vec<Slot>>")]
173 Vec<Slot>,
174 #[serde(deserialize_with = "default_on_eof")]
176 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<Vec<(Slot, Hash)>>")]
177 Vec<(Slot, Hash)>,
178);
179
180#[repr(C)]
181#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
182#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
183#[derive(Serialize, Deserialize, Clone, Debug, SchemaRead, SchemaWrite)]
184pub struct UnusedIncrementalSnapshotPersistence {
185 pub full_slot: u64,
186 pub full_hash: [u8; 32],
187 pub full_capitalization: u64,
188 pub incremental_hash: [u8; 32],
189 pub incremental_capitalization: u64,
190}
191
192#[repr(C)]
193#[cfg_attr(
194 feature = "frozen-abi",
195 derive(AbiExample, StableAbi, StableAbiSample),
196 frozen_abi(
197 abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
198 abi_serializer = ["bincode", "wincode"],
199 test_roundtrip = "eq_and_wire"
200 )
201)]
202#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, SchemaRead, SchemaWrite)]
203struct BankHashInfo {
204 unused_accounts_delta_hash: [u8; 32],
205 unused_accounts_hash: [u8; 32],
206 stats: BankHashStats,
207}
208
209#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
210#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, SchemaRead, SchemaWrite)]
211struct UnusedAccounts {
212 unused1: HashSet<Pubkey>,
213 unused2: HashSet<Pubkey>,
214 unused3: HashMap<Pubkey, u64>,
215}
216
217#[cfg_attr(
221 feature = "frozen-abi",
222 derive(Serialize, SchemaWrite, StableAbi, StableAbiSample)
223)]
224#[derive(Clone, Deserialize, SchemaRead)]
225struct DeserializableVersionedBank {
226 blockhash_queue: BlockhashQueue,
227 _unused_ancestors: HashMap<Slot, usize>,
228 hash: Hash,
229 parent_hash: Hash,
230 parent_slot: Slot,
231 hard_forks: HardForks,
232 transaction_count: u64,
233 tick_height: u64,
234 signature_count: u64,
235 capitalization: u64,
236 max_tick_height: u64,
237 hashes_per_tick: Option<u64>,
238 ticks_per_slot: u64,
239 ns_per_slot: u128,
240 genesis_creation_time: UnixTimestamp,
241 slots_per_year: f64,
242 accounts_data_len: u64,
243 slot: Slot,
244 _unused_epoch: Epoch,
245 block_height: u64,
246 leader_id: Pubkey,
247 _unused_collector_fees: u64,
248 _unused_fee_calculator: u64,
249 fee_rate_governor: FeeRateGovernor,
250 _unused_collected_rent: u64,
251 _unused_rent_collector: UnusedRentCollector,
252 epoch_schedule: EpochSchedule,
253 inflation: Inflation,
254 stakes: DeserializableDelegationStakes,
255 _unused_accounts: UnusedAccounts,
256 unused_epoch_stakes: HashMap<Epoch, ()>,
257 is_delta: bool,
258}
259
260impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
261 fn from(dvb: DeserializableVersionedBank) -> Self {
262 const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
265 let mut blockhash_queue = dvb.blockhash_queue;
267 blockhash_queue.refresh_durable_nonce();
268 BankFieldsToDeserialize {
269 blockhash_queue,
270 hash: dvb.hash,
271 parent_hash: dvb.parent_hash,
272 parent_slot: dvb.parent_slot,
273 hard_forks: dvb.hard_forks,
274 transaction_count: dvb.transaction_count,
275 tick_height: dvb.tick_height,
276 signature_count: dvb.signature_count,
277 capitalization: dvb.capitalization,
278 max_tick_height: dvb.max_tick_height,
279 hashes_per_tick: dvb.hashes_per_tick,
280 ticks_per_slot: dvb.ticks_per_slot,
281 ns_per_slot: dvb.ns_per_slot,
282 genesis_creation_time: dvb.genesis_creation_time,
283 slots_per_year: dvb.slots_per_year,
284 accounts_data_len: dvb.accounts_data_len,
285 slot: dvb.slot,
286 block_height: dvb.block_height,
287 leader_id: dvb.leader_id,
288 fee_rate_governor: dvb.fee_rate_governor,
289 epoch_schedule: dvb.epoch_schedule,
290 inflation: dvb.inflation,
291 stakes: dvb.stakes,
292 is_delta: dvb.is_delta,
293 versioned_epoch_stakes: vec![], accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), bank_hash_stats: BankHashStats::default(), block_id: None, }
298 }
299}
300
301#[cfg_attr(
304 feature = "frozen-abi",
305 derive(StableAbi, StableAbiSample),
306 frozen_abi(
309 abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
310 abi_serializer = ["bincode", "wincode"],
311 test_roundtrip = "no"
312 )
313)]
314#[derive(Serialize, SchemaWrite)]
315struct SerializableVersionedBank {
316 blockhash_queue: BlockhashQueue,
317 unused_ancestors: HashMap<Slot, usize>,
318 hash: Hash,
319 parent_hash: Hash,
320 parent_slot: Slot,
321 hard_forks: HardForks,
322 transaction_count: u64,
323 tick_height: u64,
324 signature_count: u64,
325 capitalization: u64,
326 max_tick_height: u64,
327 hashes_per_tick: Option<u64>,
328 ticks_per_slot: u64,
329 ns_per_slot: u128,
330 genesis_creation_time: UnixTimestamp,
331 slots_per_year: f64,
332 accounts_data_len: u64,
333 slot: Slot,
334 unused_epoch: Epoch,
335 block_height: u64,
336 leader_id: Pubkey,
337 unused_collector_fees: u64,
338 unused_fee_calculator: u64,
339 fee_rate_governor: FeeRateGovernor,
340 unused_collected_rent: u64,
341 unused_rent_collector: UnusedRentCollector,
342 epoch_schedule: EpochSchedule,
343 inflation: Inflation,
344 #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
345 stakes: Stakes<StakeAccount<Delegation>>,
346 unused_accounts: UnusedAccounts,
347 unused_epoch_stakes: HashMap<Epoch, ()>,
348 is_delta: bool,
349}
350
351impl From<BankFieldsToSerialize> for SerializableVersionedBank {
352 fn from(rhs: BankFieldsToSerialize) -> Self {
353 Self {
354 blockhash_queue: rhs.blockhash_queue,
355 unused_ancestors: HashMap::default(),
356 hash: rhs.hash,
357 parent_hash: rhs.parent_hash,
358 parent_slot: rhs.parent_slot,
359 hard_forks: rhs.hard_forks,
360 transaction_count: rhs.transaction_count,
361 tick_height: rhs.tick_height,
362 signature_count: rhs.signature_count,
363 capitalization: rhs.capitalization,
364 max_tick_height: rhs.max_tick_height,
365 hashes_per_tick: rhs.hashes_per_tick,
366 ticks_per_slot: rhs.ticks_per_slot,
367 ns_per_slot: rhs.ns_per_slot,
368 genesis_creation_time: rhs.genesis_creation_time,
369 slots_per_year: rhs.slots_per_year,
370 accounts_data_len: rhs.accounts_data_len,
371 slot: rhs.slot,
372 unused_epoch: 0,
373 block_height: rhs.block_height,
374 leader_id: rhs.leader_id,
375 unused_collector_fees: 0,
376 unused_fee_calculator: 0,
377 fee_rate_governor: rhs.fee_rate_governor,
378 unused_collected_rent: u64::default(),
379 unused_rent_collector: UnusedRentCollector::zeroed(),
380 epoch_schedule: rhs.epoch_schedule,
381 inflation: rhs.inflation,
382 stakes: rhs.stakes,
383 unused_accounts: UnusedAccounts::default(),
384 unused_epoch_stakes: HashMap::default(),
385 is_delta: rhs.is_delta,
386 }
387 }
388}
389
390pub struct SnapshotStreams<'a, R> {
393 pub full_snapshot_stream: &'a mut BufReader<R>,
394 pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
395}
396
397#[derive(Debug)]
400pub struct SnapshotBankFields {
401 full: BankFieldsToDeserialize,
402 incremental: Option<BankFieldsToDeserialize>,
403}
404
405impl SnapshotBankFields {
406 pub fn new(
407 full: BankFieldsToDeserialize,
408 incremental: Option<BankFieldsToDeserialize>,
409 ) -> Self {
410 Self { full, incremental }
411 }
412
413 pub fn collapse_into(self) -> BankFieldsToDeserialize {
415 self.incremental.unwrap_or(self.full)
416 }
417}
418
419#[derive(Debug)]
422pub struct SnapshotAccountsDbFields {
423 full_snapshot_accounts_db_fields: AccountsDbFields,
424 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
425}
426
427impl SnapshotAccountsDbFields {
428 pub(crate) fn new(
429 full_snapshot_accounts_db_fields: AccountsDbFields,
430 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
431 ) -> Self {
432 Self {
433 full_snapshot_accounts_db_fields,
434 incremental_snapshot_accounts_db_fields,
435 }
436 }
437
438 fn into_bank_hash_info(self) -> BankHashInfo {
443 let AccountsDbFields(
444 _snapshot_storages,
445 _snapshot_write_version,
446 _snapshot_slot,
447 snapshot_bank_hash_info,
448 _snapshot_historical_roots,
449 _snapshot_historical_roots_with_hash,
450 ) = self
451 .incremental_snapshot_accounts_db_fields
452 .unwrap_or(self.full_snapshot_accounts_db_fields);
453 snapshot_bank_hash_info
454 }
455}
456
457pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
458where
459 W: Write,
460 T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
461{
462 wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
463}
464
465pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
466where
467 R: Reader<'a>,
468 T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
469{
470 wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
471}
472
473#[cfg_attr(
480 feature = "frozen-abi",
481 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
482)]
483#[derive(Clone, Debug, Deserialize, SchemaRead)]
484struct ExtraFieldsToDeserialize {
485 #[serde(deserialize_with = "default_on_eof")]
486 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<u64>")]
487 lamports_per_signature: u64,
488 #[serde(deserialize_with = "default_on_eof")]
489 #[wincode(
490 with = "wincode_compat::DefaultOnEmptyRead<Option<UnusedIncrementalSnapshotPersistence>>"
491 )]
492 _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
493 #[serde(deserialize_with = "default_on_eof")]
494 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<Option<Hash>>")]
495 _unused_epoch_accounts_hash: Option<Hash>,
496 #[serde(deserialize_with = "default_on_eof")]
497 #[wincode(
498 with = "wincode_compat::DefaultOnEmptyRead<Vec<(u64, DeserializableVersionedEpochStakes)>>"
499 )]
500 #[cfg_attr(
502 feature = "frozen-abi",
503 stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
504 stable_abi::context::SequenceLenMax(1))")
505 )]
506 versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
507 #[serde(deserialize_with = "default_on_eof")]
508 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<Option<SerdeAccountsLtHash>>")]
509 accounts_lt_hash: Option<SerdeAccountsLtHash>,
510 #[serde(deserialize_with = "default_on_eof")]
511 #[wincode(with = "wincode_compat::DefaultOnEmptyRead<Option<Hash>>")]
512 block_id: Option<Hash>,
513}
514
515#[cfg_attr(
522 feature = "frozen-abi",
523 derive(AbiExample, StableAbi, StableAbiSample),
524 frozen_abi(
527 abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
528 abi_serializer = ["bincode", "wincode"],
529 test_roundtrip = "no"
530 )
531)]
532#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
533#[derive(Debug, Serialize, SchemaWrite)]
534pub struct ExtraFieldsToSerialize {
535 pub lamports_per_signature: u64,
536 pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
537 pub unused_epoch_accounts_hash: Option<Hash>,
538 pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
539 pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
540 pub block_id: Option<Hash>,
541}
542
543#[cfg_attr(
549 feature = "frozen-abi",
550 derive(Deserialize, Serialize, SchemaWrite, StableAbi, StableAbiSample),
551 frozen_abi(
552 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
553 abi_serializer = ["bincode", "wincode"],
554 test_roundtrip = "wire_only"
555 )
556)]
557#[derive(SchemaRead)]
558struct DeserializableBankSnapshot {
559 bank: DeserializableVersionedBank,
560 accounts_db: AccountsDbFields,
561 extra_fields: ExtraFieldsToDeserialize,
562}
563
564impl DeserializableBankSnapshot {
565 fn into_fields(self) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
567 let Self {
568 bank,
569 accounts_db,
570 extra_fields,
571 } = self;
572 if !bank.unused_epoch_stakes.is_empty() {
573 return Err(wincode::ReadError::InvalidValue(
574 "Expected deserialized bank's unused_epoch_stakes field to be empty",
575 ));
576 }
577 let mut bank_fields = BankFieldsToDeserialize::from(bank);
578 let ExtraFieldsToDeserialize {
579 lamports_per_signature,
580 _unused_incremental_snapshot_persistence,
581 _unused_epoch_accounts_hash,
582 versioned_epoch_stakes,
583 accounts_lt_hash,
584 block_id,
585 } = extra_fields;
586
587 bank_fields.fee_rate_governor = bank_fields
588 .fee_rate_governor
589 .clone_with_lamports_per_signature(lamports_per_signature);
590 bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
591 bank_fields.accounts_lt_hash = accounts_lt_hash
592 .expect("snapshot must have accounts_lt_hash")
593 .into();
594 bank_fields.block_id = block_id;
595
596 Ok((bank_fields, accounts_db))
597 }
598}
599
600pub(crate) fn fields_from_stream<R: Read>(
601 snapshot_stream: &mut BufReader<R>,
602) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
603 deserialize_wincode_from::<_, DeserializableBankSnapshot>(snapshot_stream)?.into_fields()
604}
605
606#[cfg(feature = "dev-context-only-utils")]
607pub(crate) fn fields_from_streams(
608 snapshot_streams: &mut SnapshotStreams<impl Read>,
609) -> wincode::ReadResult<(SnapshotBankFields, SnapshotAccountsDbFields)> {
610 let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
611 fields_from_stream(snapshot_streams.full_snapshot_stream)?;
612 let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
613 snapshot_streams
614 .incremental_snapshot_stream
615 .as_mut()
616 .map(|stream| fields_from_stream(stream))
617 .transpose()?
618 .unzip();
619
620 let snapshot_bank_fields = SnapshotBankFields {
621 full: full_snapshot_bank_fields,
622 incremental: incremental_snapshot_bank_fields,
623 };
624 let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
625 full_snapshot_accounts_db_fields,
626 incremental_snapshot_accounts_db_fields,
627 };
628 Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
629}
630
631#[derive(Debug)]
633pub struct BankFromStreamsInfo {
634 pub calculated_accounts_lt_hash: AccountsLtHash,
637}
638
639#[allow(clippy::too_many_arguments)]
640#[cfg(test)]
641pub(crate) fn bank_from_streams<R>(
642 snapshot_streams: &mut SnapshotStreams<R>,
643 account_paths: &[PathBuf],
644 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
645 genesis_config: &GenesisConfig,
646 runtime_config: &RuntimeConfig,
647 debug_keys: Option<Arc<HashSet<Pubkey>>>,
648 limit_load_slot_count_from_snapshot: Option<usize>,
649 verify_index: bool,
650 accounts_db_config: AccountsDbConfig,
651 accounts_update_notifier: Option<AccountsUpdateNotifier>,
652 exit: Arc<AtomicBool>,
653) -> std::result::Result<(Bank, BankFromStreamsInfo), SnapshotError>
654where
655 R: Read,
656{
657 let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
658 let (bank, info) = reconstruct_bank_from_fields(
659 bank_fields,
660 accounts_db_fields,
661 genesis_config,
662 runtime_config,
663 account_paths,
664 storage_and_next_append_vec_id,
665 debug_keys,
666 None, limit_load_slot_count_from_snapshot,
668 verify_index,
669 accounts_db_config,
670 accounts_update_notifier,
671 exit,
672 )?;
673 Ok((
674 bank,
675 BankFromStreamsInfo {
676 calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
677 },
678 ))
679}
680
681#[cfg(test)]
682pub(crate) fn bank_to_stream<W>(
683 stream: &mut io::BufWriter<W>,
684 bank: &Bank,
685 snapshot_storages: &[Arc<AccountStorageEntry>],
686) -> wincode::WriteResult<()>
687where
688 W: Write,
689{
690 let mut bank_fields = bank.get_fields_to_serialize();
691 let bank_hash_stats = bank.get_bank_hash_stats();
692 let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
693 let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
694 let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
695 let block_id = Some(bank_fields.block_id);
696 serialize_bank_snapshot_into_wincode(
697 stream,
698 bank_fields,
699 bank_hash_stats,
700 snapshot_storages,
701 ExtraFieldsToSerialize {
702 lamports_per_signature,
703 unused_incremental_snapshot_persistence: None,
704 unused_epoch_accounts_hash: None,
705 versioned_epoch_stakes,
706 accounts_lt_hash,
707 block_id,
708 },
709 )
710}
711
712pub fn serialize_bank_snapshot_into(
714 stream: &mut dyn Write,
715 bank_fields: BankFieldsToSerialize,
716 bank_hash_stats: BankHashStats,
717 account_storage_entries: &[Arc<AccountStorageEntry>],
718 extra_fields: ExtraFieldsToSerialize,
719) -> Result<(), Error> {
720 let mut serializer = bincode::Serializer::new(
721 stream,
722 bincode::DefaultOptions::new().with_fixint_encoding(),
723 );
724 serialize_bank_snapshot_with(
725 &mut serializer,
726 bank_fields,
727 bank_hash_stats,
728 account_storage_entries,
729 extra_fields,
730 )
731}
732
733#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
737#[derive(Serialize, SchemaWrite)]
738struct SerializableBankSnapshot<E> {
739 bank: SerializableVersionedBank,
740 accounts_db: SerializableAccountsDb<E>,
741 extra_fields: ExtraFieldsToSerialize,
742}
743
744#[cfg(all(test, feature = "frozen-abi"))]
749#[frozen_abi(
750 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
751 abi_serializer = ["bincode", "wincode"],
752 test_roundtrip = "no"
753)]
754type SerializableBankSnapshotForAbi = SerializableBankSnapshot<Vec<SlotAccountStorageEntries>>;
755
756pub fn serialize_bank_snapshot_with<S>(
758 serializer: S,
759 bank_fields: BankFieldsToSerialize,
760 bank_hash_stats: BankHashStats,
761 account_storage_entries: &[Arc<AccountStorageEntry>],
762 extra_fields: ExtraFieldsToSerialize,
763) -> Result<S::Ok, S::Error>
764where
765 S: serde::Serializer,
766{
767 let slot = bank_fields.slot;
768 let snapshot = SerializableBankSnapshot {
769 bank: SerializableVersionedBank::from(bank_fields),
770 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
771 extra_fields,
772 };
773 snapshot.serialize(serializer)
776}
777
778pub fn serialize_bank_snapshot_into_wincode(
783 stream: &mut dyn Write,
784 bank_fields: BankFieldsToSerialize,
785 bank_hash_stats: BankHashStats,
786 account_storage_entries: &[Arc<AccountStorageEntry>],
787 extra_fields: ExtraFieldsToSerialize,
788) -> wincode::WriteResult<()> {
789 let slot = bank_fields.slot;
790 let snapshot = SerializableBankSnapshot {
791 bank: SerializableVersionedBank::from(bank_fields),
792 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
793 extra_fields,
794 };
795 serialize_into(stream, &snapshot)
796}
797
798#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
803#[derive(Serialize, SchemaWrite)]
804struct SerializableAccountsDb<E> {
805 accounts_storage_entries: E,
807 unused_write_version: u64, slot: Slot,
809 bank_hash_info: BankHashInfo,
810 historical_roots: Vec<Slot>,
812 historical_roots_with_hash: Vec<(Slot, Hash)>,
814}
815
816struct SerializableExactIteratorView<I>(I);
822
823impl<I: Iterator> IntoIterator for SerializableExactIteratorView<I> {
824 type Item = I::Item;
825 type IntoIter = I;
826
827 fn into_iter(self) -> Self::IntoIter {
828 self.0
829 }
830}
831
832impl<I: Iterator + Clone> IntoIterator for &SerializableExactIteratorView<I> {
833 type Item = I::Item;
834 type IntoIter = I;
835
836 fn into_iter(self) -> Self::IntoIter {
837 self.0.clone()
838 }
839}
840
841impl<I> Serialize for SerializableExactIteratorView<I>
842where
843 I: ExactSizeIterator + Clone,
844 I::Item: Serialize,
845{
846 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
847 where
848 S: serde::Serializer,
849 {
850 serializer.collect_seq(self.0.clone())
851 }
852}
853
854unsafe impl<I, C: wincode::config::Config> SchemaWrite<C> for SerializableExactIteratorView<I>
857where
858 I: ExactSizeIterator + Clone,
859 I::Item: SchemaWrite<C> + Borrow<<I::Item as SchemaWrite<C>>::Src>,
860{
861 type Src = Self;
862
863 fn size_of(src: &Self::Src) -> WriteResult<usize> {
864 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::size_of(
865 src,
866 )
867 }
868
869 fn write(writer: impl wincode::io::Writer, src: &Self::Src) -> WriteResult<()> {
870 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::write(
871 writer, src,
872 )
873 }
874}
875
876impl SerializableAccountsDb<()> {
877 fn new(
878 slot: Slot,
879 account_storage_entries: &[Arc<AccountStorageEntry>],
880 bank_hash_stats: BankHashStats,
881 ) -> SerializableAccountsDb<
882 SerializableExactIteratorView<
883 impl ExactSizeIterator<Item = SlotAccountStorageEntries> + Clone + '_,
884 >,
885 > {
886 let accounts_storage_entries =
890 SerializableExactIteratorView(account_storage_entries.iter().map(move |entry| {
891 SlotAccountStorageEntries {
892 slot: entry.slot(),
893 entries: smallvec![SerializableAccountStorageEntry::new(entry, slot)],
894 }
895 }));
896 let bank_hash_info = BankHashInfo {
897 unused_accounts_delta_hash: [0; 32],
898 unused_accounts_hash: [0; 32],
899 stats: bank_hash_stats,
900 };
901 SerializableAccountsDb {
902 accounts_storage_entries,
903 unused_write_version: 0,
904 slot,
905 bank_hash_info,
906 historical_roots: Vec::default(),
907 historical_roots_with_hash: Vec::default(),
908 }
909 }
910}
911
912#[cfg(all(test, feature = "frozen-abi"))]
917#[frozen_abi(
918 abi_digest = "2TpwtsyrverM4ius4ykX3RRCGqeLfXJQbAvYHkxdUVrs",
919 abi_serializer = ["bincode", "wincode"],
920 test_roundtrip = "no"
921)]
922type SerializableAccountsDbForAbi = SerializableAccountsDb<Vec<SlotAccountStorageEntries>>;
923
924#[derive(Debug)]
926pub(crate) struct ReconstructedBankInfo {
927 pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
930 pub(crate) calculated_capitalization: u64,
932}
933
934#[expect(clippy::too_many_arguments)]
935pub(crate) fn reconstruct_bank_from_fields(
936 bank_fields: SnapshotBankFields,
937 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
938 genesis_config: &GenesisConfig,
939 runtime_config: &RuntimeConfig,
940 account_paths: &[PathBuf],
941 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
942 debug_keys: Option<Arc<HashSet<Pubkey>>>,
943 leader_for_tests: Option<SlotLeader>,
944 limit_load_slot_count_from_snapshot: Option<usize>,
945 verify_index: bool,
946 accounts_db_config: AccountsDbConfig,
947 accounts_update_notifier: Option<AccountsUpdateNotifier>,
948 exit: Arc<AtomicBool>,
949) -> Result<(Bank, ReconstructedBankInfo), SnapshotError> {
950 let mut bank_fields = bank_fields.collapse_into();
951 let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
953 let epoch_stakes_handle = thread::Builder::new()
954 .name("solRctEpochStk".into())
955 .spawn(|| {
956 deserializable_epoch_stakes
957 .into_iter()
958 .map(|(epoch, stakes)| (epoch, stakes.into()))
959 .collect()
960 })?;
961 let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
962 snapshot_accounts_db_fields,
963 account_paths,
964 storage_and_next_append_vec_id,
965 limit_load_slot_count_from_snapshot,
966 verify_index,
967 accounts_db_config,
968 accounts_update_notifier,
969 exit,
970 )?;
971 bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
972
973 let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
974 let runtime_config = Arc::new(runtime_config.clone());
975 let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
976
977 let bank = Bank::new_from_snapshot(
978 bank_rc,
979 genesis_config,
980 runtime_config,
981 bank_fields,
982 leader_for_tests,
983 debug_keys,
984 reconstructed_accounts_db_info.accounts_data_len,
985 epoch_stakes,
986 );
987
988 Ok((
989 bank,
990 ReconstructedBankInfo {
991 calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
992 calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
993 },
994 ))
995}
996
997pub(crate) fn reconstruct_single_storage(
998 slot: &Slot,
999 append_vec_file_info: FileInfo,
1000 id: AccountsFileId,
1001 obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
1002) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
1003 let obsolete_accounts =
1014 if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
1015 if obsolete_id != id {
1016 return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
1017 }
1018
1019 obsolete_accounts
1020 } else {
1021 ObsoleteAccounts::default()
1022 };
1023
1024 let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
1025 Ok(Arc::new(AccountStorageEntry::new_existing(
1026 *slot,
1027 id,
1028 accounts_file,
1029 obsolete_accounts,
1030 )))
1031}
1032
1033pub(crate) fn remap_append_vec_file(
1037 slot: Slot,
1038 old_append_vec_id: SerializedAccountsFileId,
1039 append_vec_file_info: FileInfo,
1040 next_append_vec_id: &AtomicAccountsFileId,
1041 num_collisions: &mut usize,
1042) -> io::Result<(AccountsFileId, FileInfo)> {
1043 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1044 let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
1045
1046 let mut remapped_append_vec_path = append_vec_file_info.path.clone();
1047
1048 let (remapped_append_vec_id, remapped_append_vec_path) = loop {
1054 let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
1055
1056 if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
1058 break (remapped_append_vec_id, remapped_append_vec_path);
1059 }
1060
1061 let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
1062 remapped_append_vec_path = remapped_append_vec_path
1063 .parent()
1064 .unwrap()
1065 .join(remapped_file_name);
1066
1067 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1068 {
1069 let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
1070
1071 match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
1074 Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
1076 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
1079 Err(e) => return Err(e),
1080 }
1081 }
1082
1083 #[cfg(any(
1084 not(target_os = "linux"),
1085 all(target_os = "linux", not(target_env = "gnu"))
1086 ))]
1087 if std::fs::metadata(&remapped_append_vec_path).is_err() {
1088 break (remapped_append_vec_id, remapped_append_vec_path);
1089 }
1090
1091 *num_collisions += 1;
1094 };
1095
1096 #[cfg(any(
1099 not(target_os = "linux"),
1100 all(target_os = "linux", not(target_env = "gnu"))
1101 ))]
1102 if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
1103 std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
1104 }
1105
1106 Ok((
1107 remapped_append_vec_id,
1108 FileInfo {
1109 path: remapped_append_vec_path,
1110 ..append_vec_file_info
1111 },
1112 ))
1113}
1114
1115pub(crate) fn remap_and_reconstruct_single_storage(
1116 slot: Slot,
1117 old_append_vec_id: SerializedAccountsFileId,
1118 append_vec_file_info: FileInfo,
1119 next_append_vec_id: &AtomicAccountsFileId,
1120 num_collisions: &mut usize,
1121) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
1122 let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
1123 slot,
1124 old_append_vec_id,
1125 append_vec_file_info,
1126 next_append_vec_id,
1127 num_collisions,
1128 )?;
1129 let storage = reconstruct_single_storage(
1130 &slot,
1131 remapped_append_vec_file_info,
1132 remapped_append_vec_id,
1133 None,
1134 )?;
1135 Ok(storage)
1136}
1137
1138#[derive(Debug)]
1140pub struct ReconstructedAccountsDbInfo {
1141 pub accounts_data_len: u64,
1142 pub calculated_accounts_lt_hash: AccountsLtHash,
1145 pub calculated_capitalization: u64,
1147 pub bank_hash_stats: BankHashStats,
1148}
1149
1150fn reconstruct_accountsdb_from_fields(
1151 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
1152 account_paths: &[PathBuf],
1153 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
1154 limit_load_slot_count_from_snapshot: Option<usize>,
1155 verify_index: bool,
1156 accounts_db_config: AccountsDbConfig,
1157 accounts_update_notifier: Option<AccountsUpdateNotifier>,
1158 exit: Arc<AtomicBool>,
1159) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), SnapshotError> {
1160 let mut accounts_db = AccountsDb::new_with_config(
1161 account_paths.to_vec(),
1162 accounts_db_config,
1163 accounts_update_notifier,
1164 exit,
1165 );
1166
1167 let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
1168
1169 for path in &accounts_db.paths {
1171 std::fs::create_dir_all(path)
1172 .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
1173 }
1174
1175 let StorageAndNextAccountsFileId {
1176 storage,
1177 next_append_vec_id,
1178 } = storage_and_next_append_vec_id;
1179
1180 assert!(
1181 !storage.is_empty(),
1182 "At least one storage entry must exist from deserializing stream"
1183 );
1184
1185 let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
1186 let max_append_vec_id = next_append_vec_id - 1;
1187 assert!(
1188 max_append_vec_id <= AccountsFileId::MAX / 2,
1189 "Storage id {max_append_vec_id} larger than allowed max"
1190 );
1191
1192 accounts_db.storage.initialize(storage);
1194 accounts_db
1195 .next_id
1196 .store(next_append_vec_id, Ordering::Release);
1197
1198 info!("Building accounts index...");
1199 let start = Instant::now();
1200 let IndexGenerationInfo {
1201 accounts_data_len,
1202 calculated_accounts_lt_hash,
1203 calculated_capitalization,
1204 } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1205 info!("Building accounts index... Done in {:?}", start.elapsed());
1206
1207 Ok((
1208 accounts_db,
1209 ReconstructedAccountsDbInfo {
1210 accounts_data_len,
1211 calculated_accounts_lt_hash,
1212 calculated_capitalization,
1213 bank_hash_stats: snapshot_bank_hash_info.stats,
1214 },
1215 ))
1216}
1217
1218#[cfg(all(target_os = "linux", target_env = "gnu"))]
1220fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1221 let ret = unsafe {
1222 libc::renameat2(
1223 libc::AT_FDCWD,
1224 src.as_ptr() as *const _,
1225 libc::AT_FDCWD,
1226 dest.as_ptr() as *const _,
1227 libc::RENAME_NOREPLACE,
1228 )
1229 };
1230 if ret == -1 {
1231 return Err(io::Error::last_os_error());
1232 }
1233
1234 Ok(())
1235}
1236
1237#[cfg(all(target_os = "linux", target_env = "gnu"))]
1238fn cstring_from_path(path: &Path) -> io::Result<CString> {
1239 CString::new(path.as_os_str().as_encoded_bytes())
1244 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1245}