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, de::DeserializeOwned},
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
88#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
91#[derive(Debug, Serialize, Deserialize, SchemaWrite)]
92pub(crate) struct SlotAccountStorageEntries {
93 slot: Slot,
94 #[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, StableAbi, StableAbiSample)
108)]
109#[derive(Debug, Deserialize)]
110pub(crate) struct AccountsDbFields(
111 Vec<SlotAccountStorageEntries>,
112 u64, Slot,
114 BankHashInfo,
115 #[serde(deserialize_with = "default_on_eof")]
117 Vec<Slot>,
118 #[serde(deserialize_with = "default_on_eof")]
120 Vec<(Slot, Hash)>,
121);
122
123#[repr(C)]
124#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
125#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
126#[derive(Serialize, Deserialize, Clone, Debug, SchemaRead, SchemaWrite)]
127pub struct UnusedIncrementalSnapshotPersistence {
128 pub full_slot: u64,
129 pub full_hash: [u8; 32],
130 pub full_capitalization: u64,
131 pub incremental_hash: [u8; 32],
132 pub incremental_capitalization: u64,
133}
134
135#[repr(C)]
136#[cfg_attr(
137 feature = "frozen-abi",
138 derive(AbiExample, StableAbi, StableAbiSample),
139 frozen_abi(
140 abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
141 abi_serializer = ["bincode", "wincode"],
142 test_roundtrip = "eq_and_wire"
143 )
144)]
145#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, SchemaRead, SchemaWrite)]
146struct BankHashInfo {
147 unused_accounts_delta_hash: [u8; 32],
148 unused_accounts_hash: [u8; 32],
149 stats: BankHashStats,
150}
151
152#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
153#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, SchemaRead, SchemaWrite)]
154struct UnusedAccounts {
155 unused1: HashSet<Pubkey>,
156 unused2: HashSet<Pubkey>,
157 unused3: HashMap<Pubkey, u64>,
158}
159
160#[cfg_attr(feature = "frozen-abi", derive(Serialize, StableAbi, StableAbiSample))]
163#[derive(Clone, Deserialize)]
164struct DeserializableVersionedBank {
165 blockhash_queue: BlockhashQueue,
166 _unused_ancestors: HashMap<Slot, usize>,
167 hash: Hash,
168 parent_hash: Hash,
169 parent_slot: Slot,
170 hard_forks: HardForks,
171 transaction_count: u64,
172 tick_height: u64,
173 signature_count: u64,
174 capitalization: u64,
175 max_tick_height: u64,
176 hashes_per_tick: Option<u64>,
177 ticks_per_slot: u64,
178 ns_per_slot: u128,
179 genesis_creation_time: UnixTimestamp,
180 slots_per_year: f64,
181 accounts_data_len: u64,
182 slot: Slot,
183 _unused_epoch: Epoch,
184 block_height: u64,
185 leader_id: Pubkey,
186 _unused_collector_fees: u64,
187 _unused_fee_calculator: u64,
188 fee_rate_governor: FeeRateGovernor,
189 _unused_collected_rent: u64,
190 _unused_rent_collector: UnusedRentCollector,
191 epoch_schedule: EpochSchedule,
192 inflation: Inflation,
193 stakes: DeserializableDelegationStakes,
194 _unused_accounts: UnusedAccounts,
195 unused_epoch_stakes: HashMap<Epoch, ()>,
196 is_delta: bool,
197}
198
199impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
200 fn from(dvb: DeserializableVersionedBank) -> Self {
201 const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
204 BankFieldsToDeserialize {
205 blockhash_queue: dvb.blockhash_queue,
206 hash: dvb.hash,
207 parent_hash: dvb.parent_hash,
208 parent_slot: dvb.parent_slot,
209 hard_forks: dvb.hard_forks,
210 transaction_count: dvb.transaction_count,
211 tick_height: dvb.tick_height,
212 signature_count: dvb.signature_count,
213 capitalization: dvb.capitalization,
214 max_tick_height: dvb.max_tick_height,
215 hashes_per_tick: dvb.hashes_per_tick,
216 ticks_per_slot: dvb.ticks_per_slot,
217 ns_per_slot: dvb.ns_per_slot,
218 genesis_creation_time: dvb.genesis_creation_time,
219 slots_per_year: dvb.slots_per_year,
220 accounts_data_len: dvb.accounts_data_len,
221 slot: dvb.slot,
222 block_height: dvb.block_height,
223 leader_id: dvb.leader_id,
224 fee_rate_governor: dvb.fee_rate_governor,
225 epoch_schedule: dvb.epoch_schedule,
226 inflation: dvb.inflation,
227 stakes: dvb.stakes,
228 is_delta: dvb.is_delta,
229 versioned_epoch_stakes: vec![], accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), bank_hash_stats: BankHashStats::default(), block_id: None, }
234 }
235}
236
237#[cfg_attr(
240 feature = "frozen-abi",
241 derive(StableAbi, StableAbiSample),
242 frozen_abi(
245 abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
246 abi_serializer = ["bincode", "wincode"],
247 test_roundtrip = "no"
248 )
249)]
250#[derive(Serialize, SchemaWrite)]
251struct SerializableVersionedBank {
252 blockhash_queue: BlockhashQueue,
253 unused_ancestors: HashMap<Slot, usize>,
254 hash: Hash,
255 parent_hash: Hash,
256 parent_slot: Slot,
257 hard_forks: HardForks,
258 transaction_count: u64,
259 tick_height: u64,
260 signature_count: u64,
261 capitalization: u64,
262 max_tick_height: u64,
263 hashes_per_tick: Option<u64>,
264 ticks_per_slot: u64,
265 ns_per_slot: u128,
266 genesis_creation_time: UnixTimestamp,
267 slots_per_year: f64,
268 accounts_data_len: u64,
269 slot: Slot,
270 unused_epoch: Epoch,
271 block_height: u64,
272 leader_id: Pubkey,
273 unused_collector_fees: u64,
274 unused_fee_calculator: u64,
275 fee_rate_governor: FeeRateGovernor,
276 unused_collected_rent: u64,
277 unused_rent_collector: UnusedRentCollector,
278 epoch_schedule: EpochSchedule,
279 inflation: Inflation,
280 #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
281 stakes: Stakes<StakeAccount<Delegation>>,
282 unused_accounts: UnusedAccounts,
283 unused_epoch_stakes: HashMap<Epoch, ()>,
284 is_delta: bool,
285}
286
287impl From<BankFieldsToSerialize> for SerializableVersionedBank {
288 fn from(rhs: BankFieldsToSerialize) -> Self {
289 Self {
290 blockhash_queue: rhs.blockhash_queue,
291 unused_ancestors: HashMap::default(),
292 hash: rhs.hash,
293 parent_hash: rhs.parent_hash,
294 parent_slot: rhs.parent_slot,
295 hard_forks: rhs.hard_forks,
296 transaction_count: rhs.transaction_count,
297 tick_height: rhs.tick_height,
298 signature_count: rhs.signature_count,
299 capitalization: rhs.capitalization,
300 max_tick_height: rhs.max_tick_height,
301 hashes_per_tick: rhs.hashes_per_tick,
302 ticks_per_slot: rhs.ticks_per_slot,
303 ns_per_slot: rhs.ns_per_slot,
304 genesis_creation_time: rhs.genesis_creation_time,
305 slots_per_year: rhs.slots_per_year,
306 accounts_data_len: rhs.accounts_data_len,
307 slot: rhs.slot,
308 unused_epoch: 0,
309 block_height: rhs.block_height,
310 leader_id: rhs.leader_id,
311 unused_collector_fees: 0,
312 unused_fee_calculator: 0,
313 fee_rate_governor: rhs.fee_rate_governor,
314 unused_collected_rent: u64::default(),
315 unused_rent_collector: UnusedRentCollector::zeroed(),
316 epoch_schedule: rhs.epoch_schedule,
317 inflation: rhs.inflation,
318 stakes: rhs.stakes,
319 unused_accounts: UnusedAccounts::default(),
320 unused_epoch_stakes: HashMap::default(),
321 is_delta: rhs.is_delta,
322 }
323 }
324}
325
326pub struct SnapshotStreams<'a, R> {
329 pub full_snapshot_stream: &'a mut BufReader<R>,
330 pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
331}
332
333#[derive(Debug)]
336pub struct SnapshotBankFields {
337 full: BankFieldsToDeserialize,
338 incremental: Option<BankFieldsToDeserialize>,
339}
340
341impl SnapshotBankFields {
342 pub fn new(
343 full: BankFieldsToDeserialize,
344 incremental: Option<BankFieldsToDeserialize>,
345 ) -> Self {
346 Self { full, incremental }
347 }
348
349 pub fn collapse_into(self) -> BankFieldsToDeserialize {
351 self.incremental.unwrap_or(self.full)
352 }
353}
354
355#[derive(Debug)]
358pub struct SnapshotAccountsDbFields {
359 full_snapshot_accounts_db_fields: AccountsDbFields,
360 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
361}
362
363impl SnapshotAccountsDbFields {
364 pub(crate) fn new(
365 full_snapshot_accounts_db_fields: AccountsDbFields,
366 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
367 ) -> Self {
368 Self {
369 full_snapshot_accounts_db_fields,
370 incremental_snapshot_accounts_db_fields,
371 }
372 }
373
374 fn into_bank_hash_info(self) -> BankHashInfo {
379 let AccountsDbFields(
380 _snapshot_storages,
381 _snapshot_write_version,
382 _snapshot_slot,
383 snapshot_bank_hash_info,
384 _snapshot_historical_roots,
385 _snapshot_historical_roots_with_hash,
386 ) = self
387 .incremental_snapshot_accounts_db_fields
388 .unwrap_or(self.full_snapshot_accounts_db_fields);
389 snapshot_bank_hash_info
390 }
391}
392
393pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
394where
395 W: Write,
396 T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
397{
398 wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
399}
400
401pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
402where
403 R: Reader<'a>,
404 T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
405{
406 wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
407}
408
409pub(crate) fn deserialize_from<R, T>(reader: R) -> bincode::Result<T>
410where
411 R: Read,
412 T: DeserializeOwned,
413{
414 bincode::options()
415 .with_limit(MAX_STREAM_SIZE as u64)
416 .with_fixint_encoding()
417 .allow_trailing_bytes()
418 .deserialize_from::<R, T>(reader)
419}
420
421#[cfg(test)]
422fn deserialize_accounts_db_fields<R>(stream: &mut BufReader<R>) -> Result<AccountsDbFields, Error>
423where
424 R: Read,
425{
426 deserialize_from::<_, _>(stream)
427}
428
429#[cfg_attr(
436 feature = "frozen-abi",
437 derive(AbiExample, Serialize, StableAbi, StableAbiSample)
438)]
439#[derive(Clone, Debug, Deserialize)]
440struct ExtraFieldsToDeserialize {
441 #[serde(deserialize_with = "default_on_eof")]
442 lamports_per_signature: u64,
443 #[serde(deserialize_with = "default_on_eof")]
444 _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
445 #[serde(deserialize_with = "default_on_eof")]
446 _unused_epoch_accounts_hash: Option<Hash>,
447 #[serde(deserialize_with = "default_on_eof")]
448 #[cfg_attr(
450 feature = "frozen-abi",
451 stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
452 stable_abi::context::SequenceLenMax(1))")
453 )]
454 versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
455 #[serde(deserialize_with = "default_on_eof")]
456 accounts_lt_hash: Option<SerdeAccountsLtHash>,
457 #[serde(deserialize_with = "default_on_eof")]
458 block_id: Option<Hash>,
459}
460
461#[cfg_attr(
468 feature = "frozen-abi",
469 derive(AbiExample, StableAbi, StableAbiSample),
470 frozen_abi(
473 abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
474 abi_serializer = ["bincode", "wincode"],
475 test_roundtrip = "no"
476 )
477)]
478#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
479#[derive(Debug, Serialize, SchemaWrite)]
480pub struct ExtraFieldsToSerialize {
481 pub lamports_per_signature: u64,
482 pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
483 pub unused_epoch_accounts_hash: Option<Hash>,
484 pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
485 pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
486 pub block_id: Option<Hash>,
487}
488
489#[cfg_attr(
495 feature = "frozen-abi",
496 derive(Serialize, StableAbi, StableAbiSample),
497 frozen_abi(
498 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
499 abi_serializer = "bincode",
500 test_roundtrip = "wire_only"
501 )
502)]
503#[derive(Deserialize)]
504struct DeserializableBankSnapshot {
505 bank: DeserializableVersionedBank,
506 accounts_db: AccountsDbFields,
507 extra_fields: ExtraFieldsToDeserialize,
508}
509
510impl DeserializableBankSnapshot {
511 fn into_fields(self) -> Result<(BankFieldsToDeserialize, AccountsDbFields), Error> {
513 let Self {
514 bank,
515 accounts_db,
516 extra_fields,
517 } = self;
518 if !bank.unused_epoch_stakes.is_empty() {
519 return Err(Box::new(bincode::ErrorKind::Custom(
520 "Expected deserialized bank's unused_epoch_stakes field to be empty".to_string(),
521 )));
522 }
523 let mut bank_fields = BankFieldsToDeserialize::from(bank);
524 let ExtraFieldsToDeserialize {
525 lamports_per_signature,
526 _unused_incremental_snapshot_persistence,
527 _unused_epoch_accounts_hash,
528 versioned_epoch_stakes,
529 accounts_lt_hash,
530 block_id,
531 } = extra_fields;
532
533 bank_fields.fee_rate_governor = bank_fields
534 .fee_rate_governor
535 .clone_with_lamports_per_signature(lamports_per_signature);
536 bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
537 bank_fields.accounts_lt_hash = accounts_lt_hash
538 .expect("snapshot must have accounts_lt_hash")
539 .into();
540 bank_fields.block_id = block_id;
541
542 Ok((bank_fields, accounts_db))
543 }
544}
545
546fn deserialize_bank_fields<R>(
547 stream: &mut BufReader<R>,
548) -> Result<(BankFieldsToDeserialize, AccountsDbFields), Error>
549where
550 R: Read,
551{
552 deserialize_from::<_, DeserializableBankSnapshot>(stream)?.into_fields()
553}
554
555pub(crate) fn fields_from_stream<R: Read>(
556 snapshot_stream: &mut BufReader<R>,
557) -> std::result::Result<(BankFieldsToDeserialize, AccountsDbFields), Error> {
558 deserialize_bank_fields(snapshot_stream)
559}
560
561#[cfg(feature = "dev-context-only-utils")]
562pub(crate) fn fields_from_streams(
563 snapshot_streams: &mut SnapshotStreams<impl Read>,
564) -> std::result::Result<(SnapshotBankFields, SnapshotAccountsDbFields), Error> {
565 let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
566 fields_from_stream(snapshot_streams.full_snapshot_stream)?;
567 let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
568 snapshot_streams
569 .incremental_snapshot_stream
570 .as_mut()
571 .map(|stream| fields_from_stream(stream))
572 .transpose()?
573 .unzip();
574
575 let snapshot_bank_fields = SnapshotBankFields {
576 full: full_snapshot_bank_fields,
577 incremental: incremental_snapshot_bank_fields,
578 };
579 let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
580 full_snapshot_accounts_db_fields,
581 incremental_snapshot_accounts_db_fields,
582 };
583 Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
584}
585
586#[derive(Debug)]
588pub struct BankFromStreamsInfo {
589 pub calculated_accounts_lt_hash: AccountsLtHash,
592}
593
594#[allow(clippy::too_many_arguments)]
595#[cfg(test)]
596pub(crate) fn bank_from_streams<R>(
597 snapshot_streams: &mut SnapshotStreams<R>,
598 account_paths: &[PathBuf],
599 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
600 genesis_config: &GenesisConfig,
601 runtime_config: &RuntimeConfig,
602 debug_keys: Option<Arc<HashSet<Pubkey>>>,
603 limit_load_slot_count_from_snapshot: Option<usize>,
604 verify_index: bool,
605 accounts_db_config: AccountsDbConfig,
606 accounts_update_notifier: Option<AccountsUpdateNotifier>,
607 exit: Arc<AtomicBool>,
608) -> std::result::Result<(Bank, BankFromStreamsInfo), Error>
609where
610 R: Read,
611{
612 let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
613 let (bank, info) = reconstruct_bank_from_fields(
614 bank_fields,
615 accounts_db_fields,
616 genesis_config,
617 runtime_config,
618 account_paths,
619 storage_and_next_append_vec_id,
620 debug_keys,
621 None, limit_load_slot_count_from_snapshot,
623 verify_index,
624 accounts_db_config,
625 accounts_update_notifier,
626 exit,
627 )?;
628 Ok((
629 bank,
630 BankFromStreamsInfo {
631 calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
632 },
633 ))
634}
635
636#[cfg(test)]
637pub(crate) fn bank_to_stream<W>(
638 stream: &mut io::BufWriter<W>,
639 bank: &Bank,
640 snapshot_storages: &[Arc<AccountStorageEntry>],
641) -> wincode::WriteResult<()>
642where
643 W: Write,
644{
645 let mut bank_fields = bank.get_fields_to_serialize();
646 let bank_hash_stats = bank.get_bank_hash_stats();
647 let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
648 let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
649 let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
650 let block_id = Some(bank_fields.block_id);
651 serialize_bank_snapshot_into_wincode(
652 stream,
653 bank_fields,
654 bank_hash_stats,
655 snapshot_storages,
656 ExtraFieldsToSerialize {
657 lamports_per_signature,
658 unused_incremental_snapshot_persistence: None,
659 unused_epoch_accounts_hash: None,
660 versioned_epoch_stakes,
661 accounts_lt_hash,
662 block_id,
663 },
664 )
665}
666
667pub fn serialize_bank_snapshot_into(
669 stream: &mut dyn Write,
670 bank_fields: BankFieldsToSerialize,
671 bank_hash_stats: BankHashStats,
672 account_storage_entries: &[Arc<AccountStorageEntry>],
673 extra_fields: ExtraFieldsToSerialize,
674) -> Result<(), Error> {
675 let mut serializer = bincode::Serializer::new(
676 stream,
677 bincode::DefaultOptions::new().with_fixint_encoding(),
678 );
679 serialize_bank_snapshot_with(
680 &mut serializer,
681 bank_fields,
682 bank_hash_stats,
683 account_storage_entries,
684 extra_fields,
685 )
686}
687
688#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
692#[derive(Serialize, SchemaWrite)]
693struct SerializableBankSnapshot<E> {
694 bank: SerializableVersionedBank,
695 accounts_db: SerializableAccountsDb<E>,
696 extra_fields: ExtraFieldsToSerialize,
697}
698
699#[cfg(all(test, feature = "frozen-abi"))]
704#[frozen_abi(
705 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
706 abi_serializer = ["bincode", "wincode"],
707 test_roundtrip = "no"
708)]
709type SerializableBankSnapshotForAbi = SerializableBankSnapshot<Vec<SlotAccountStorageEntries>>;
710
711pub fn serialize_bank_snapshot_with<S>(
713 serializer: S,
714 bank_fields: BankFieldsToSerialize,
715 bank_hash_stats: BankHashStats,
716 account_storage_entries: &[Arc<AccountStorageEntry>],
717 extra_fields: ExtraFieldsToSerialize,
718) -> Result<S::Ok, S::Error>
719where
720 S: serde::Serializer,
721{
722 let slot = bank_fields.slot;
723 let snapshot = SerializableBankSnapshot {
724 bank: SerializableVersionedBank::from(bank_fields),
725 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
726 extra_fields,
727 };
728 snapshot.serialize(serializer)
731}
732
733pub fn serialize_bank_snapshot_into_wincode(
738 stream: &mut dyn Write,
739 bank_fields: BankFieldsToSerialize,
740 bank_hash_stats: BankHashStats,
741 account_storage_entries: &[Arc<AccountStorageEntry>],
742 extra_fields: ExtraFieldsToSerialize,
743) -> wincode::WriteResult<()> {
744 let slot = bank_fields.slot;
745 let snapshot = SerializableBankSnapshot {
746 bank: SerializableVersionedBank::from(bank_fields),
747 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
748 extra_fields,
749 };
750 serialize_into(stream, &snapshot)
751}
752
753#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
758#[derive(Serialize, SchemaWrite)]
759struct SerializableAccountsDb<E> {
760 accounts_storage_entries: E,
762 unused_write_version: u64, slot: Slot,
764 bank_hash_info: BankHashInfo,
765 historical_roots: Vec<Slot>,
767 historical_roots_with_hash: Vec<(Slot, Hash)>,
769}
770
771struct SerializableExactIteratorView<I>(I);
777
778impl<I: Iterator> IntoIterator for SerializableExactIteratorView<I> {
779 type Item = I::Item;
780 type IntoIter = I;
781
782 fn into_iter(self) -> Self::IntoIter {
783 self.0
784 }
785}
786
787impl<I: Iterator + Clone> IntoIterator for &SerializableExactIteratorView<I> {
788 type Item = I::Item;
789 type IntoIter = I;
790
791 fn into_iter(self) -> Self::IntoIter {
792 self.0.clone()
793 }
794}
795
796impl<I> Serialize for SerializableExactIteratorView<I>
797where
798 I: ExactSizeIterator + Clone,
799 I::Item: Serialize,
800{
801 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
802 where
803 S: serde::Serializer,
804 {
805 serializer.collect_seq(self.0.clone())
806 }
807}
808
809unsafe impl<I, C: wincode::config::Config> SchemaWrite<C> for SerializableExactIteratorView<I>
812where
813 I: ExactSizeIterator + Clone,
814 I::Item: SchemaWrite<C> + Borrow<<I::Item as SchemaWrite<C>>::Src>,
815{
816 type Src = Self;
817
818 fn size_of(src: &Self::Src) -> WriteResult<usize> {
819 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::size_of(
820 src,
821 )
822 }
823
824 fn write(writer: impl wincode::io::Writer, src: &Self::Src) -> WriteResult<()> {
825 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::write(
826 writer, src,
827 )
828 }
829}
830
831impl SerializableAccountsDb<()> {
832 fn new(
833 slot: Slot,
834 account_storage_entries: &[Arc<AccountStorageEntry>],
835 bank_hash_stats: BankHashStats,
836 ) -> SerializableAccountsDb<
837 SerializableExactIteratorView<
838 impl ExactSizeIterator<Item = SlotAccountStorageEntries> + Clone + '_,
839 >,
840 > {
841 let accounts_storage_entries =
845 SerializableExactIteratorView(account_storage_entries.iter().map(move |entry| {
846 SlotAccountStorageEntries {
847 slot: entry.slot(),
848 entries: smallvec![SerializableAccountStorageEntry::new(entry, slot)],
849 }
850 }));
851 let bank_hash_info = BankHashInfo {
852 unused_accounts_delta_hash: [0; 32],
853 unused_accounts_hash: [0; 32],
854 stats: bank_hash_stats,
855 };
856 SerializableAccountsDb {
857 accounts_storage_entries,
858 unused_write_version: 0,
859 slot,
860 bank_hash_info,
861 historical_roots: Vec::default(),
862 historical_roots_with_hash: Vec::default(),
863 }
864 }
865}
866
867#[cfg(all(test, feature = "frozen-abi"))]
872#[frozen_abi(
873 abi_digest = "2TpwtsyrverM4ius4ykX3RRCGqeLfXJQbAvYHkxdUVrs",
874 abi_serializer = ["bincode", "wincode"],
875 test_roundtrip = "no"
876)]
877type SerializableAccountsDbForAbi = SerializableAccountsDb<Vec<SlotAccountStorageEntries>>;
878
879#[derive(Debug)]
881pub(crate) struct ReconstructedBankInfo {
882 pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
885 pub(crate) calculated_capitalization: u64,
887}
888
889#[expect(clippy::too_many_arguments)]
890pub(crate) fn reconstruct_bank_from_fields(
891 bank_fields: SnapshotBankFields,
892 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
893 genesis_config: &GenesisConfig,
894 runtime_config: &RuntimeConfig,
895 account_paths: &[PathBuf],
896 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
897 debug_keys: Option<Arc<HashSet<Pubkey>>>,
898 leader_for_tests: Option<SlotLeader>,
899 limit_load_slot_count_from_snapshot: Option<usize>,
900 verify_index: bool,
901 accounts_db_config: AccountsDbConfig,
902 accounts_update_notifier: Option<AccountsUpdateNotifier>,
903 exit: Arc<AtomicBool>,
904) -> Result<(Bank, ReconstructedBankInfo), Error> {
905 let mut bank_fields = bank_fields.collapse_into();
906 let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
908 let epoch_stakes_handle = thread::Builder::new()
909 .name("solRctEpochStk".into())
910 .spawn(|| {
911 deserializable_epoch_stakes
912 .into_iter()
913 .map(|(epoch, stakes)| (epoch, stakes.into()))
914 .collect()
915 })?;
916 let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
917 snapshot_accounts_db_fields,
918 account_paths,
919 storage_and_next_append_vec_id,
920 limit_load_slot_count_from_snapshot,
921 verify_index,
922 accounts_db_config,
923 accounts_update_notifier,
924 exit,
925 )?;
926 bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
927
928 let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
929 let runtime_config = Arc::new(runtime_config.clone());
930 let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
931
932 let bank = Bank::new_from_snapshot(
933 bank_rc,
934 genesis_config,
935 runtime_config,
936 bank_fields,
937 leader_for_tests,
938 debug_keys,
939 reconstructed_accounts_db_info.accounts_data_len,
940 epoch_stakes,
941 );
942
943 Ok((
944 bank,
945 ReconstructedBankInfo {
946 calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
947 calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
948 },
949 ))
950}
951
952pub(crate) fn reconstruct_single_storage(
953 slot: &Slot,
954 append_vec_file_info: FileInfo,
955 id: AccountsFileId,
956 obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
957) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
958 let obsolete_accounts =
969 if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
970 if obsolete_id != id {
971 return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
972 }
973
974 obsolete_accounts
975 } else {
976 ObsoleteAccounts::default()
977 };
978
979 let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
980 Ok(Arc::new(AccountStorageEntry::new_existing(
981 *slot,
982 id,
983 accounts_file,
984 obsolete_accounts,
985 )))
986}
987
988pub(crate) fn remap_append_vec_file(
992 slot: Slot,
993 old_append_vec_id: SerializedAccountsFileId,
994 append_vec_file_info: FileInfo,
995 next_append_vec_id: &AtomicAccountsFileId,
996 num_collisions: &mut usize,
997) -> io::Result<(AccountsFileId, FileInfo)> {
998 #[cfg(all(target_os = "linux", target_env = "gnu"))]
999 let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
1000
1001 let mut remapped_append_vec_path = append_vec_file_info.path.clone();
1002
1003 let (remapped_append_vec_id, remapped_append_vec_path) = loop {
1009 let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
1010
1011 if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
1013 break (remapped_append_vec_id, remapped_append_vec_path);
1014 }
1015
1016 let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
1017 remapped_append_vec_path = remapped_append_vec_path
1018 .parent()
1019 .unwrap()
1020 .join(remapped_file_name);
1021
1022 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1023 {
1024 let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
1025
1026 match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
1029 Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
1031 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
1034 Err(e) => return Err(e),
1035 }
1036 }
1037
1038 #[cfg(any(
1039 not(target_os = "linux"),
1040 all(target_os = "linux", not(target_env = "gnu"))
1041 ))]
1042 if std::fs::metadata(&remapped_append_vec_path).is_err() {
1043 break (remapped_append_vec_id, remapped_append_vec_path);
1044 }
1045
1046 *num_collisions += 1;
1049 };
1050
1051 #[cfg(any(
1054 not(target_os = "linux"),
1055 all(target_os = "linux", not(target_env = "gnu"))
1056 ))]
1057 if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
1058 std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
1059 }
1060
1061 Ok((
1062 remapped_append_vec_id,
1063 FileInfo {
1064 path: remapped_append_vec_path,
1065 ..append_vec_file_info
1066 },
1067 ))
1068}
1069
1070pub(crate) fn remap_and_reconstruct_single_storage(
1071 slot: Slot,
1072 old_append_vec_id: SerializedAccountsFileId,
1073 append_vec_file_info: FileInfo,
1074 next_append_vec_id: &AtomicAccountsFileId,
1075 num_collisions: &mut usize,
1076) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
1077 let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
1078 slot,
1079 old_append_vec_id,
1080 append_vec_file_info,
1081 next_append_vec_id,
1082 num_collisions,
1083 )?;
1084 let storage = reconstruct_single_storage(
1085 &slot,
1086 remapped_append_vec_file_info,
1087 remapped_append_vec_id,
1088 None,
1089 )?;
1090 Ok(storage)
1091}
1092
1093#[derive(Debug)]
1095pub struct ReconstructedAccountsDbInfo {
1096 pub accounts_data_len: u64,
1097 pub calculated_accounts_lt_hash: AccountsLtHash,
1100 pub calculated_capitalization: u64,
1102 pub bank_hash_stats: BankHashStats,
1103}
1104
1105fn reconstruct_accountsdb_from_fields(
1106 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
1107 account_paths: &[PathBuf],
1108 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
1109 limit_load_slot_count_from_snapshot: Option<usize>,
1110 verify_index: bool,
1111 accounts_db_config: AccountsDbConfig,
1112 accounts_update_notifier: Option<AccountsUpdateNotifier>,
1113 exit: Arc<AtomicBool>,
1114) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), Error> {
1115 let mut accounts_db = AccountsDb::new_with_config(
1116 account_paths.to_vec(),
1117 accounts_db_config,
1118 accounts_update_notifier,
1119 exit,
1120 );
1121
1122 let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
1123
1124 for path in &accounts_db.paths {
1126 std::fs::create_dir_all(path)
1127 .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
1128 }
1129
1130 let StorageAndNextAccountsFileId {
1131 storage,
1132 next_append_vec_id,
1133 } = storage_and_next_append_vec_id;
1134
1135 assert!(
1136 !storage.is_empty(),
1137 "At least one storage entry must exist from deserializing stream"
1138 );
1139
1140 let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
1141 let max_append_vec_id = next_append_vec_id - 1;
1142 assert!(
1143 max_append_vec_id <= AccountsFileId::MAX / 2,
1144 "Storage id {max_append_vec_id} larger than allowed max"
1145 );
1146
1147 accounts_db.storage.initialize(storage);
1149 accounts_db
1150 .next_id
1151 .store(next_append_vec_id, Ordering::Release);
1152
1153 info!("Building accounts index...");
1154 let start = Instant::now();
1155 let IndexGenerationInfo {
1156 accounts_data_len,
1157 calculated_accounts_lt_hash,
1158 calculated_capitalization,
1159 } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1160 info!("Building accounts index... Done in {:?}", start.elapsed());
1161
1162 Ok((
1163 accounts_db,
1164 ReconstructedAccountsDbInfo {
1165 accounts_data_len,
1166 calculated_accounts_lt_hash,
1167 calculated_capitalization,
1168 bank_hash_stats: snapshot_bank_hash_info.stats,
1169 },
1170 ))
1171}
1172
1173#[cfg(all(target_os = "linux", target_env = "gnu"))]
1175fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1176 let ret = unsafe {
1177 libc::renameat2(
1178 libc::AT_FDCWD,
1179 src.as_ptr() as *const _,
1180 libc::AT_FDCWD,
1181 dest.as_ptr() as *const _,
1182 libc::RENAME_NOREPLACE,
1183 )
1184 };
1185 if ret == -1 {
1186 return Err(io::Error::last_os_error());
1187 }
1188
1189 Ok(())
1190}
1191
1192#[cfg(all(target_os = "linux", target_env = "gnu"))]
1193fn cstring_from_path(path: &Path) -> io::Result<CString> {
1194 CString::new(path.as_os_str().as_encoded_bytes())
1199 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1200}