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