1#[cfg(feature = "frozen-abi")]
2use solana_frozen_abi::stable_abi;
3#[cfg(all(target_os = "linux", target_env = "gnu"))]
4use std::{
5 ffi::{CStr, CString},
6 path::Path,
7};
8use {
9 crate::{
10 bank::{Bank, BankFieldsToDeserialize, BankFieldsToSerialize, BankHashStats, BankRc},
11 epoch_stakes::{DeserializableVersionedEpochStakes, VersionedEpochStakes},
12 runtime_config::RuntimeConfig,
13 snapshot_utils::StorageAndNextAccountsFileId,
14 stake_account::StakeAccount,
15 stakes::{
16 DeserializableDelegationStakes, Stakes, serialize_stake_accounts_to_delegation_format,
17 },
18 },
19 agave_fs::FileInfo,
20 agave_snapshots::error::SnapshotError,
21 bincode::{self, Error, config::Options},
22 log::*,
23 serde::{Deserialize, Serialize},
24 smallvec::{SmallVec, smallvec},
25 solana_accounts_db::{
26 ObsoleteAccounts,
27 account_storage_entry::AccountStorageEntry,
28 accounts::Accounts,
29 accounts_db::{
30 AccountsDb, AccountsDbConfig, AccountsFileId, AtomicAccountsFileId, IndexGenerationInfo,
31 },
32 accounts_file::AccountsFile,
33 accounts_hash::AccountsLtHash,
34 accounts_update_notifier_interface::AccountsUpdateNotifier,
35 blockhash_queue::BlockhashQueue,
36 },
37 solana_clock::{Epoch, Slot, UnixTimestamp},
38 solana_epoch_schedule::EpochSchedule,
39 solana_fee_calculator::FeeRateGovernor,
40 solana_genesis_config::GenesisConfig,
41 solana_hard_forks::HardForks,
42 solana_hash::Hash,
43 solana_inflation::Inflation,
44 solana_lattice_hash::lt_hash::LtHash,
45 solana_leader_schedule::SlotLeader,
46 solana_pubkey::Pubkey,
47 solana_serde::default_on_eof,
48 solana_stake_interface::state::Delegation,
49 std::{
50 borrow::Borrow,
51 collections::{HashMap, HashSet},
52 io::{self, BufReader, Read, Write},
53 path::PathBuf,
54 result::Result,
55 sync::{
56 Arc,
57 atomic::{AtomicBool, Ordering},
58 },
59 thread,
60 time::Instant,
61 },
62 types::{SerdeAccountsLtHash, UnusedRentCollector},
63 wincode::{
64 ReadResult, SchemaRead, SchemaReadOwned, SchemaWrite, WriteResult,
65 adapter::DefaultOnEmptyRead,
66 containers::FromIntoIterator,
67 io::{Reader, std_write::WriteAdapter},
68 len::BincodeLen,
69 },
70};
71
72mod obsolete_accounts;
73mod status_cache;
74mod storage;
75mod storages_list;
76mod tests;
77mod types;
78
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(
98 feature = "frozen-abi",
99 stable_abi_sample(with = "solana_frozen_abi::stable_abi::sample_collection_sized(rng, \
100 solana_frozen_abi::stable_abi::context::SequenceLenRange::new(0.\
101 .=5))")
102 )]
103 entries: SmallVec<[SerializableAccountStorageEntry; 1]>,
104}
105
106#[cfg_attr(
107 feature = "frozen-abi",
108 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
109)]
110#[derive(Debug, Deserialize, SchemaRead)]
111pub(crate) struct AccountsDbFields(
112 Vec<SlotAccountStorageEntries>,
113 u64, Slot,
115 BankHashInfo,
116 #[serde(deserialize_with = "default_on_eof")]
118 #[wincode(with = "DefaultOnEmptyRead<Vec<Slot>>")]
119 Vec<Slot>,
120 #[serde(deserialize_with = "default_on_eof")]
122 #[wincode(with = "DefaultOnEmptyRead<Vec<(Slot, Hash)>>")]
123 Vec<(Slot, Hash)>,
124);
125
126#[repr(C)]
127#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
128#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
129#[derive(Serialize, Deserialize, Clone, Debug, SchemaRead, SchemaWrite)]
130pub struct UnusedIncrementalSnapshotPersistence {
131 pub full_slot: u64,
132 pub full_hash: [u8; 32],
133 pub full_capitalization: u64,
134 pub incremental_hash: [u8; 32],
135 pub incremental_capitalization: u64,
136}
137
138#[repr(C)]
139#[cfg_attr(
140 feature = "frozen-abi",
141 derive(AbiExample, StableAbi, StableAbiSample),
142 frozen_abi(
143 abi_digest = "EcPdH21GSyYYTiSZbAN157YfrT3G8rKvDiNh7q1fw8Bc",
144 abi_serializer = ["bincode", "wincode"],
145 test_roundtrip = "eq_and_wire"
146 )
147)]
148#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, SchemaRead, SchemaWrite)]
149struct BankHashInfo {
150 unused_accounts_delta_hash: [u8; 32],
151 unused_accounts_hash: [u8; 32],
152 stats: BankHashStats,
153}
154
155#[cfg_attr(feature = "frozen-abi", derive(AbiExample, StableAbi, StableAbiSample))]
156#[derive(Default, Clone, PartialEq, Eq, Debug, Deserialize, Serialize, SchemaRead, SchemaWrite)]
157struct UnusedAccounts {
158 unused1: HashSet<Pubkey>,
159 unused2: HashSet<Pubkey>,
160 unused3: HashMap<Pubkey, u64>,
161}
162
163#[cfg_attr(
167 feature = "frozen-abi",
168 derive(Serialize, SchemaWrite, StableAbi, StableAbiSample)
169)]
170#[derive(Clone, Deserialize, SchemaRead)]
171struct DeserializableVersionedBank {
172 blockhash_queue: BlockhashQueue,
173 _unused_ancestors: HashMap<Slot, usize>,
174 hash: Hash,
175 parent_hash: Hash,
176 parent_slot: Slot,
177 hard_forks: HardForks,
178 transaction_count: u64,
179 tick_height: u64,
180 signature_count: u64,
181 capitalization: u64,
182 max_tick_height: u64,
183 hashes_per_tick: Option<u64>,
184 ticks_per_slot: u64,
185 ns_per_slot: u128,
186 genesis_creation_time: UnixTimestamp,
187 slots_per_year: f64,
188 accounts_data_len: u64,
189 slot: Slot,
190 _unused_epoch: Epoch,
191 block_height: u64,
192 leader_id: Pubkey,
193 _unused_collector_fees: u64,
194 _unused_fee_calculator: u64,
195 fee_rate_governor: FeeRateGovernor,
196 _unused_collected_rent: u64,
197 _unused_rent_collector: UnusedRentCollector,
198 epoch_schedule: EpochSchedule,
199 inflation: Inflation,
200 stakes: DeserializableDelegationStakes,
201 _unused_accounts: UnusedAccounts,
202 unused_epoch_stakes: HashMap<Epoch, ()>,
203 is_delta: bool,
204}
205
206impl From<DeserializableVersionedBank> for BankFieldsToDeserialize {
207 fn from(dvb: DeserializableVersionedBank) -> Self {
208 const LT_HASH_CANARY: LtHash = LtHash([0xCAFE; LtHash::NUM_ELEMENTS]);
211 let mut blockhash_queue = dvb.blockhash_queue;
213 blockhash_queue.refresh_durable_nonce();
214 BankFieldsToDeserialize {
215 blockhash_queue,
216 hash: dvb.hash,
217 parent_hash: dvb.parent_hash,
218 parent_slot: dvb.parent_slot,
219 hard_forks: dvb.hard_forks,
220 transaction_count: dvb.transaction_count,
221 tick_height: dvb.tick_height,
222 signature_count: dvb.signature_count,
223 capitalization: dvb.capitalization,
224 max_tick_height: dvb.max_tick_height,
225 hashes_per_tick: dvb.hashes_per_tick,
226 ticks_per_slot: dvb.ticks_per_slot,
227 ns_per_slot: dvb.ns_per_slot,
228 genesis_creation_time: dvb.genesis_creation_time,
229 slots_per_year: dvb.slots_per_year,
230 accounts_data_len: dvb.accounts_data_len,
231 slot: dvb.slot,
232 block_height: dvb.block_height,
233 leader_id: dvb.leader_id,
234 fee_rate_governor: dvb.fee_rate_governor,
235 epoch_schedule: dvb.epoch_schedule,
236 inflation: dvb.inflation,
237 stakes: dvb.stakes,
238 is_delta: dvb.is_delta,
239 versioned_epoch_stakes: vec![], accounts_lt_hash: AccountsLtHash(LT_HASH_CANARY), bank_hash_stats: BankHashStats::default(), block_id: None, }
244 }
245}
246
247#[cfg_attr(
250 feature = "frozen-abi",
251 derive(StableAbi, StableAbiSample),
252 frozen_abi(
255 abi_digest = "7bTCffg34CBt8zAyc1H81TUazqPTUC1Xtkd597FV7wjr",
256 abi_serializer = ["bincode", "wincode"],
257 test_roundtrip = "no"
258 )
259)]
260#[derive(Serialize, SchemaWrite)]
261struct SerializableVersionedBank {
262 blockhash_queue: BlockhashQueue,
263 unused_ancestors: HashMap<Slot, usize>,
264 hash: Hash,
265 parent_hash: Hash,
266 parent_slot: Slot,
267 hard_forks: HardForks,
268 transaction_count: u64,
269 tick_height: u64,
270 signature_count: u64,
271 capitalization: u64,
272 max_tick_height: u64,
273 hashes_per_tick: Option<u64>,
274 ticks_per_slot: u64,
275 ns_per_slot: u128,
276 genesis_creation_time: UnixTimestamp,
277 slots_per_year: f64,
278 accounts_data_len: u64,
279 slot: Slot,
280 unused_epoch: Epoch,
281 block_height: u64,
282 leader_id: Pubkey,
283 unused_collector_fees: u64,
284 unused_fee_calculator: u64,
285 fee_rate_governor: FeeRateGovernor,
286 unused_collected_rent: u64,
287 unused_rent_collector: UnusedRentCollector,
288 epoch_schedule: EpochSchedule,
289 inflation: Inflation,
290 #[serde(serialize_with = "serialize_stake_accounts_to_delegation_format")]
291 stakes: Stakes<StakeAccount<Delegation>>,
292 unused_accounts: UnusedAccounts,
293 unused_epoch_stakes: HashMap<Epoch, ()>,
294 is_delta: bool,
295}
296
297impl From<BankFieldsToSerialize> for SerializableVersionedBank {
298 fn from(rhs: BankFieldsToSerialize) -> Self {
299 Self {
300 blockhash_queue: rhs.blockhash_queue,
301 unused_ancestors: HashMap::default(),
302 hash: rhs.hash,
303 parent_hash: rhs.parent_hash,
304 parent_slot: rhs.parent_slot,
305 hard_forks: rhs.hard_forks,
306 transaction_count: rhs.transaction_count,
307 tick_height: rhs.tick_height,
308 signature_count: rhs.signature_count,
309 capitalization: rhs.capitalization,
310 max_tick_height: rhs.max_tick_height,
311 hashes_per_tick: rhs.hashes_per_tick,
312 ticks_per_slot: rhs.ticks_per_slot,
313 ns_per_slot: rhs.ns_per_slot,
314 genesis_creation_time: rhs.genesis_creation_time,
315 slots_per_year: rhs.slots_per_year,
316 accounts_data_len: rhs.accounts_data_len,
317 slot: rhs.slot,
318 unused_epoch: 0,
319 block_height: rhs.block_height,
320 leader_id: rhs.leader_id,
321 unused_collector_fees: 0,
322 unused_fee_calculator: 0,
323 fee_rate_governor: rhs.fee_rate_governor,
324 unused_collected_rent: u64::default(),
325 unused_rent_collector: UnusedRentCollector::zeroed(),
326 epoch_schedule: rhs.epoch_schedule,
327 inflation: rhs.inflation,
328 stakes: rhs.stakes,
329 unused_accounts: UnusedAccounts::default(),
330 unused_epoch_stakes: HashMap::default(),
331 is_delta: rhs.is_delta,
332 }
333 }
334}
335
336pub struct SnapshotStreams<'a, R> {
339 pub full_snapshot_stream: &'a mut BufReader<R>,
340 pub incremental_snapshot_stream: Option<&'a mut BufReader<R>>,
341}
342
343#[derive(Debug)]
346pub struct SnapshotBankFields {
347 full: BankFieldsToDeserialize,
348 incremental: Option<BankFieldsToDeserialize>,
349}
350
351impl SnapshotBankFields {
352 pub fn new(
353 full: BankFieldsToDeserialize,
354 incremental: Option<BankFieldsToDeserialize>,
355 ) -> Self {
356 Self { full, incremental }
357 }
358
359 pub fn collapse_into(self) -> BankFieldsToDeserialize {
361 self.incremental.unwrap_or(self.full)
362 }
363}
364
365#[derive(Debug)]
368pub struct SnapshotAccountsDbFields {
369 full_snapshot_accounts_db_fields: AccountsDbFields,
370 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
371}
372
373impl SnapshotAccountsDbFields {
374 pub(crate) fn new(
375 full_snapshot_accounts_db_fields: AccountsDbFields,
376 incremental_snapshot_accounts_db_fields: Option<AccountsDbFields>,
377 ) -> Self {
378 Self {
379 full_snapshot_accounts_db_fields,
380 incremental_snapshot_accounts_db_fields,
381 }
382 }
383
384 fn into_bank_hash_info(self) -> BankHashInfo {
389 let AccountsDbFields(
390 _snapshot_storages,
391 _snapshot_write_version,
392 _snapshot_slot,
393 snapshot_bank_hash_info,
394 _snapshot_historical_roots,
395 _snapshot_historical_roots_with_hash,
396 ) = self
397 .incremental_snapshot_accounts_db_fields
398 .unwrap_or(self.full_snapshot_accounts_db_fields);
399 snapshot_bank_hash_info
400 }
401}
402
403pub(crate) fn serialize_into<W, T>(writer: W, value: &T) -> WriteResult<()>
404where
405 W: Write,
406 T: SchemaWrite<MaxStreamSizeConfig, Src = T>,
407{
408 wincode::config::serialize_into(WriteAdapter::new(writer), value, MaxStreamSizeConfig::new())
409}
410
411pub(crate) fn deserialize_wincode_from<'a, R, T>(reader: R) -> ReadResult<T>
412where
413 R: Reader<'a>,
414 T: SchemaReadOwned<MaxStreamSizeConfig, Dst = T>,
415{
416 wincode::config::deserialize_from(reader, MaxStreamSizeConfig::new())
417}
418
419#[cfg_attr(
426 feature = "frozen-abi",
427 derive(AbiExample, Serialize, SchemaWrite, StableAbi, StableAbiSample)
428)]
429#[derive(Clone, Debug, Deserialize, SchemaRead)]
430struct ExtraFieldsToDeserialize {
431 #[serde(deserialize_with = "default_on_eof")]
432 #[wincode(with = "DefaultOnEmptyRead<u64>")]
433 lamports_per_signature: u64,
434 #[serde(deserialize_with = "default_on_eof")]
435 #[wincode(with = "DefaultOnEmptyRead<Option<UnusedIncrementalSnapshotPersistence>>")]
436 _unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
437 #[serde(deserialize_with = "default_on_eof")]
438 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
439 _unused_epoch_accounts_hash: Option<Hash>,
440 #[serde(deserialize_with = "default_on_eof")]
441 #[wincode(with = "DefaultOnEmptyRead<Vec<(u64, DeserializableVersionedEpochStakes)>>")]
442 #[cfg_attr(
444 feature = "frozen-abi",
445 stable_abi_sample(with = "stable_abi::sample_collection_sized(rng, \
446 stable_abi::context::SequenceLenMax(1))")
447 )]
448 versioned_epoch_stakes: Vec<(u64, DeserializableVersionedEpochStakes)>,
449 #[serde(deserialize_with = "default_on_eof")]
450 #[wincode(with = "DefaultOnEmptyRead<Option<SerdeAccountsLtHash>>")]
451 accounts_lt_hash: Option<SerdeAccountsLtHash>,
452 #[serde(deserialize_with = "default_on_eof")]
453 #[wincode(with = "DefaultOnEmptyRead<Option<Hash>>")]
454 block_id: Option<Hash>,
455}
456
457#[cfg_attr(
464 feature = "frozen-abi",
465 derive(AbiExample, StableAbi, StableAbiSample),
466 frozen_abi(
469 abi_digest = "A1hmQvmrkwy33dXMpHXTweArYefPfWtsmwXK6EbNV4K6",
470 abi_serializer = ["bincode", "wincode"],
471 test_roundtrip = "no"
472 )
473)]
474#[cfg_attr(feature = "dev-context-only-utils", derive(Default, PartialEq))]
475#[derive(Debug, Serialize, SchemaWrite)]
476pub struct ExtraFieldsToSerialize {
477 pub lamports_per_signature: u64,
478 pub unused_incremental_snapshot_persistence: Option<UnusedIncrementalSnapshotPersistence>,
479 pub unused_epoch_accounts_hash: Option<Hash>,
480 pub versioned_epoch_stakes: HashMap<u64, VersionedEpochStakes>,
481 pub accounts_lt_hash: Option<SerdeAccountsLtHash>,
482 pub block_id: Option<Hash>,
483}
484
485#[cfg_attr(
491 feature = "frozen-abi",
492 derive(Deserialize, Serialize, SchemaWrite, StableAbi, StableAbiSample),
493 frozen_abi(
494 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
495 abi_serializer = ["bincode", "wincode"],
496 test_roundtrip = "wire_only"
497 )
498)]
499#[derive(SchemaRead)]
500struct DeserializableBankSnapshot {
501 bank: DeserializableVersionedBank,
502 accounts_db: AccountsDbFields,
503 extra_fields: ExtraFieldsToDeserialize,
504}
505
506impl DeserializableBankSnapshot {
507 fn into_fields(self) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
509 let Self {
510 bank,
511 accounts_db,
512 extra_fields,
513 } = self;
514 if !bank.unused_epoch_stakes.is_empty() {
515 return Err(wincode::ReadError::InvalidValue(
516 "Expected deserialized bank's unused_epoch_stakes field to be empty",
517 ));
518 }
519 let mut bank_fields = BankFieldsToDeserialize::from(bank);
520 let ExtraFieldsToDeserialize {
521 lamports_per_signature,
522 _unused_incremental_snapshot_persistence,
523 _unused_epoch_accounts_hash,
524 versioned_epoch_stakes,
525 accounts_lt_hash,
526 block_id,
527 } = extra_fields;
528
529 bank_fields.fee_rate_governor = bank_fields
530 .fee_rate_governor
531 .clone_with_lamports_per_signature(lamports_per_signature);
532 bank_fields.versioned_epoch_stakes = versioned_epoch_stakes;
533 bank_fields.accounts_lt_hash = accounts_lt_hash
534 .expect("snapshot must have accounts_lt_hash")
535 .into();
536 bank_fields.block_id = block_id;
537
538 Ok((bank_fields, accounts_db))
539 }
540}
541
542pub(crate) fn fields_from_stream<R: Read>(
543 snapshot_stream: &mut BufReader<R>,
544) -> wincode::ReadResult<(BankFieldsToDeserialize, AccountsDbFields)> {
545 deserialize_wincode_from::<_, DeserializableBankSnapshot>(snapshot_stream)?.into_fields()
546}
547
548#[cfg(feature = "dev-context-only-utils")]
549pub(crate) fn fields_from_streams(
550 snapshot_streams: &mut SnapshotStreams<impl Read>,
551) -> wincode::ReadResult<(SnapshotBankFields, SnapshotAccountsDbFields)> {
552 let (full_snapshot_bank_fields, full_snapshot_accounts_db_fields) =
553 fields_from_stream(snapshot_streams.full_snapshot_stream)?;
554 let (incremental_snapshot_bank_fields, incremental_snapshot_accounts_db_fields) =
555 snapshot_streams
556 .incremental_snapshot_stream
557 .as_mut()
558 .map(|stream| fields_from_stream(stream))
559 .transpose()?
560 .unzip();
561
562 let snapshot_bank_fields = SnapshotBankFields {
563 full: full_snapshot_bank_fields,
564 incremental: incremental_snapshot_bank_fields,
565 };
566 let snapshot_accounts_db_fields = SnapshotAccountsDbFields {
567 full_snapshot_accounts_db_fields,
568 incremental_snapshot_accounts_db_fields,
569 };
570 Ok((snapshot_bank_fields, snapshot_accounts_db_fields))
571}
572
573#[derive(Debug)]
575pub struct BankFromStreamsInfo {
576 pub calculated_accounts_lt_hash: AccountsLtHash,
579}
580
581#[allow(clippy::too_many_arguments)]
582#[cfg(test)]
583pub(crate) fn bank_from_streams<R>(
584 snapshot_streams: &mut SnapshotStreams<R>,
585 account_paths: &[PathBuf],
586 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
587 genesis_config: &GenesisConfig,
588 runtime_config: &RuntimeConfig,
589 debug_keys: Option<Arc<HashSet<Pubkey>>>,
590 limit_load_slot_count_from_snapshot: Option<usize>,
591 verify_index: bool,
592 accounts_db_config: AccountsDbConfig,
593 accounts_update_notifier: Option<AccountsUpdateNotifier>,
594 exit: Arc<AtomicBool>,
595) -> std::result::Result<(Bank, BankFromStreamsInfo), SnapshotError>
596where
597 R: Read,
598{
599 let (bank_fields, accounts_db_fields) = fields_from_streams(snapshot_streams)?;
600 let (bank, info) = reconstruct_bank_from_fields(
601 bank_fields,
602 accounts_db_fields,
603 genesis_config,
604 runtime_config,
605 account_paths,
606 storage_and_next_append_vec_id,
607 debug_keys,
608 None, limit_load_slot_count_from_snapshot,
610 verify_index,
611 accounts_db_config,
612 accounts_update_notifier,
613 exit,
614 )?;
615 Ok((
616 bank,
617 BankFromStreamsInfo {
618 calculated_accounts_lt_hash: info.calculated_accounts_lt_hash,
619 },
620 ))
621}
622
623#[cfg(test)]
624pub(crate) fn bank_to_stream<W>(
625 stream: &mut io::BufWriter<W>,
626 bank: &Bank,
627 snapshot_storages: &[Arc<AccountStorageEntry>],
628) -> wincode::WriteResult<()>
629where
630 W: Write,
631{
632 let mut bank_fields = bank.get_fields_to_serialize();
633 let bank_hash_stats = bank.get_bank_hash_stats();
634 let lamports_per_signature = bank_fields.fee_rate_governor.lamports_per_signature;
635 let versioned_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
636 let accounts_lt_hash = Some(bank_fields.accounts_lt_hash.clone().into());
637 let block_id = Some(bank_fields.block_id);
638 serialize_bank_snapshot_into_wincode(
639 stream,
640 bank_fields,
641 bank_hash_stats,
642 snapshot_storages,
643 ExtraFieldsToSerialize {
644 lamports_per_signature,
645 unused_incremental_snapshot_persistence: None,
646 unused_epoch_accounts_hash: None,
647 versioned_epoch_stakes,
648 accounts_lt_hash,
649 block_id,
650 },
651 )
652}
653
654pub fn serialize_bank_snapshot_into(
656 stream: &mut dyn Write,
657 bank_fields: BankFieldsToSerialize,
658 bank_hash_stats: BankHashStats,
659 account_storage_entries: &[Arc<AccountStorageEntry>],
660 extra_fields: ExtraFieldsToSerialize,
661) -> Result<(), Error> {
662 let mut serializer = bincode::Serializer::new(
663 stream,
664 bincode::DefaultOptions::new().with_fixint_encoding(),
665 );
666 serialize_bank_snapshot_with(
667 &mut serializer,
668 bank_fields,
669 bank_hash_stats,
670 account_storage_entries,
671 extra_fields,
672 )
673}
674
675#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
679#[derive(Serialize, SchemaWrite)]
680struct SerializableBankSnapshot<E> {
681 bank: SerializableVersionedBank,
682 accounts_db: SerializableAccountsDb<E>,
683 extra_fields: ExtraFieldsToSerialize,
684}
685
686#[cfg(all(test, feature = "frozen-abi"))]
691#[frozen_abi(
692 abi_digest = "2TVKjhahaEGqUZAJtMmaaagcxWzhMPUsNrVHsSoNboK7",
693 abi_serializer = ["bincode", "wincode"],
694 test_roundtrip = "no"
695)]
696type SerializableBankSnapshotForAbi = SerializableBankSnapshot<Vec<SlotAccountStorageEntries>>;
697
698pub fn serialize_bank_snapshot_with<S>(
700 serializer: S,
701 bank_fields: BankFieldsToSerialize,
702 bank_hash_stats: BankHashStats,
703 account_storage_entries: &[Arc<AccountStorageEntry>],
704 extra_fields: ExtraFieldsToSerialize,
705) -> Result<S::Ok, S::Error>
706where
707 S: serde::Serializer,
708{
709 let slot = bank_fields.slot;
710 let snapshot = SerializableBankSnapshot {
711 bank: SerializableVersionedBank::from(bank_fields),
712 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
713 extra_fields,
714 };
715 snapshot.serialize(serializer)
718}
719
720pub fn serialize_bank_snapshot_into_wincode(
725 stream: &mut dyn Write,
726 bank_fields: BankFieldsToSerialize,
727 bank_hash_stats: BankHashStats,
728 account_storage_entries: &[Arc<AccountStorageEntry>],
729 extra_fields: ExtraFieldsToSerialize,
730) -> wincode::WriteResult<()> {
731 let slot = bank_fields.slot;
732 let snapshot = SerializableBankSnapshot {
733 bank: SerializableVersionedBank::from(bank_fields),
734 accounts_db: SerializableAccountsDb::new(slot, account_storage_entries, bank_hash_stats),
735 extra_fields,
736 };
737 serialize_into(stream, &snapshot)
738}
739
740#[cfg_attr(feature = "frozen-abi", derive(StableAbi, StableAbiSample))]
745#[derive(Serialize, SchemaWrite)]
746struct SerializableAccountsDb<E> {
747 accounts_storage_entries: E,
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
758struct SerializableExactIteratorView<I>(I);
764
765impl<I: Iterator> IntoIterator for SerializableExactIteratorView<I> {
766 type Item = I::Item;
767 type IntoIter = I;
768
769 fn into_iter(self) -> Self::IntoIter {
770 self.0
771 }
772}
773
774impl<I: Iterator + Clone> IntoIterator for &SerializableExactIteratorView<I> {
775 type Item = I::Item;
776 type IntoIter = I;
777
778 fn into_iter(self) -> Self::IntoIter {
779 self.0.clone()
780 }
781}
782
783impl<I> Serialize for SerializableExactIteratorView<I>
784where
785 I: ExactSizeIterator + Clone,
786 I::Item: Serialize,
787{
788 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
789 where
790 S: serde::Serializer,
791 {
792 serializer.collect_seq(self.0.clone())
793 }
794}
795
796unsafe impl<I, C: wincode::config::Config> SchemaWrite<C> for SerializableExactIteratorView<I>
799where
800 I: ExactSizeIterator + Clone,
801 I::Item: SchemaWrite<C> + Borrow<<I::Item as SchemaWrite<C>>::Src>,
802{
803 type Src = Self;
804
805 fn size_of(src: &Self::Src) -> WriteResult<usize> {
806 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::size_of(
807 src,
808 )
809 }
810
811 fn write(writer: impl wincode::io::Writer, src: &Self::Src) -> WriteResult<()> {
812 <FromIntoIterator<SerializableExactIteratorView<I>, BincodeLen> as SchemaWrite<C>>::write(
813 writer, src,
814 )
815 }
816}
817
818impl SerializableAccountsDb<()> {
819 fn new(
820 slot: Slot,
821 account_storage_entries: &[Arc<AccountStorageEntry>],
822 bank_hash_stats: BankHashStats,
823 ) -> SerializableAccountsDb<
824 SerializableExactIteratorView<
825 impl ExactSizeIterator<Item = SlotAccountStorageEntries> + Clone + '_,
826 >,
827 > {
828 let accounts_storage_entries =
832 SerializableExactIteratorView(account_storage_entries.iter().map(move |entry| {
833 SlotAccountStorageEntries {
834 slot: entry.slot(),
835 entries: smallvec![SerializableAccountStorageEntry::new(entry, slot)],
836 }
837 }));
838 let bank_hash_info = BankHashInfo {
839 unused_accounts_delta_hash: [0; 32],
840 unused_accounts_hash: [0; 32],
841 stats: bank_hash_stats,
842 };
843 SerializableAccountsDb {
844 accounts_storage_entries,
845 unused_write_version: 0,
846 slot,
847 bank_hash_info,
848 historical_roots: Vec::default(),
849 historical_roots_with_hash: Vec::default(),
850 }
851 }
852}
853
854#[cfg(all(test, feature = "frozen-abi"))]
859#[frozen_abi(
860 abi_digest = "2TpwtsyrverM4ius4ykX3RRCGqeLfXJQbAvYHkxdUVrs",
861 abi_serializer = ["bincode", "wincode"],
862 test_roundtrip = "no"
863)]
864type SerializableAccountsDbForAbi = SerializableAccountsDb<Vec<SlotAccountStorageEntries>>;
865
866#[derive(Debug)]
868pub(crate) struct ReconstructedBankInfo {
869 pub(crate) calculated_accounts_lt_hash: AccountsLtHash,
872 pub(crate) calculated_capitalization: u64,
874}
875
876#[expect(clippy::too_many_arguments)]
877pub(crate) fn reconstruct_bank_from_fields(
878 bank_fields: SnapshotBankFields,
879 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
880 genesis_config: &GenesisConfig,
881 runtime_config: &RuntimeConfig,
882 account_paths: &[PathBuf],
883 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
884 debug_keys: Option<Arc<HashSet<Pubkey>>>,
885 leader_for_tests: Option<SlotLeader>,
886 limit_load_slot_count_from_snapshot: Option<usize>,
887 verify_index: bool,
888 accounts_db_config: AccountsDbConfig,
889 accounts_update_notifier: Option<AccountsUpdateNotifier>,
890 exit: Arc<AtomicBool>,
891) -> Result<(Bank, ReconstructedBankInfo), SnapshotError> {
892 let mut bank_fields = bank_fields.collapse_into();
893 let deserializable_epoch_stakes = std::mem::take(&mut bank_fields.versioned_epoch_stakes);
895 let epoch_stakes_handle = thread::Builder::new()
896 .name("solRctEpochStk".into())
897 .spawn(|| {
898 deserializable_epoch_stakes
899 .into_iter()
900 .map(|(epoch, stakes)| (epoch, stakes.into()))
901 .collect()
902 })?;
903 let (accounts_db, reconstructed_accounts_db_info) = reconstruct_accountsdb_from_fields(
904 snapshot_accounts_db_fields,
905 account_paths,
906 storage_and_next_append_vec_id,
907 limit_load_slot_count_from_snapshot,
908 verify_index,
909 accounts_db_config,
910 accounts_update_notifier,
911 exit,
912 )?;
913 bank_fields.bank_hash_stats = reconstructed_accounts_db_info.bank_hash_stats;
914
915 let bank_rc = BankRc::new(Accounts::new(Arc::new(accounts_db)));
916 let runtime_config = Arc::new(runtime_config.clone());
917 let epoch_stakes = epoch_stakes_handle.join().expect("calculate epoch stakes");
918
919 let bank = Bank::new_from_snapshot(
920 bank_rc,
921 genesis_config,
922 runtime_config,
923 bank_fields,
924 leader_for_tests,
925 debug_keys,
926 reconstructed_accounts_db_info.accounts_data_len,
927 epoch_stakes,
928 );
929
930 Ok((
931 bank,
932 ReconstructedBankInfo {
933 calculated_accounts_lt_hash: reconstructed_accounts_db_info.calculated_accounts_lt_hash,
934 calculated_capitalization: reconstructed_accounts_db_info.calculated_capitalization,
935 },
936 ))
937}
938
939pub(crate) fn reconstruct_single_storage(
940 slot: &Slot,
941 append_vec_file_info: FileInfo,
942 id: AccountsFileId,
943 obsolete_accounts: Option<(ObsoleteAccounts, AccountsFileId, usize)>,
944) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
945 let obsolete_accounts =
956 if let Some((obsolete_accounts, obsolete_id, _obsolete_bytes)) = obsolete_accounts {
957 if obsolete_id != id {
958 return Err(SnapshotError::MismatchedAccountsFileId(id, obsolete_id));
959 }
960
961 obsolete_accounts
962 } else {
963 ObsoleteAccounts::default()
964 };
965
966 let accounts_file = AccountsFile::new_for_startup(append_vec_file_info)?;
967 Ok(Arc::new(AccountStorageEntry::new_existing(
968 *slot,
969 id,
970 accounts_file,
971 obsolete_accounts,
972 )))
973}
974
975pub(crate) fn remap_append_vec_file(
979 slot: Slot,
980 old_append_vec_id: SerializedAccountsFileId,
981 append_vec_file_info: FileInfo,
982 next_append_vec_id: &AtomicAccountsFileId,
983 num_collisions: &mut usize,
984) -> io::Result<(AccountsFileId, FileInfo)> {
985 #[cfg(all(target_os = "linux", target_env = "gnu"))]
986 let append_vec_path_cstr = cstring_from_path(&append_vec_file_info.path)?;
987
988 let mut remapped_append_vec_path = append_vec_file_info.path.clone();
989
990 let (remapped_append_vec_id, remapped_append_vec_path) = loop {
996 let remapped_append_vec_id = next_append_vec_id.fetch_add(1, Ordering::AcqRel);
997
998 if old_append_vec_id == remapped_append_vec_id as SerializedAccountsFileId {
1000 break (remapped_append_vec_id, remapped_append_vec_path);
1001 }
1002
1003 let remapped_file_name = AccountsFile::file_name(slot, remapped_append_vec_id);
1004 remapped_append_vec_path = remapped_append_vec_path
1005 .parent()
1006 .unwrap()
1007 .join(remapped_file_name);
1008
1009 #[cfg(all(target_os = "linux", target_env = "gnu"))]
1010 {
1011 let remapped_append_vec_path_cstr = cstring_from_path(&remapped_append_vec_path)?;
1012
1013 match rename_no_replace(&append_vec_path_cstr, &remapped_append_vec_path_cstr) {
1016 Ok(_) => break (remapped_append_vec_id, remapped_append_vec_path),
1018 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
1021 Err(e) => return Err(e),
1022 }
1023 }
1024
1025 #[cfg(any(
1026 not(target_os = "linux"),
1027 all(target_os = "linux", not(target_env = "gnu"))
1028 ))]
1029 if std::fs::metadata(&remapped_append_vec_path).is_err() {
1030 break (remapped_append_vec_id, remapped_append_vec_path);
1031 }
1032
1033 *num_collisions += 1;
1036 };
1037
1038 #[cfg(any(
1041 not(target_os = "linux"),
1042 all(target_os = "linux", not(target_env = "gnu"))
1043 ))]
1044 if old_append_vec_id != remapped_append_vec_id as SerializedAccountsFileId {
1045 std::fs::rename(&append_vec_file_info.path, &remapped_append_vec_path)?;
1046 }
1047
1048 Ok((
1049 remapped_append_vec_id,
1050 FileInfo {
1051 path: remapped_append_vec_path,
1052 ..append_vec_file_info
1053 },
1054 ))
1055}
1056
1057pub(crate) fn remap_and_reconstruct_single_storage(
1058 slot: Slot,
1059 old_append_vec_id: SerializedAccountsFileId,
1060 append_vec_file_info: FileInfo,
1061 next_append_vec_id: &AtomicAccountsFileId,
1062 num_collisions: &mut usize,
1063) -> Result<Arc<AccountStorageEntry>, SnapshotError> {
1064 let (remapped_append_vec_id, remapped_append_vec_file_info) = remap_append_vec_file(
1065 slot,
1066 old_append_vec_id,
1067 append_vec_file_info,
1068 next_append_vec_id,
1069 num_collisions,
1070 )?;
1071 let storage = reconstruct_single_storage(
1072 &slot,
1073 remapped_append_vec_file_info,
1074 remapped_append_vec_id,
1075 None,
1076 )?;
1077 Ok(storage)
1078}
1079
1080#[derive(Debug)]
1082pub struct ReconstructedAccountsDbInfo {
1083 pub accounts_data_len: u64,
1084 pub calculated_accounts_lt_hash: AccountsLtHash,
1087 pub calculated_capitalization: u64,
1089 pub bank_hash_stats: BankHashStats,
1090}
1091
1092fn reconstruct_accountsdb_from_fields(
1093 snapshot_accounts_db_fields: SnapshotAccountsDbFields,
1094 account_paths: &[PathBuf],
1095 storage_and_next_append_vec_id: StorageAndNextAccountsFileId,
1096 limit_load_slot_count_from_snapshot: Option<usize>,
1097 verify_index: bool,
1098 accounts_db_config: AccountsDbConfig,
1099 accounts_update_notifier: Option<AccountsUpdateNotifier>,
1100 exit: Arc<AtomicBool>,
1101) -> Result<(AccountsDb, ReconstructedAccountsDbInfo), SnapshotError> {
1102 let mut accounts_db = AccountsDb::new_with_config(
1103 account_paths.to_vec(),
1104 accounts_db_config,
1105 accounts_update_notifier,
1106 exit,
1107 );
1108
1109 let snapshot_bank_hash_info = snapshot_accounts_db_fields.into_bank_hash_info();
1110
1111 for path in &accounts_db.paths {
1113 std::fs::create_dir_all(path)
1114 .unwrap_or_else(|err| panic!("Failed to create directory {}: {}", path.display(), err));
1115 }
1116
1117 let StorageAndNextAccountsFileId {
1118 storage,
1119 next_append_vec_id,
1120 } = storage_and_next_append_vec_id;
1121
1122 assert!(
1123 !storage.is_empty(),
1124 "At least one storage entry must exist from deserializing stream"
1125 );
1126
1127 let next_append_vec_id = next_append_vec_id.load(Ordering::Acquire);
1128 let max_append_vec_id = next_append_vec_id - 1;
1129 assert!(
1130 max_append_vec_id <= AccountsFileId::MAX / 2,
1131 "Storage id {max_append_vec_id} larger than allowed max"
1132 );
1133
1134 accounts_db.storage.initialize(storage);
1136 accounts_db
1137 .next_id
1138 .store(next_append_vec_id, Ordering::Release);
1139
1140 info!("Building accounts index...");
1141 let start = Instant::now();
1142 let IndexGenerationInfo {
1143 accounts_data_len,
1144 calculated_accounts_lt_hash,
1145 calculated_capitalization,
1146 } = accounts_db.generate_index(limit_load_slot_count_from_snapshot, verify_index);
1147 info!("Building accounts index... Done in {:?}", start.elapsed());
1148
1149 Ok((
1150 accounts_db,
1151 ReconstructedAccountsDbInfo {
1152 accounts_data_len,
1153 calculated_accounts_lt_hash,
1154 calculated_capitalization,
1155 bank_hash_stats: snapshot_bank_hash_info.stats,
1156 },
1157 ))
1158}
1159
1160#[cfg(all(target_os = "linux", target_env = "gnu"))]
1162fn rename_no_replace(src: &CStr, dest: &CStr) -> io::Result<()> {
1163 let ret = unsafe {
1164 libc::renameat2(
1165 libc::AT_FDCWD,
1166 src.as_ptr() as *const _,
1167 libc::AT_FDCWD,
1168 dest.as_ptr() as *const _,
1169 libc::RENAME_NOREPLACE,
1170 )
1171 };
1172 if ret == -1 {
1173 return Err(io::Error::last_os_error());
1174 }
1175
1176 Ok(())
1177}
1178
1179#[cfg(all(target_os = "linux", target_env = "gnu"))]
1180fn cstring_from_path(path: &Path) -> io::Result<CString> {
1181 CString::new(path.as_os_str().as_encoded_bytes())
1186 .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
1187}