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