1#[cfg(feature = "frozen-abi")]
2use solana_frozen_abi::stable_abi;
3#[cfg(all(target_os = "linux", target_env = "gnu"))]
4use std::{
5 ffi::{CStr, CString},
6 path::Path,
7};
8use {
9 crate::{
10 bank::{Bank, BankFieldsToDeserialize, BankFieldsToSerialize, BankHashStats, BankRc},
11 epoch_stakes::{DeserializableVersionedEpochStakes, VersionedEpochStakes},
12 runtime_config::RuntimeConfig,
13 snapshot_utils::StorageAndNextAccountsFileId,
14 stake_account::StakeAccount,
15 stakes::{DeserializableDelegationStakes, Stakes},
16 },
17 agave_fs::FileInfo,
18 agave_snapshots::error::SnapshotError,
19 log::*,
20 smallvec::SmallVec,
21 solana_accounts_db::{
22 ObsoleteAccounts,
23 account_storage_entry::AccountStorageEntry,
24 accounts::Accounts,
25 accounts_db::{
26 AccountsDb, AccountsDbConfig, AccountsFileId, AtomicAccountsFileId, IndexGenerationInfo,
27 },
28 accounts_file::AccountsFile,
29 accounts_hash::AccountsLtHash,
30 accounts_update_notifier_interface::AccountsUpdateNotifier,
31 blockhash_queue::BlockhashQueue,
32 },
33 solana_clock::{Epoch, Slot, UnixTimestamp},
34 solana_epoch_schedule::EpochSchedule,
35 solana_fee_calculator::FeeRateGovernor,
36 solana_genesis_config::GenesisConfig,
37 solana_hard_forks::HardForks,
38 solana_hash::Hash,
39 solana_inflation::Inflation,
40 solana_lattice_hash::lt_hash::LtHash,
41 solana_leader_schedule::SlotLeader,
42 solana_pubkey::Pubkey,
43 solana_stake_interface::state::Delegation,
44 std::{
45 collections::{HashMap, HashSet},
46 io::{self, BufReader, Read, Write},
47 path::PathBuf,
48 result::Result,
49 sync::{
50 Arc,
51 atomic::{AtomicBool, Ordering},
52 },
53 thread,
54 time::Instant,
55 },
56 types::{SerdeAccountsLtHash, UnusedRentCollector},
57 wincode::{
58 ReadResult, SchemaRead, SchemaReadOwned, SchemaWrite, WriteResult,
59 adapter::{DefaultOnEmptyRead, DiscardSeq},
60 io::{Reader, std_write::WriteAdapter},
61 len::BincodeLen,
62 },
63};
64
65mod obsolete_accounts;
66mod startup_hints;
67mod status_cache;
68mod storage;
69mod storages_list;
70mod tests;
71mod types;
72
73pub use startup_hints::StartupHints;
74#[cfg(feature = "dev-context-only-utils")]
75pub use status_cache::serialize_status_cache_into;
76pub(crate) use {
77 obsolete_accounts::{SerdeObsoleteAccounts, SerdeObsoleteAccountsMap},
78 status_cache::{deserialize_status_cache, serialize_status_cache},
79 storage::{SerializableAccountStorageEntry, SerializedAccountsFileId},
80 storages_list::{StorageListItem, StoragesList},
81};
82
83const MAX_STREAM_SIZE: usize = 32 * 1024 * 1024 * 1024;
84type MaxStreamSizeConfig = wincode::config::Configuration<true, MAX_STREAM_SIZE>;
85
86#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
89#[derive(Debug, SchemaRead, SchemaWrite)]
90pub(crate) struct SlotAccountStorageEntries {
91 slot: Slot,
92 #[cfg_attr(
96 feature = "frozen-abi",
97 stable_abi_sample(with = "solana_frozen_abi::stable_abi::sample_collection_sized(rng, \
98 solana_frozen_abi::stable_abi::context::SequenceLenRange::new(0.\
99 .=5))")
100 )]
101 entries: SmallVec<[SerializableAccountStorageEntry; 1]>,
102}
103
104#[cfg_attr(
105 feature = "frozen-abi",
106 derive(SchemaWrite, StableAbi, StableAbiSample)
107)]
108#[derive(Debug, SchemaRead)]
109pub(crate) struct AccountsDbFields(
110 #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
113 #[wincode(with = "DiscardSeq<SlotAccountStorageEntries, BincodeLen>")]
114 Vec<SlotAccountStorageEntries>,
115 u64, Slot,
117 BankHashInfo,
118 #[wincode(with = "DefaultOnEmptyRead<Vec<Slot>>")]
120 Vec<Slot>,
121 #[wincode(with = "DefaultOnEmptyRead<Vec<(Slot, Hash)>>")]
123 Vec<(Slot, Hash)>,
124);
125
126#[repr(C)]
127#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
128#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
129#[derive(Clone, Debug, SchemaRead, SchemaWrite)]
130pub struct UnusedIncrementalSnapshotPersistence {
131 pub full_slot: u64,
132 pub full_hash: [u8; 32],
133 pub full_capitalization: u64,
134 pub incremental_hash: [u8; 32],
135 pub incremental_capitalization: u64,
136}
137
138#[repr(C)]
139#[cfg_attr(
140 feature = "frozen-abi",
141 derive(StableAbi, StableAbiSample),
142 frozen_abi(
143 abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
144 abi_serializer = "wincode",
145 test_roundtrip = "eq_and_wire"
146 )
147)]
148#[derive(Clone, Default, Debug, PartialEq, Eq, SchemaRead, SchemaWrite)]
149struct BankHashInfo {
150 unused_accounts_delta_hash: [u8; 32],
151 unused_accounts_hash: [u8; 32],
152 stats: BankHashStats,
153}
154
155#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
156#[derive(Default, Clone, PartialEq, Eq, Debug, SchemaRead, SchemaWrite)]
157struct UnusedAccounts {
158 unused1: HashSet<Pubkey>,
159 unused2: HashSet<Pubkey>,
160 unused3: HashMap<Pubkey, u64>,
161}
162
163#[cfg_attr(
167 feature = "frozen-abi",
168 derive(SchemaWrite, StableAbi, StableAbiSample)
169)]
170#[derive(Clone, SchemaRead)]
171struct DeserializableVersionedBank {
172 blockhash_queue: BlockhashQueue,
173 _unused_ancestors: HashMap<Slot, usize>,
174 hash: Hash,
175 parent_hash: Hash,
176 parent_slot: Slot,
177 hard_forks: HardForks,
178 transaction_count: u64,
179 tick_height: u64,
180 signature_count: u64,
181 capitalization: u64,
182 max_tick_height: u64,
183 hashes_per_tick: Option<u64>,
184 ticks_per_slot: u64,
185 ns_per_slot: u128,
186 genesis_creation_time: UnixTimestamp,
187 slots_per_year: f64,
188 accounts_data_len: u64,
189 slot: Slot,
190 _unused_epoch: Epoch,
191 block_height: u64,
192 leader_id: Pubkey,
193 _unused_collector_fees: u64,
194 _unused_fee_calculator: u64,
195 fee_rate_governor: FeeRateGovernor,
196 _unused_collected_rent: u64,
197 _unused_rent_collector: UnusedRentCollector,
198 epoch_schedule: EpochSchedule,
199 inflation: Inflation,
200 stakes: DeserializableDelegationStakes,
201 _unused_accounts: UnusedAccounts,
202 unused_epoch_stakes: HashMap<Epoch, ()>,
203 is_delta: bool,
204}
205
206impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
207 fn from(dvb: DeserializableVersionedBank) -> Self {
208 const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
211 let mut blockhash_queue = dvb.blockhash_queue;
213 blockhash_queue.refresh_durable_nonce();
214 BankFieldsToDeserialize {
215 blockhash_queue,
216 hash: dvb.hash,
217 parent_hash: dvb.parent_hash,
218 parent_slot: dvb.parent_slot,
219 hard_forks: dvb.hard_forks,
220 transaction_count: dvb.transaction_count,
221 tick_height: dvb.tick_height,
222 signature_count: dvb.signature_count,
223 capitalization: dvb.capitalization,
224 max_tick_height: dvb.max_tick_height,
225 hashes_per_tick: dvb.hashes_per_tick,
226 ticks_per_slot: dvb.ticks_per_slot,
227 ns_per_slot: dvb.ns_per_slot,
228 genesis_creation_time: dvb.genesis_creation_time,
229 slots_per_year: dvb.slots_per_year,
230 accounts_data_len: dvb.accounts_data_len,
231 slot: dvb.slot,
232 block_height: dvb.block_height,
233 leader_id: dvb.leader_id,
234 fee_rate_governor: dvb.fee_rate_governor,
235 epoch_schedule: dvb.epoch_schedule,
236 inflation: dvb.inflation,
237 stakes: dvb.stakes,
238 is_delta: dvb.is_delta,
239 versioned_epoch_stakes: vec![], accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), bank_hash_stats: BankHashStats::default(), block_id: None, }
244 }
245}
246
247#[cfg_attr(
250 feature = "frozen-abi",
251 derive(StableAbi, StableAbiSample),
252 frozen_abi(
255 abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
256 abi_serializer = "wincode",
257 test_roundtrip = "no"
258 )
259)]
260#[derive(SchemaWrite)]
261struct SerializableVersionedBank {
262 blockhash_queue: BlockhashQueue,
263 unused_ancestors: HashMap<Slot, usize>,
264 hash: Hash,
265 parent_hash: Hash,
266 parent_slot: Slot,
267 hard_forks: HardForks,
268 transaction_count: u64,
269 tick_height: u64,
270 signature_count: u64,
271 capitalization: u64,
272 max_tick_height: u64,
273 hashes_per_tick: Option<u64>,
274 ticks_per_slot: u64,
275 ns_per_slot: u128,
276 genesis_creation_time: UnixTimestamp,
277 slots_per_year: f64,
278 accounts_data_len: u64,
279 slot: Slot,
280 unused_epoch: Epoch,
281 block_height: u64,
282 leader_id: Pubkey,
283 unused_collector_fees: u64,
284 unused_fee_calculator: u64,
285 fee_rate_governor: FeeRateGovernor,
286 unused_collected_rent: u64,
287 unused_rent_collector: UnusedRentCollector,
288 epoch_schedule: EpochSchedule,
289 inflation: Inflation,
290 stakes: Stakes<StakeAccount<Delegation>>,
291 unused_accounts: UnusedAccounts,
292 unused_epoch_stakes: HashMap<Epoch, ()>,
293 is_delta: bool,
294}
295
296impl From<BankFieldsToSerialize> for SerializableVersionedBank {
297 fn from(rhs: BankFieldsToSerialize) -> Self {
298 Self {
299 blockhash_queue: rhs.blockhash_queue,
300 unused_ancestors: HashMap::default(),
301 hash: rhs.hash,
302 parent_hash: rhs.parent_hash,
303 parent_slot: rhs.parent_slot,
304 hard_forks: rhs.hard_forks,
305 transaction_count: rhs.transaction_count,
306 tick_height: rhs.tick_height,
307 signature_count: rhs.signature_count,
308 capitalization: rhs.capitalization,
309 max_tick_height: rhs.max_tick_height,
310 hashes_per_tick: rhs.hashes_per_tick,
311 ticks_per_slot: rhs.ticks_per_slot,
312 ns_per_slot: rhs.ns_per_slot,
313 genesis_creation_time: rhs.genesis_creation_time,
314 slots_per_year: rhs.slots_per_year,
315 accounts_data_len: rhs.accounts_data_len,
316 slot: rhs.slot,
317 unused_epoch: 0,
318 block_height: rhs.block_height,
319 leader_id: rhs.leader_id,
320 unused_collector_fees: 0,
321 unused_fee_calculator: 0,
322 fee_rate_governor: rhs.fee_rate_governor,
323 unused_collected_rent: u64::default(),
324 unused_rent_collector: UnusedRentCollector::zeroed(),
325 epoch_schedule: rhs.epoch_schedule,
326 inflation: rhs.inflation,
327 stakes: rhs.stakes,
328 unused_accounts: UnusedAccounts::default(),
329 unused_epoch_stakes: HashMap::default(),
330 is_delta: rhs.is_delta,
331 }
332 }
333}
334
335pub struct SnapshotStreams<'a, R> {
338 pub full_snapshot_stream: &'a mut BufReader<R>,
339 pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
340}
341
342#[derive(Debug)]
345pub struct SnapshotBankFields {
346 full: BankFieldsToDeserialize,
347 incremental: Option<BankFieldsToDeserialize>,
348}
349
350impl SnapshotBankFields {
351 pub fn new(
352 full: BankFieldsToDeserialize,
353 incremental: Option<BankFieldsToDeserialize>,
354 ) -> Self {
355 Self { full, incremental }
356 }
357
358 pub fn collapse_into(self) -> BankFieldsToDeserialize {
360 self.incremental.unwrap_or(self.full)
361 }
362}
363
364#[derive(Debug)]
367pub struct SnapshotAccountsDbFields {
368 full_snapshot_accounts_db_fields: AccountsDbFields,
369 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
370}
371
372impl SnapshotAccountsDbFields {
373 pub(crate) fn new(
374 full_snapshot_accounts_db_fields: AccountsDbFields,
375 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
376 ) -> Self {
377 Self {
378 full_snapshot_accounts_db_fields,
379 incremental_snapshot_accounts_db_fields,
380 }
381 }
382
383 fn into_bank_hash_info(self) -> BankHashInfo {
388 let AccountsDbFields(
389 _snapshot_storages,
390 _snapshot_write_version,
391 _snapshot_slot,
392 snapshot_bank_hash_info,
393 _snapshot_historical_roots,
394 _snapshot_historical_roots_with_hash,
395 ) = self
396 .incremental_snapshot_accounts_db_fields
397 .unwrap_or(self.full_snapshot_accounts_db_fields);
398 snapshot_bank_hash_info
399 }
400}
401
402pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
403where
404 W: Write,
405 T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
406{
407 wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
408}
409
410pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
411where
412 R: Reader<'a>,
413 T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
414{
415 wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
416}
417
418#[cfg_attr(
425 feature = "frozen-abi",
426 derive(SchemaWrite, StableAbi, StableAbiSample)
427)]
428#[derive(Clone, Debug, SchemaRead)]
429struct ExtraFieldsToDeserialize {
430 #[wincode(with = "DefaultOnEmptyRead<u64>")]
431 lamports_per_signature: u64,
432 #[wincode(with = "DefaultOnEmptyRead<Option<UnusedIncrementalSnapshotPersistence>>")]
433 _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
434 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
435 _unused_epoch_accounts_hash: Option<Hash>,
436 #[wincode(with = "DefaultOnEmptyRead<Vec<(u64, DeserializableVersionedEpochStakes)>>")]
437 #[cfg_attr(
439 feature = "frozen-abi",
440 stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
441 stable_abi::context::SequenceLenMax(1))")
442 )]
443 versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
444 #[wincode(with = "DefaultOnEmptyRead<Option<SerdeAccountsLtHash>>")]
445 accounts_lt_hash: Option<SerdeAccountsLtHash>,
446 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
447 block_id: Option<Hash>,
448}
449
450#[cfg_attr(
457 feature = "frozen-abi",
458 derive(StableAbi, StableAbiSample),
459 frozen_abi(
462 abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
463 abi_serializer = "wincode",
464 test_roundtrip = "no"
465 )
466)]
467#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
468#[derive(Debug, SchemaWrite)]
469pub struct ExtraFieldsToSerialize {
470 pub lamports_per_signature: u64,
471 pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
472 pub unused_epoch_accounts_hash: Option<Hash>,
473 pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
474 pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
475 pub block_id: Option<Hash>,
476}
477
478#[cfg_attr(
484 feature = "frozen-abi",
485 derive(SchemaWrite, StableAbi, StableAbiSample),
486 frozen_abi(
487 abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
488 abi_serializer = "wincode",
489 test_roundtrip = "wire_only"
490 )
491)]
492#[derive(SchemaRead)]
493struct DeserializableBankSnapshot {
494 bank: DeserializableVersionedBank,
495 accounts_db: AccountsDbFields,
496 extra_fields: ExtraFieldsToDeserialize,
497}
498
499impl DeserializableBankSnapshot {
500 fn into_fields(self) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
502 let Self {
503 bank,
504 accounts_db,
505 extra_fields,
506 } = self;
507 if !bank.unused_epoch_stakes.is_empty() {
508 return Err(wincode::ReadError::InvalidValue(
509 "Expected deserialized bank's unused_epoch_stakes field to be empty",
510 ));
511 }
512 let mut bank_fields = BankFieldsToDeserialize::from(bank);
513 let ExtraFieldsToDeserialize {
514 lamports_per_signature,
515 _unused_incremental_snapshot_persistence,
516 _unused_epoch_accounts_hash,
517 versioned_epoch_stakes,
518 accounts_lt_hash,
519 block_id,
520 } = extra_fields;
521
522 bank_fields.fee_rate_governor = bank_fields
523 .fee_rate_governor
524 .clone_with_lamports_per_signature(lamports_per_signature);
525 bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
526 bank_fields.accounts_lt_hash = accounts_lt_hash
527 .expect("snapshot must have accounts_lt_hash")
528 .into();
529 bank_fields.block_id = block_id;
530
531 Ok((bank_fields, accounts_db))
532 }
533}
534
535pub(crate) fn fields_from_stream<R: Read>(
536 snapshot_stream: &mut BufReader<R>,
537) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
538 deserialize_wincode_from::<_, DeserializableBankSnapshot>(snapshot_stream)?.into_fields()
539}
540
541#[cfg(feature = "dev-context-only-utils")]
542pub(crate) fn fields_from_streams(
543 snapshot_streams: &mut SnapshotStreams<impl Read>,
544) -> wincode::ReadResult<(SnapshotBankFields, SnapshotAccountsDbFields)> {
545 let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
546 fields_from_stream(snapshot_streams.full_snapshot_stream)?;
547 let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
548 snapshot_streams
549 .incremental_snapshot_stream
550 .as_mut()
551 .map(|stream| fields_from_stream(stream))
552 .transpose()?
553 .unzip();
554
555 let snapshot_bank_fields = SnapshotBankFields {
556 full: full_snapshot_bank_fields,
557 incremental: incremental_snapshot_bank_fields,
558 };
559 let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
560 full_snapshot_accounts_db_fields,
561 incremental_snapshot_accounts_db_fields,
562 };
563 Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
564}
565
566#[derive(Debug)]
568pub struct BankFromStreamsInfo {
569 pub calculated_accounts_lt_hash: AccountsLtHash,
572}
573
574#[allow(clippy::too_many_arguments)]
575#[cfg(test)]
576pub(crate) fn bank_from_streams<R>(
577 snapshot_streams: &mut SnapshotStreams<R>,
578 account_paths: &[PathBuf],
579 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
580 genesis_config: &GenesisConfig,
581 runtime_config: &RuntimeConfig,
582 debug_keys: Option<Arc<HashSet<Pubkey>>>,
583 limit_load_slot_count_from_snapshot: Option<usize>,
584 verify_index: bool,
585 accounts_db_config: AccountsDbConfig,
586 accounts_update_notifier: Option<AccountsUpdateNotifier>,
587 exit: Arc<AtomicBool>,
588) -> std::result::Result<(Bank, BankFromStreamsInfo), SnapshotError>
589where
590 R: Read,
591{
592 let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
593 let (bank, info) = reconstruct_bank_from_fields(
594 bank_fields,
595 accounts_db_fields,
596 genesis_config,
597 runtime_config,
598 account_paths,
599 storage_and_next_append_vec_id,
600 debug_keys,
601 None, limit_load_slot_count_from_snapshot,
603 verify_index,
604 accounts_db_config,
605 accounts_update_notifier,
606 exit,
607 )?;
608 Ok((
609 bank,
610 BankFromStreamsInfo {
611 calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
612 },
613 ))
614}
615
616#[cfg(test)]
617pub(crate) fn bank_to_stream<W>(
618 stream: &mut io::BufWriter<W>,
619 bank: &Bank,
620) -> wincode::WriteResult<()>
621where
622 W: Write,
623{
624 let mut bank_fields = bank.get_fields_to_serialize();
625 let bank_hash_stats = bank.get_bank_hash_stats();
626 let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
627 let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
628 let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
629 let block_id = Some(bank_fields.block_id);
630 serialize_bank_snapshot_into(
631 stream,
632 bank_fields,
633 bank_hash_stats,
634 ExtraFieldsToSerialize {
635 lamports_per_signature,
636 unused_incremental_snapshot_persistence: None,
637 unused_epoch_accounts_hash: None,
638 versioned_epoch_stakes,
639 accounts_lt_hash,
640 block_id,
641 },
642 )
643}
644
645#[cfg_attr(
648 feature = "frozen-abi",
649 derive(StableAbi, StableAbiSample),
650 frozen_abi(
653 abi_digest = "EULkWXkHiQJQazbeCQSP6L7ZMDBXZpBg1JntdHZktrEh",
654 abi_serializer = "wincode",
655 test_roundtrip = "no"
656 )
657)]
658#[derive(SchemaWrite)]
659struct SerializableBankSnapshot {
660 bank: SerializableVersionedBank,
661 accounts_db: SerializableAccountsDb,
662 extra_fields: ExtraFieldsToSerialize,
663}
664
665pub fn serialize_bank_snapshot_into(
667 stream: &mut dyn Write,
668 bank_fields: BankFieldsToSerialize,
669 bank_hash_stats: BankHashStats,
670 extra_fields: ExtraFieldsToSerialize,
671) -> wincode::WriteResult<()> {
672 let slot = bank_fields.slot;
673 let snapshot = SerializableBankSnapshot {
674 bank: SerializableVersionedBank::from(bank_fields),
675 accounts_db: SerializableAccountsDb::new(slot, bank_hash_stats),
676 extra_fields,
677 };
678 serialize_into(stream, &snapshot)
681}
682
683#[cfg_attr(
685 feature = "frozen-abi",
686 derive(StableAbi, StableAbiSample),
687 frozen_abi(
690 abi_digest = "6d9LgxwkMTVHRKGtF8QSFFn3rYG8MSmyT9wPrL1HESu1",
691 abi_serializer = "wincode",
692 test_roundtrip = "no"
693 )
694)]
695#[derive(SchemaWrite)]
696struct SerializableAccountsDb {
697 #[cfg_attr(feature = "frozen-abi", stable_abi_sample(with = "Vec::new()"))]
700 unused_accounts_storage_entries: Vec<SlotAccountStorageEntries>,
701 unused_write_version: u64, slot: Slot,
703 bank_hash_info: BankHashInfo,
704 historical_roots: Vec<Slot>,
706 historical_roots_with_hash: Vec<(Slot, Hash)>,
708}
709
710impl SerializableAccountsDb {
711 fn new(slot: Slot, bank_hash_stats: BankHashStats) -> Self {
712 let bank_hash_info = BankHashInfo {
713 unused_accounts_delta_hash: [0; 32],
714 unused_accounts_hash: [0; 32],
715 stats: bank_hash_stats,
716 };
717 SerializableAccountsDb {
718 unused_accounts_storage_entries: Vec::default(),
719 unused_write_version: 0,
720 slot,
721 bank_hash_info,
722 historical_roots: Vec::default(),
723 historical_roots_with_hash: Vec::default(),
724 }
725 }
726}
727
728#[derive(Debug)]
730pub(crate) struct ReconstructedBankInfo {
731 pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
734 pub(crate) calculated_capitalization: u64,
736}
737
738#[expect(clippy::too_many_arguments)]
739pub(crate) fn reconstruct_bank_from_fields(
740 bank_fields: SnapshotBankFields,
741 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
742 genesis_config: &GenesisConfig,
743 runtime_config: &RuntimeConfig,
744 account_paths: &[PathBuf],
745 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
746 debug_keys: Option<Arc<HashSet<Pubkey>>>,
747 leader_for_tests: Option<SlotLeader>,
748 limit_load_slot_count_from_snapshot: Option<usize>,
749 verify_index: bool,
750 accounts_db_config: AccountsDbConfig,
751 accounts_update_notifier: Option<AccountsUpdateNotifier>,
752 exit: Arc<AtomicBool>,
753) -> Result<(Bank, ReconstructedBankInfo), SnapshotError> {
754 let mut bank_fields = bank_fields.collapse_into();
755 let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
757 let epoch_stakes_handle = thread::Builder::new()
758 .name("solRctEpochStk".into())
759 .spawn(|| {
760 deserializable_epoch_stakes
761 .into_iter()
762 .map(|(epoch, stakes)| (epoch, stakes.into()))
763 .collect()
764 })?;
765 let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
766 snapshot_accounts_db_fields,
767 account_paths,
768 storage_and_next_append_vec_id,
769 limit_load_slot_count_from_snapshot,
770 verify_index,
771 accounts_db_config,
772 accounts_update_notifier,
773 exit,
774 )?;
775 bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
776
777 let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
778 let runtime_config = Arc::new(runtime_config.clone());
779 let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
780
781 let bank = Bank::new_from_snapshot(
782 bank_rc,
783 genesis_config,
784 runtime_config,
785 bank_fields,
786 leader_for_tests,
787 debug_keys,
788 reconstructed_accounts_db_info.accounts_data_len,
789 epoch_stakes,
790 );
791
792 Ok((
793 bank,
794 ReconstructedBankInfo {
795 calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
796 calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
797 },
798 ))
799}
800
801pub(crate) fn reconstruct_single_storage(
802 slot: &Slot,
803 append_vec_file_info: FileInfo,
804 id: AccountsFileId,
805 obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
806) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
807 let obsolete_accounts =
818 if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
819 if obsolete_id != id {
820 return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
821 }
822
823 obsolete_accounts
824 } else {
825 ObsoleteAccounts::default()
826 };
827
828 let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
829 Ok(Arc::new(AccountStorageEntry::new_existing(
830 *slot,
831 id,
832 accounts_file,
833 obsolete_accounts,
834 )))
835}
836
837pub(crate) fn remap_append_vec_file(
841 slot: Slot,
842 old_append_vec_id: SerializedAccountsFileId,
843 append_vec_file_info: FileInfo,
844 next_append_vec_id: &AtomicAccountsFileId,
845 num_collisions: &mut usize,
846) -> io::Result<(AccountsFileId, FileInfo)> {
847 #[cfg(all(target_os = "linux", target_env = "gnu"))]
848 let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
849
850 let mut remapped_append_vec_path = append_vec_file_info.path.clone();
851
852 let (remapped_append_vec_id, remapped_append_vec_path) = loop {
858 let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
859
860 if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
862 break (remapped_append_vec_id, remapped_append_vec_path);
863 }
864
865 let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
866 remapped_append_vec_path = remapped_append_vec_path
867 .parent()
868 .unwrap()
869 .join(remapped_file_name);
870
871 #[cfg(all(target_os = "linux", target_env = "gnu"))]
872 {
873 let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
874
875 match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
878 Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
880 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
883 Err(e) => return Err(e),
884 }
885 }
886
887 #[cfg(any(
888 not(target_os = "linux"),
889 all(target_os = "linux", not(target_env = "gnu"))
890 ))]
891 if std::fs::metadata(&remapped_append_vec_path).is_err() {
892 break (remapped_append_vec_id, remapped_append_vec_path);
893 }
894
895 *num_collisions += 1;
898 };
899
900 #[cfg(any(
903 not(target_os = "linux"),
904 all(target_os = "linux", not(target_env = "gnu"))
905 ))]
906 if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
907 std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
908 }
909
910 Ok((
911 remapped_append_vec_id,
912 FileInfo {
913 path: remapped_append_vec_path,
914 ..append_vec_file_info
915 },
916 ))
917}
918
919pub(crate) fn remap_and_reconstruct_single_storage(
920 slot: Slot,
921 old_append_vec_id: SerializedAccountsFileId,
922 append_vec_file_info: FileInfo,
923 next_append_vec_id: &AtomicAccountsFileId,
924 num_collisions: &mut usize,
925) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
926 let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
927 slot,
928 old_append_vec_id,
929 append_vec_file_info,
930 next_append_vec_id,
931 num_collisions,
932 )?;
933 let storage = reconstruct_single_storage(
934 &slot,
935 remapped_append_vec_file_info,
936 remapped_append_vec_id,
937 None,
938 )?;
939 Ok(storage)
940}
941
942#[derive(Debug)]
944pub struct ReconstructedAccountsDbInfo {
945 pub accounts_data_len: u64,
946 pub calculated_accounts_lt_hash: AccountsLtHash,
949 pub calculated_capitalization: u64,
951 pub bank_hash_stats: BankHashStats,
952}
953
954fn reconstruct_accountsdb_from_fields(
955 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
956 account_paths: &[PathBuf],
957 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
958 limit_load_slot_count_from_snapshot: Option<usize>,
959 verify_index: bool,
960 accounts_db_config: AccountsDbConfig,
961 accounts_update_notifier: Option<AccountsUpdateNotifier>,
962 exit: Arc<AtomicBool>,
963) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), SnapshotError> {
964 let mut accounts_db = AccountsDb::new_with_config(
965 account_paths.to_vec(),
966 accounts_db_config,
967 accounts_update_notifier,
968 exit,
969 );
970
971 let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
972
973 for path in &accounts_db.paths {
975 std::fs::create_dir_all(path)
976 .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
977 }
978
979 let StorageAndNextAccountsFileId {
980 storage,
981 next_append_vec_id,
982 } = storage_and_next_append_vec_id;
983
984 assert!(
985 !storage.is_empty(),
986 "At least one storage entry must exist from deserializing stream"
987 );
988
989 let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
990 let max_append_vec_id = next_append_vec_id - 1;
991 assert!(
992 max_append_vec_id <= AccountsFileId::MAX / 2,
993 "Storage id {max_append_vec_id} larger than allowed max"
994 );
995
996 accounts_db.storage.initialize(storage);
998 accounts_db
999 .next_id
1000 .store(next_append_vec_id, Ordering::Release);
1001
1002 info!("Building accounts index...");
1003 let start = Instant::now();
1004 let IndexGenerationInfo {
1005 accounts_data_len,
1006 calculated_accounts_lt_hash,
1007 calculated_capitalization,
1008 } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1009 info!("Building accounts index... Done in {:?}", start.elapsed());
1010
1011 Ok((
1012 accounts_db,
1013 ReconstructedAccountsDbInfo {
1014 accounts_data_len,
1015 calculated_accounts_lt_hash,
1016 calculated_capitalization,
1017 bank_hash_stats: snapshot_bank_hash_info.stats,
1018 },
1019 ))
1020}
1021
1022#[cfg(all(target_os = "linux", target_env = "gnu"))]
1024fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1025 let ret = unsafe {
1026 libc::renameat2(
1027 libc::AT_FDCWD,
1028 src.as_ptr() as *const _,
1029 libc::AT_FDCWD,
1030 dest.as_ptr() as *const _,
1031 libc::RENAME_NOREPLACE,
1032 )
1033 };
1034 if ret == -1 {
1035 return Err(io::Error::last_os_error());
1036 }
1037
1038 Ok(())
1039}
1040
1041#[cfg(all(target_os = "linux", target_env = "gnu"))]
1042fn cstring_from_path(path: &Path) -> io::Result<CString> {
1043 CString::new(path.as_os_str().as_encoded_bytes())
1048 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1049}