1use {
4 crate::{
5 account_info::AccountInfo,
6 account_storage::meta::StoredAccountMeta,
7 accounts_file::{MatchAccountOwnerError, StoredAccountsInfo},
8 append_vec::{IndexInfo, IndexInfoInner},
9 tiered_storage::{
10 byte_block,
11 file::{TieredReadableFile, TieredWritableFile},
12 footer::{AccountBlockFormat, AccountMetaFormat, TieredStorageFooter},
13 index::{AccountIndexWriterEntry, AccountOffset, IndexBlockFormat, IndexOffset},
14 meta::{
15 AccountAddressRange, AccountMetaFlags, AccountMetaOptionalFields, TieredAccountMeta,
16 },
17 mmap_utils::{get_pod, get_slice},
18 owners::{OwnerOffset, OwnersBlockFormat, OwnersTable},
19 StorableAccounts, TieredStorageError, TieredStorageFormat, TieredStorageResult,
20 },
21 },
22 bytemuck_derive::{Pod, Zeroable},
23 memmap2::{Mmap, MmapOptions},
24 modular_bitfield::prelude::*,
25 solana_pubkey::Pubkey,
26 solana_sdk::{
27 account::{AccountSharedData, ReadableAccount, WritableAccount},
28 rent_collector::RENT_EXEMPT_RENT_EPOCH,
29 stake_history::Epoch,
30 },
31 std::{io::Write, option::Option, path::Path},
32};
33
34pub const HOT_FORMAT: TieredStorageFormat = TieredStorageFormat {
35 meta_entry_size: std::mem::size_of::<HotAccountMeta>(),
36 account_meta_format: AccountMetaFormat::Hot,
37 owners_block_format: OwnersBlockFormat::AddressesOnly,
38 index_block_format: IndexBlockFormat::AddressesThenOffsets,
39 account_block_format: AccountBlockFormat::AlignedRaw,
40};
41
42fn new_hot_footer() -> TieredStorageFooter {
45 TieredStorageFooter {
46 account_meta_format: HOT_FORMAT.account_meta_format,
47 account_meta_entry_size: HOT_FORMAT.meta_entry_size as u32,
48 account_block_format: HOT_FORMAT.account_block_format,
49 index_block_format: HOT_FORMAT.index_block_format,
50 owners_block_format: HOT_FORMAT.owners_block_format,
51 ..TieredStorageFooter::default()
52 }
53}
54
55const MAX_HOT_OWNER_OFFSET: OwnerOffset = OwnerOffset((1 << 29) - 1);
57
58pub(crate) const HOT_ACCOUNT_ALIGNMENT: usize = 8;
64
65pub(crate) const HOT_BLOCK_ALIGNMENT: usize = 8;
70
71const MAX_HOT_ACCOUNT_OFFSET: usize = u32::MAX as usize * HOT_ACCOUNT_ALIGNMENT;
73
74fn padding_bytes(data_len: usize) -> u8 {
76 ((HOT_ACCOUNT_ALIGNMENT - (data_len % HOT_ACCOUNT_ALIGNMENT)) % HOT_ACCOUNT_ALIGNMENT) as u8
77}
78
79const MAX_HOT_PADDING: u8 = 7;
81
82const PADDING_BUFFER: [u8; 8] = [0u8; HOT_ACCOUNT_ALIGNMENT];
84
85#[bitfield(bits = 32)]
86#[repr(C)]
87#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Pod, Zeroable)]
88struct HotMetaPackedFields {
89 padding: B3,
99 owner_offset: B29,
101}
102
103const _: () = assert!(std::mem::size_of::<HotMetaPackedFields>() == 4);
105
106#[repr(C)]
108#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Pod, Zeroable)]
109pub struct HotAccountOffset(u32);
110
111const _: () = assert!(std::mem::size_of::<HotAccountOffset>() == 4);
113
114impl AccountOffset for HotAccountOffset {}
115
116impl HotAccountOffset {
117 pub fn new(offset: usize) -> TieredStorageResult<Self> {
119 if offset > MAX_HOT_ACCOUNT_OFFSET {
120 return Err(TieredStorageError::OffsetOutOfBounds(
121 offset,
122 MAX_HOT_ACCOUNT_OFFSET,
123 ));
124 }
125
126 if offset % HOT_ACCOUNT_ALIGNMENT != 0 {
128 return Err(TieredStorageError::OffsetAlignmentError(
129 offset,
130 HOT_ACCOUNT_ALIGNMENT,
131 ));
132 }
133
134 Ok(HotAccountOffset((offset / HOT_ACCOUNT_ALIGNMENT) as u32))
135 }
136
137 fn offset(&self) -> usize {
139 self.0 as usize * HOT_ACCOUNT_ALIGNMENT
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
146#[repr(C)]
147pub struct HotAccountMeta {
148 lamports: u64,
150 packed_fields: HotMetaPackedFields,
152 flags: AccountMetaFlags,
154}
155
156const _: () = assert!(std::mem::size_of::<HotAccountMeta>() == 8 + 4 + 4);
158
159impl TieredAccountMeta for HotAccountMeta {
160 fn new() -> Self {
162 HotAccountMeta {
163 lamports: 0,
164 packed_fields: HotMetaPackedFields::default(),
165 flags: AccountMetaFlags::new(),
166 }
167 }
168
169 fn with_lamports(mut self, lamports: u64) -> Self {
171 self.lamports = lamports;
172 self
173 }
174
175 fn with_account_data_padding(mut self, padding: u8) -> Self {
178 if padding > MAX_HOT_PADDING {
179 panic!("padding exceeds MAX_HOT_PADDING");
180 }
181 self.packed_fields.set_padding(padding);
182 self
183 }
184
185 fn with_owner_offset(mut self, owner_offset: OwnerOffset) -> Self {
187 if owner_offset > MAX_HOT_OWNER_OFFSET {
188 panic!("owner_offset exceeds MAX_HOT_OWNER_OFFSET");
189 }
190 self.packed_fields.set_owner_offset(owner_offset.0);
191 self
192 }
193
194 fn with_account_data_size(self, _account_data_size: u64) -> Self {
196 self
199 }
200
201 fn with_flags(mut self, flags: &AccountMetaFlags) -> Self {
204 self.flags = *flags;
205 self
206 }
207
208 fn lamports(&self) -> u64 {
210 self.lamports
211 }
212
213 fn account_data_padding(&self) -> u8 {
215 self.packed_fields.padding()
216 }
217
218 fn owner_offset(&self) -> OwnerOffset {
220 OwnerOffset(self.packed_fields.owner_offset())
221 }
222
223 fn flags(&self) -> &AccountMetaFlags {
225 &self.flags
226 }
227
228 fn supports_shared_account_block() -> bool {
231 false
232 }
233
234 fn rent_epoch(&self, account_block: &[u8]) -> Option<Epoch> {
238 self.flags()
239 .has_rent_epoch()
240 .then(|| {
241 let offset = self.optional_fields_offset(account_block)
242 + AccountMetaOptionalFields::rent_epoch_offset(self.flags());
243 byte_block::read_pod::<Epoch>(account_block, offset).copied()
244 })
245 .flatten()
246 }
247
248 fn final_rent_epoch(&self, account_block: &[u8]) -> Epoch {
255 self.rent_epoch(account_block)
256 .unwrap_or(if self.lamports() != 0 {
257 RENT_EXEMPT_RENT_EPOCH
258 } else {
259 Epoch::default()
264 })
265 }
266
267 fn optional_fields_offset(&self, account_block: &[u8]) -> usize {
270 account_block
271 .len()
272 .saturating_sub(AccountMetaOptionalFields::size_from_flags(&self.flags))
273 }
274
275 fn account_data_size(&self, account_block: &[u8]) -> usize {
278 self.optional_fields_offset(account_block)
279 .saturating_sub(self.account_data_padding() as usize)
280 }
281
282 fn account_data<'a>(&self, account_block: &'a [u8]) -> &'a [u8] {
285 &account_block[..self.account_data_size(account_block)]
286 }
287}
288
289#[derive(PartialEq, Eq, Debug)]
291pub struct HotAccount<'accounts_file, M: TieredAccountMeta> {
292 pub meta: &'accounts_file M,
294 pub address: &'accounts_file Pubkey,
296 pub owner: &'accounts_file Pubkey,
298 pub index: IndexOffset,
300 pub account_block: &'accounts_file [u8],
303}
304
305impl<'accounts_file, M: TieredAccountMeta> HotAccount<'accounts_file, M> {
306 pub fn address(&self) -> &'accounts_file Pubkey {
308 self.address
309 }
310
311 pub fn index(&self) -> IndexOffset {
313 self.index
314 }
315
316 pub fn data(&self) -> &'accounts_file [u8] {
318 self.meta.account_data(self.account_block)
319 }
320
321 pub fn stored_size(&self) -> usize {
323 stored_size(self.meta.account_data_size(self.account_block))
324 }
325}
326
327impl<'accounts_file, M: TieredAccountMeta> ReadableAccount for HotAccount<'accounts_file, M> {
328 fn lamports(&self) -> u64 {
330 self.meta.lamports()
331 }
332
333 fn owner(&self) -> &'accounts_file Pubkey {
335 self.owner
336 }
337
338 fn executable(&self) -> bool {
340 self.meta.flags().executable()
341 }
342
343 fn rent_epoch(&self) -> Epoch {
350 self.meta.final_rent_epoch(self.account_block)
351 }
352
353 fn data(&self) -> &'accounts_file [u8] {
355 self.data()
356 }
357}
358
359#[derive(Debug)]
361pub struct HotStorageReader {
362 mmap: Mmap,
363 footer: TieredStorageFooter,
364}
365
366impl HotStorageReader {
367 pub fn new(file: TieredReadableFile) -> TieredStorageResult<Self> {
368 let mmap = unsafe { MmapOptions::new().map(&file.0)? };
369 let footer = *TieredStorageFooter::new_from_mmap(&mmap)?;
374
375 Ok(Self { mmap, footer })
376 }
377
378 pub fn len(&self) -> usize {
380 self.mmap.len()
381 }
382
383 pub fn is_empty(&self) -> bool {
385 self.len() == 0
386 }
387
388 pub fn capacity(&self) -> u64 {
389 self.len() as u64
390 }
391
392 pub fn footer(&self) -> &TieredStorageFooter {
394 &self.footer
395 }
396
397 pub fn num_accounts(&self) -> usize {
400 self.footer.account_entry_count as usize
401 }
402
403 fn get_account_meta_from_offset(
405 &self,
406 account_offset: HotAccountOffset,
407 ) -> TieredStorageResult<&HotAccountMeta> {
408 let offset = account_offset.offset();
409
410 assert!(
411 offset.saturating_add(std::mem::size_of::<HotAccountMeta>())
412 <= self.footer.index_block_offset as usize,
413 "reading HotAccountOffset ({}) would exceed accounts blocks offset boundary ({}).",
414 offset,
415 self.footer.index_block_offset,
416 );
417 let (meta, _) = get_pod::<HotAccountMeta>(&self.mmap, offset)?;
418 Ok(meta)
419 }
420
421 pub(super) fn get_account_offset(
423 &self,
424 index_offset: IndexOffset,
425 ) -> TieredStorageResult<HotAccountOffset> {
426 self.footer
427 .index_block_format
428 .get_account_offset::<HotAccountOffset>(&self.mmap, &self.footer, index_offset)
429 }
430
431 fn get_account_address(&self, index: IndexOffset) -> TieredStorageResult<&Pubkey> {
433 self.footer
434 .index_block_format
435 .get_account_address(&self.mmap, &self.footer, index)
436 }
437
438 fn get_owner_address(&self, owner_offset: OwnerOffset) -> TieredStorageResult<&Pubkey> {
441 self.footer
442 .owners_block_format
443 .get_owner_address(&self.mmap, &self.footer, owner_offset)
444 }
445
446 pub fn account_matches_owners(
456 &self,
457 account_offset: HotAccountOffset,
458 owners: &[Pubkey],
459 ) -> Result<usize, MatchAccountOwnerError> {
460 let account_meta = self
461 .get_account_meta_from_offset(account_offset)
462 .map_err(|_| MatchAccountOwnerError::UnableToLoad)?;
463
464 if account_meta.lamports() == 0 {
465 Err(MatchAccountOwnerError::NoMatch)
466 } else {
467 let account_owner = self
468 .get_owner_address(account_meta.owner_offset())
469 .map_err(|_| MatchAccountOwnerError::UnableToLoad)?;
470
471 owners
472 .iter()
473 .position(|candidate| account_owner == candidate)
474 .ok_or(MatchAccountOwnerError::NoMatch)
475 }
476 }
477
478 fn get_account_block_size(
485 &self,
486 account_offset: HotAccountOffset,
487 index_offset: IndexOffset,
488 ) -> TieredStorageResult<usize> {
489 let account_meta_offset = account_offset.offset();
491
492 let account_block_ending_offset =
496 if index_offset.0.saturating_add(1) == self.footer.account_entry_count {
497 self.footer.index_block_offset as usize
498 } else {
499 self.get_account_offset(IndexOffset(index_offset.0.saturating_add(1)))?
500 .offset()
501 };
502
503 Ok(account_block_ending_offset
507 .saturating_sub(account_meta_offset)
508 .saturating_sub(std::mem::size_of::<HotAccountMeta>()))
509 }
510
511 fn get_account_block(
514 &self,
515 account_offset: HotAccountOffset,
516 index_offset: IndexOffset,
517 ) -> TieredStorageResult<&[u8]> {
518 let (data, _) = get_slice(
519 &self.mmap,
520 account_offset.offset() + std::mem::size_of::<HotAccountMeta>(),
521 self.get_account_block_size(account_offset, index_offset)?,
522 )?;
523
524 Ok(data)
525 }
526
527 pub fn get_stored_account_meta_callback<Ret>(
529 &self,
530 index_offset: IndexOffset,
531 mut callback: impl for<'local> FnMut(StoredAccountMeta<'local>) -> Ret,
532 ) -> TieredStorageResult<Option<Ret>> {
533 if index_offset.0 >= self.footer.account_entry_count {
534 return Ok(None);
535 }
536
537 let account_offset = self.get_account_offset(index_offset)?;
538
539 let meta = self.get_account_meta_from_offset(account_offset)?;
540 let address = self.get_account_address(index_offset)?;
541 let owner = self.get_owner_address(meta.owner_offset())?;
542 let account_block = self.get_account_block(account_offset, index_offset)?;
543
544 Ok(Some(callback(StoredAccountMeta::Hot(HotAccount {
545 meta,
546 address,
547 owner,
548 index: index_offset,
549 account_block,
550 }))))
551 }
552
553 pub fn get_account_shared_data(
555 &self,
556 index_offset: IndexOffset,
557 ) -> TieredStorageResult<Option<AccountSharedData>> {
558 if index_offset.0 >= self.footer.account_entry_count {
559 return Ok(None);
560 }
561
562 let account_offset = self.get_account_offset(index_offset)?;
563
564 let meta = self.get_account_meta_from_offset(account_offset)?;
565 let account_block = self.get_account_block(account_offset, index_offset)?;
566
567 let lamports = meta.lamports();
568 let data = meta.account_data(account_block).to_vec();
569 let owner = *self.get_owner_address(meta.owner_offset())?;
570 let executable = meta.flags().executable();
571 let rent_epoch = meta.final_rent_epoch(account_block);
572 Ok(Some(AccountSharedData::create(
573 lamports, data, owner, executable, rent_epoch,
574 )))
575 }
576
577 pub fn scan_pubkeys(&self, mut callback: impl FnMut(&Pubkey)) -> TieredStorageResult<()> {
579 for i in 0..self.footer.account_entry_count {
580 let address = self.get_account_address(IndexOffset(i))?;
581 callback(address);
582 }
583 Ok(())
584 }
585
586 pub(crate) fn get_account_sizes(
588 &self,
589 sorted_offsets: &[usize],
590 ) -> TieredStorageResult<Vec<usize>> {
591 let mut result = Vec::with_capacity(sorted_offsets.len());
592 for &offset in sorted_offsets {
593 let index_offset = IndexOffset(AccountInfo::get_reduced_offset(offset));
594 let account_offset = self.get_account_offset(index_offset)?;
595 let meta = self.get_account_meta_from_offset(account_offset)?;
596 let account_block = self.get_account_block(account_offset, index_offset)?;
597 let data_len = meta.account_data_size(account_block);
598 result.push(stored_size(data_len));
599 }
600 Ok(result)
601 }
602
603 pub(crate) fn scan_accounts(
605 &self,
606 mut callback: impl for<'local> FnMut(StoredAccountMeta<'local>),
607 ) -> TieredStorageResult<()> {
608 for i in 0..self.footer.account_entry_count {
609 self.get_stored_account_meta_callback(IndexOffset(i), &mut callback)?;
610 }
611 Ok(())
612 }
613
614 pub(crate) fn scan_index(
616 &self,
617 mut callback: impl FnMut(IndexInfo),
618 ) -> TieredStorageResult<()> {
619 for i in 0..self.footer.account_entry_count {
620 let index_offset = IndexOffset(i);
621 let account_offset = self.get_account_offset(index_offset)?;
622
623 let meta = self.get_account_meta_from_offset(account_offset)?;
624 let pubkey = self.get_account_address(index_offset)?;
625 let lamports = meta.lamports();
626 let account_block = self.get_account_block(account_offset, index_offset)?;
627 let data_len = meta.account_data_size(account_block);
628 callback(IndexInfo {
629 index_info: {
630 IndexInfoInner {
631 pubkey: *pubkey,
632 lamports,
633 offset: AccountInfo::reduced_offset_to_offset(i),
634 data_len: data_len as u64,
635 executable: meta.flags().executable(),
636 rent_epoch: meta.final_rent_epoch(account_block),
637 }
638 },
639 stored_size_aligned: stored_size(data_len),
640 });
641 }
642 Ok(())
643 }
644
645 pub fn data_for_archive(&self) -> &[u8] {
647 self.mmap.as_ref()
648 }
649}
650
651fn stored_size(data_len: usize) -> usize {
654 data_len + std::mem::size_of::<Pubkey>()
655}
656
657fn write_optional_fields(
658 file: &mut TieredWritableFile,
659 opt_fields: &AccountMetaOptionalFields,
660) -> TieredStorageResult<usize> {
661 let mut size = 0;
662 if let Some(rent_epoch) = opt_fields.rent_epoch {
663 size += file.write_pod(&rent_epoch)?;
664 }
665
666 debug_assert_eq!(size, opt_fields.size());
667
668 Ok(size)
669}
670
671#[derive(Debug)]
673pub struct HotStorageWriter {
674 storage: TieredWritableFile,
675}
676
677impl HotStorageWriter {
678 pub fn new(file_path: impl AsRef<Path>) -> TieredStorageResult<Self> {
680 Ok(Self {
681 storage: TieredWritableFile::new(file_path)?,
682 })
683 }
684
685 fn write_account(
688 &mut self,
689 lamports: u64,
690 owner_offset: OwnerOffset,
691 account_data: &[u8],
692 executable: bool,
693 rent_epoch: Option<Epoch>,
694 ) -> TieredStorageResult<usize> {
695 let optional_fields = AccountMetaOptionalFields { rent_epoch };
696
697 let mut flags = AccountMetaFlags::new_from(&optional_fields);
698 flags.set_executable(executable);
699
700 let padding_len = padding_bytes(account_data.len());
701 let meta = HotAccountMeta::new()
702 .with_lamports(lamports)
703 .with_owner_offset(owner_offset)
704 .with_account_data_size(account_data.len() as u64)
705 .with_account_data_padding(padding_len)
706 .with_flags(&flags);
707
708 let mut stored_size = 0;
709
710 stored_size += self.storage.write_pod(&meta)?;
711 stored_size += self.storage.write_bytes(account_data)?;
712 stored_size += self
713 .storage
714 .write_bytes(&PADDING_BUFFER[0..(padding_len as usize)])?;
715 stored_size += write_optional_fields(&mut self.storage, &optional_fields)?;
716
717 Ok(stored_size)
718 }
719
720 pub fn write_accounts<'a>(
724 &mut self,
725 accounts: &impl StorableAccounts<'a>,
726 skip: usize,
727 ) -> TieredStorageResult<StoredAccountsInfo> {
728 let mut footer = new_hot_footer();
729 let mut index = vec![];
730 let mut owners_table = OwnersTable::default();
731 let mut cursor = 0;
732 let mut address_range = AccountAddressRange::default();
733
734 let len = accounts.len();
735 let total_input_accounts = len.saturating_sub(skip);
736 let mut offsets = Vec::with_capacity(total_input_accounts);
737
738 for i in skip..len {
740 accounts.account_default_if_zero_lamport::<TieredStorageResult<()>>(i, |account| {
741 let index_entry = AccountIndexWriterEntry {
742 address: *account.pubkey(),
743 offset: HotAccountOffset::new(cursor)?,
744 };
745 address_range.update(account.pubkey());
746
747 let (lamports, owner, data, executable, rent_epoch) = {
750 (
751 account.lamports(),
752 account.owner(),
753 account.data(),
754 account.executable(),
755 (account.rent_epoch() != RENT_EXEMPT_RENT_EPOCH)
757 .then_some(account.rent_epoch()),
758 )
759 };
760 let owner_offset = owners_table.insert(owner);
761 cursor +=
762 self.write_account(lamports, owner_offset, data, executable, rent_epoch)?;
763
764 offsets.push(index.len());
770 index.push(index_entry);
771 Ok(())
772 })?;
773 }
774 footer.account_entry_count = total_input_accounts as u32;
775
776 assert!(cursor % HOT_BLOCK_ALIGNMENT == 0);
779 footer.index_block_offset = cursor as u64;
780 cursor += footer
781 .index_block_format
782 .write_index_block(&mut self.storage, &index)?;
783 if cursor % HOT_BLOCK_ALIGNMENT != 0 {
784 assert_eq!(cursor % HOT_BLOCK_ALIGNMENT, 4);
788 cursor += self.storage.write_pod(&0u32)?;
789 }
790
791 assert!(cursor % HOT_BLOCK_ALIGNMENT == 0);
793 footer.owners_block_offset = cursor as u64;
794 footer.owner_count = owners_table.len() as u32;
795 cursor += footer
796 .owners_block_format
797 .write_owners_block(&mut self.storage, &owners_table)?;
798
799 footer.min_account_address = address_range.min;
801 footer.max_account_address = address_range.max;
802 cursor += footer.write_footer_block(&mut self.storage)?;
803
804 Ok(StoredAccountsInfo {
805 offsets,
806 size: cursor,
807 })
808 }
809
810 pub fn flush(&mut self) -> TieredStorageResult<()> {
812 self.storage
813 .0
814 .flush()
815 .map_err(TieredStorageError::FlushHotWriter)
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use {
822 super::*,
823 crate::tiered_storage::{
824 byte_block::ByteBlockWriter,
825 file::{TieredStorageMagicNumber, TieredWritableFile},
826 footer::{AccountBlockFormat, AccountMetaFormat, TieredStorageFooter, FOOTER_SIZE},
827 hot::{HotAccountMeta, HotStorageReader},
828 index::{AccountIndexWriterEntry, IndexBlockFormat, IndexOffset},
829 meta::{AccountMetaFlags, AccountMetaOptionalFields, TieredAccountMeta},
830 owners::{OwnersBlockFormat, OwnersTable},
831 test_utils::{create_test_account, verify_test_account},
832 },
833 assert_matches::assert_matches,
834 memoffset::offset_of,
835 rand::{seq::SliceRandom, Rng},
836 solana_pubkey::Pubkey,
837 solana_sdk::{
838 account::ReadableAccount, hash::Hash, slot_history::Slot, stake_history::Epoch,
839 },
840 std::path::PathBuf,
841 tempfile::TempDir,
842 };
843
844 struct WriteTestFileInfo {
846 metas: Vec<HotAccountMeta>,
848 addresses: Vec<Pubkey>,
850 owners: Vec<Pubkey>,
852 datas: Vec<Vec<u8>>,
854 file_path: PathBuf,
856 temp_dir: TempDir,
858 }
859
860 fn write_test_file(num_accounts: usize, num_owners: usize) -> WriteTestFileInfo {
862 let temp_dir = TempDir::new().unwrap();
864 let file_path = temp_dir.path().join("test");
865
866 let mut rng = rand::thread_rng();
867
868 let owners: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
870 .take(num_owners)
871 .collect();
872
873 let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
875 .take(num_accounts)
876 .collect();
877
878 let datas: Vec<_> = (0..num_accounts)
880 .map(|i| vec![i as u8; rng.gen_range(0..4096)])
881 .collect();
882
883 let metas: Vec<_> = (0..num_accounts)
885 .map(|i| {
886 HotAccountMeta::new()
887 .with_lamports(rng.gen())
888 .with_owner_offset(OwnerOffset(rng.gen_range(0..num_owners) as u32))
889 .with_account_data_padding(padding_bytes(datas[i].len()))
890 })
891 .collect();
892
893 let mut footer = TieredStorageFooter {
894 account_meta_format: AccountMetaFormat::Hot,
895 account_entry_count: num_accounts as u32,
896 owner_count: num_owners as u32,
897 ..TieredStorageFooter::default()
898 };
899
900 {
902 let mut file = TieredWritableFile::new(&file_path).unwrap();
903 let mut current_offset = 0;
904
905 let padding_buffer = [0u8; HOT_ACCOUNT_ALIGNMENT];
907 let index_writer_entries: Vec<_> = metas
908 .iter()
909 .zip(datas.iter())
910 .zip(addresses.iter())
911 .map(|((meta, data), address)| {
912 let prev_offset = current_offset;
913 current_offset += file.write_pod(meta).unwrap();
914 current_offset += file.write_bytes(data).unwrap();
915 current_offset += file
916 .write_bytes(&padding_buffer[0..padding_bytes(data.len()) as usize])
917 .unwrap();
918 AccountIndexWriterEntry {
919 address: *address,
920 offset: HotAccountOffset::new(prev_offset).unwrap(),
921 }
922 })
923 .collect();
924
925 footer.index_block_offset = current_offset as u64;
927 current_offset += footer
928 .index_block_format
929 .write_index_block(&mut file, &index_writer_entries)
930 .unwrap();
931
932 footer.owners_block_offset = current_offset as u64;
934 let mut owners_table = OwnersTable::default();
935 owners.iter().for_each(|owner_address| {
936 owners_table.insert(owner_address);
937 });
938 footer
939 .owners_block_format
940 .write_owners_block(&mut file, &owners_table)
941 .unwrap();
942
943 footer.write_footer_block(&mut file).unwrap();
944 }
945
946 WriteTestFileInfo {
947 metas,
948 addresses,
949 owners,
950 datas,
951 file_path,
952 temp_dir,
953 }
954 }
955
956 #[test]
957 fn test_hot_account_meta_layout() {
958 assert_eq!(offset_of!(HotAccountMeta, lamports), 0x00);
959 assert_eq!(offset_of!(HotAccountMeta, packed_fields), 0x08);
960 assert_eq!(offset_of!(HotAccountMeta, flags), 0x0C);
961 assert_eq!(std::mem::size_of::<HotAccountMeta>(), 16);
962 }
963
964 #[test]
965 fn test_packed_fields() {
966 const TEST_PADDING: u8 = 7;
967 const TEST_OWNER_OFFSET: u32 = 0x1fff_ef98;
968 let mut packed_fields = HotMetaPackedFields::default();
969 packed_fields.set_padding(TEST_PADDING);
970 packed_fields.set_owner_offset(TEST_OWNER_OFFSET);
971 assert_eq!(packed_fields.padding(), TEST_PADDING);
972 assert_eq!(packed_fields.owner_offset(), TEST_OWNER_OFFSET);
973 }
974
975 #[test]
976 fn test_packed_fields_max_values() {
977 let mut packed_fields = HotMetaPackedFields::default();
978 packed_fields.set_padding(MAX_HOT_PADDING);
979 packed_fields.set_owner_offset(MAX_HOT_OWNER_OFFSET.0);
980 assert_eq!(packed_fields.padding(), MAX_HOT_PADDING);
981 assert_eq!(packed_fields.owner_offset(), MAX_HOT_OWNER_OFFSET.0);
982 }
983
984 #[test]
985 fn test_hot_meta_max_values() {
986 let meta = HotAccountMeta::new()
987 .with_account_data_padding(MAX_HOT_PADDING)
988 .with_owner_offset(MAX_HOT_OWNER_OFFSET);
989
990 assert_eq!(meta.account_data_padding(), MAX_HOT_PADDING);
991 assert_eq!(meta.owner_offset(), MAX_HOT_OWNER_OFFSET);
992 }
993
994 #[test]
995 fn test_max_hot_account_offset() {
996 assert_matches!(HotAccountOffset::new(0), Ok(_));
997 assert_matches!(HotAccountOffset::new(MAX_HOT_ACCOUNT_OFFSET), Ok(_));
998 }
999
1000 #[test]
1001 fn test_max_hot_account_offset_out_of_bounds() {
1002 assert_matches!(
1003 HotAccountOffset::new(MAX_HOT_ACCOUNT_OFFSET + HOT_ACCOUNT_ALIGNMENT),
1004 Err(TieredStorageError::OffsetOutOfBounds(_, _))
1005 );
1006 }
1007
1008 #[test]
1009 fn test_max_hot_account_offset_alignment_error() {
1010 assert_matches!(
1011 HotAccountOffset::new(HOT_ACCOUNT_ALIGNMENT - 1),
1012 Err(TieredStorageError::OffsetAlignmentError(_, _))
1013 );
1014 }
1015
1016 #[test]
1017 #[should_panic(expected = "padding exceeds MAX_HOT_PADDING")]
1018 fn test_hot_meta_padding_exceeds_limit() {
1019 HotAccountMeta::new().with_account_data_padding(MAX_HOT_PADDING + 1);
1020 }
1021
1022 #[test]
1023 #[should_panic(expected = "owner_offset exceeds MAX_HOT_OWNER_OFFSET")]
1024 fn test_hot_meta_owner_offset_exceeds_limit() {
1025 HotAccountMeta::new().with_owner_offset(OwnerOffset(MAX_HOT_OWNER_OFFSET.0 + 1));
1026 }
1027
1028 #[test]
1029 fn test_hot_account_meta() {
1030 const TEST_LAMPORTS: u64 = 2314232137;
1031 const TEST_PADDING: u8 = 5;
1032 const TEST_OWNER_OFFSET: OwnerOffset = OwnerOffset(0x1fef_1234);
1033 const TEST_RENT_EPOCH: Epoch = 7;
1034
1035 let optional_fields = AccountMetaOptionalFields {
1036 rent_epoch: Some(TEST_RENT_EPOCH),
1037 };
1038
1039 let flags = AccountMetaFlags::new_from(&optional_fields);
1040 let meta = HotAccountMeta::new()
1041 .with_lamports(TEST_LAMPORTS)
1042 .with_account_data_padding(TEST_PADDING)
1043 .with_owner_offset(TEST_OWNER_OFFSET)
1044 .with_flags(&flags);
1045
1046 assert_eq!(meta.lamports(), TEST_LAMPORTS);
1047 assert_eq!(meta.account_data_padding(), TEST_PADDING);
1048 assert_eq!(meta.owner_offset(), TEST_OWNER_OFFSET);
1049 assert_eq!(*meta.flags(), flags);
1050 }
1051
1052 #[test]
1053 fn test_hot_account_meta_full() {
1054 let account_data = [11u8; 83];
1055 let padding = [0u8; 5];
1056
1057 const TEST_LAMPORT: u64 = 2314232137;
1058 const OWNER_OFFSET: u32 = 0x1fef_1234;
1059 const TEST_RENT_EPOCH: Epoch = 7;
1060
1061 let optional_fields = AccountMetaOptionalFields {
1062 rent_epoch: Some(TEST_RENT_EPOCH),
1063 };
1064
1065 let flags = AccountMetaFlags::new_from(&optional_fields);
1066 let expected_meta = HotAccountMeta::new()
1067 .with_lamports(TEST_LAMPORT)
1068 .with_account_data_padding(padding.len().try_into().unwrap())
1069 .with_owner_offset(OwnerOffset(OWNER_OFFSET))
1070 .with_flags(&flags);
1071
1072 let mut writer = ByteBlockWriter::new(AccountBlockFormat::AlignedRaw);
1073 writer.write_pod(&expected_meta).unwrap();
1074 unsafe {
1076 writer.write_type(&account_data).unwrap();
1077 writer.write_type(&padding).unwrap();
1078 }
1079 writer.write_optional_fields(&optional_fields).unwrap();
1080 let buffer = writer.finish().unwrap();
1081
1082 let meta = byte_block::read_pod::<HotAccountMeta>(&buffer, 0).unwrap();
1083 assert_eq!(expected_meta, *meta);
1084 assert!(meta.flags().has_rent_epoch());
1085 assert_eq!(meta.account_data_padding() as usize, padding.len());
1086
1087 let account_block = &buffer[std::mem::size_of::<HotAccountMeta>()..];
1088 assert_eq!(
1089 meta.optional_fields_offset(account_block),
1090 account_block
1091 .len()
1092 .saturating_sub(AccountMetaOptionalFields::size_from_flags(&flags))
1093 );
1094 assert_eq!(account_data.len(), meta.account_data_size(account_block));
1095 assert_eq!(account_data, meta.account_data(account_block));
1096 assert_eq!(meta.rent_epoch(account_block), optional_fields.rent_epoch);
1097 }
1098
1099 #[test]
1100 fn test_hot_storage_footer() {
1101 let temp_dir = TempDir::new().unwrap();
1103 let path = temp_dir.path().join("test_hot_storage_footer");
1104 let expected_footer = TieredStorageFooter {
1105 account_meta_format: AccountMetaFormat::Hot,
1106 owners_block_format: OwnersBlockFormat::AddressesOnly,
1107 index_block_format: IndexBlockFormat::AddressesThenOffsets,
1108 account_block_format: AccountBlockFormat::AlignedRaw,
1109 account_entry_count: 300,
1110 account_meta_entry_size: 16,
1111 account_block_size: 4096,
1112 owner_count: 250,
1113 owner_entry_size: 32,
1114 index_block_offset: 1069600,
1115 owners_block_offset: 1081200,
1116 hash: Hash::new_unique(),
1117 min_account_address: Pubkey::default(),
1118 max_account_address: Pubkey::new_unique(),
1119 footer_size: FOOTER_SIZE as u64,
1120 format_version: 1,
1121 };
1122
1123 {
1124 let mut file = TieredWritableFile::new(&path).unwrap();
1125 expected_footer.write_footer_block(&mut file).unwrap();
1126 }
1127
1128 {
1131 let file = TieredReadableFile::new(&path).unwrap();
1132 let hot_storage = HotStorageReader::new(file).unwrap();
1133 assert_eq!(expected_footer, *hot_storage.footer());
1134 }
1135 }
1136
1137 #[test]
1138 fn test_hot_storage_get_account_meta_from_offset() {
1139 let temp_dir = TempDir::new().unwrap();
1141 let path = temp_dir.path().join("test_hot_storage_footer");
1142
1143 const NUM_ACCOUNTS: u32 = 10;
1144 let mut rng = rand::thread_rng();
1145
1146 let hot_account_metas: Vec<_> = (0..NUM_ACCOUNTS)
1147 .map(|_| {
1148 HotAccountMeta::new()
1149 .with_lamports(rng.gen_range(0..u64::MAX))
1150 .with_owner_offset(OwnerOffset(rng.gen_range(0..NUM_ACCOUNTS)))
1151 })
1152 .collect();
1153
1154 let account_offsets: Vec<_>;
1155 let mut footer = TieredStorageFooter {
1156 account_meta_format: AccountMetaFormat::Hot,
1157 account_entry_count: NUM_ACCOUNTS,
1158 ..TieredStorageFooter::default()
1159 };
1160 {
1161 let mut file = TieredWritableFile::new(&path).unwrap();
1162 let mut current_offset = 0;
1163
1164 account_offsets = hot_account_metas
1165 .iter()
1166 .map(|meta| {
1167 let prev_offset = current_offset;
1168 current_offset += file.write_pod(meta).unwrap();
1169 HotAccountOffset::new(prev_offset).unwrap()
1170 })
1171 .collect();
1172 footer.index_block_offset = current_offset as u64;
1175 footer.write_footer_block(&mut file).unwrap();
1176 }
1177
1178 let file = TieredReadableFile::new(&path).unwrap();
1179 let hot_storage = HotStorageReader::new(file).unwrap();
1180
1181 for (offset, expected_meta) in account_offsets.iter().zip(hot_account_metas.iter()) {
1182 let meta = hot_storage.get_account_meta_from_offset(*offset).unwrap();
1183 assert_eq!(meta, expected_meta);
1184 }
1185
1186 assert_eq!(&footer, hot_storage.footer());
1187 }
1188
1189 #[test]
1190 #[should_panic(expected = "would exceed accounts blocks offset boundary")]
1191 fn test_get_acount_meta_from_offset_out_of_bounds() {
1192 let temp_dir = TempDir::new().unwrap();
1194 let path = temp_dir
1195 .path()
1196 .join("test_get_acount_meta_from_offset_out_of_bounds");
1197
1198 let footer = TieredStorageFooter {
1199 account_meta_format: AccountMetaFormat::Hot,
1200 index_block_offset: 160,
1201 ..TieredStorageFooter::default()
1202 };
1203
1204 {
1205 let mut file = TieredWritableFile::new(&path).unwrap();
1206 footer.write_footer_block(&mut file).unwrap();
1207 }
1208
1209 let file = TieredReadableFile::new(&path).unwrap();
1210 let hot_storage = HotStorageReader::new(file).unwrap();
1211 let offset = HotAccountOffset::new(footer.index_block_offset as usize).unwrap();
1212 hot_storage.get_account_meta_from_offset(offset).unwrap();
1215 }
1216
1217 #[test]
1218 fn test_hot_storage_get_account_offset_and_address() {
1219 let temp_dir = TempDir::new().unwrap();
1221 let path = temp_dir
1222 .path()
1223 .join("test_hot_storage_get_account_offset_and_address");
1224 const NUM_ACCOUNTS: u32 = 10;
1225 let mut rng = rand::thread_rng();
1226
1227 let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
1228 .take(NUM_ACCOUNTS as usize)
1229 .collect();
1230
1231 let index_writer_entries: Vec<_> = addresses
1232 .iter()
1233 .map(|address| AccountIndexWriterEntry {
1234 address: *address,
1235 offset: HotAccountOffset::new(
1236 rng.gen_range(0..u32::MAX) as usize * HOT_ACCOUNT_ALIGNMENT,
1237 )
1238 .unwrap(),
1239 })
1240 .collect();
1241
1242 let mut footer = TieredStorageFooter {
1243 account_meta_format: AccountMetaFormat::Hot,
1244 account_entry_count: NUM_ACCOUNTS,
1245 index_block_offset: 0,
1248 ..TieredStorageFooter::default()
1249 };
1250 {
1251 let mut file = TieredWritableFile::new(&path).unwrap();
1252
1253 let cursor = footer
1254 .index_block_format
1255 .write_index_block(&mut file, &index_writer_entries)
1256 .unwrap();
1257 footer.owners_block_offset = cursor as u64;
1258 footer.write_footer_block(&mut file).unwrap();
1259 }
1260
1261 let file = TieredReadableFile::new(&path).unwrap();
1262 let hot_storage = HotStorageReader::new(file).unwrap();
1263 for (i, index_writer_entry) in index_writer_entries.iter().enumerate() {
1264 let account_offset = hot_storage
1265 .get_account_offset(IndexOffset(i as u32))
1266 .unwrap();
1267 assert_eq!(account_offset, index_writer_entry.offset);
1268
1269 let account_address = hot_storage
1270 .get_account_address(IndexOffset(i as u32))
1271 .unwrap();
1272 assert_eq!(account_address, &index_writer_entry.address);
1273 }
1274 }
1275
1276 #[test]
1277 fn test_hot_storage_get_owner_address() {
1278 let temp_dir = TempDir::new().unwrap();
1280 let path = temp_dir.path().join("test_hot_storage_get_owner_address");
1281 const NUM_OWNERS: usize = 10;
1282
1283 let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
1284 .take(NUM_OWNERS)
1285 .collect();
1286
1287 let footer = TieredStorageFooter {
1288 account_meta_format: AccountMetaFormat::Hot,
1289 owners_block_offset: 0,
1291 ..TieredStorageFooter::default()
1292 };
1293
1294 {
1295 let mut file = TieredWritableFile::new(&path).unwrap();
1296
1297 let mut owners_table = OwnersTable::default();
1298 addresses.iter().for_each(|owner_address| {
1299 owners_table.insert(owner_address);
1300 });
1301 footer
1302 .owners_block_format
1303 .write_owners_block(&mut file, &owners_table)
1304 .unwrap();
1305
1306 footer.write_footer_block(&mut file).unwrap();
1309 }
1310
1311 let file = TieredReadableFile::new(&path).unwrap();
1312 let hot_storage = HotStorageReader::new(file).unwrap();
1313 for (i, address) in addresses.iter().enumerate() {
1314 assert_eq!(
1315 hot_storage
1316 .get_owner_address(OwnerOffset(i as u32))
1317 .unwrap(),
1318 address,
1319 );
1320 }
1321 }
1322
1323 #[test]
1324 fn test_account_matches_owners() {
1325 let temp_dir = TempDir::new().unwrap();
1327 let path = temp_dir.path().join("test_hot_storage_get_owner_address");
1328 const NUM_OWNERS: u32 = 10;
1329
1330 let owner_addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
1331 .take(NUM_OWNERS as usize)
1332 .collect();
1333
1334 const NUM_ACCOUNTS: u32 = 30;
1335 let mut rng = rand::thread_rng();
1336
1337 let hot_account_metas: Vec<_> = std::iter::repeat_with({
1338 || {
1339 HotAccountMeta::new()
1340 .with_lamports(rng.gen_range(1..u64::MAX))
1341 .with_owner_offset(OwnerOffset(rng.gen_range(0..NUM_OWNERS)))
1342 }
1343 })
1344 .take(NUM_ACCOUNTS as usize)
1345 .collect();
1346 let mut footer = TieredStorageFooter {
1347 account_meta_format: AccountMetaFormat::Hot,
1348 account_entry_count: NUM_ACCOUNTS,
1349 owner_count: NUM_OWNERS,
1350 ..TieredStorageFooter::default()
1351 };
1352 let account_offsets: Vec<_>;
1353
1354 {
1355 let mut file = TieredWritableFile::new(&path).unwrap();
1356 let mut current_offset = 0;
1357
1358 account_offsets = hot_account_metas
1359 .iter()
1360 .map(|meta| {
1361 let prev_offset = current_offset;
1362 current_offset += file.write_pod(meta).unwrap();
1363 HotAccountOffset::new(prev_offset).unwrap()
1364 })
1365 .collect();
1366 footer.index_block_offset = current_offset as u64;
1367 footer.owners_block_offset = footer.index_block_offset;
1371
1372 let mut owners_table = OwnersTable::default();
1373 owner_addresses.iter().for_each(|owner_address| {
1374 owners_table.insert(owner_address);
1375 });
1376 footer
1377 .owners_block_format
1378 .write_owners_block(&mut file, &owners_table)
1379 .unwrap();
1380
1381 footer.write_footer_block(&mut file).unwrap();
1384 }
1385
1386 let file = TieredReadableFile::new(&path).unwrap();
1387 let hot_storage = HotStorageReader::new(file).unwrap();
1388
1389 let mut owner_candidates = owner_addresses.clone();
1391 owner_candidates.shuffle(&mut rng);
1392
1393 for (account_offset, account_meta) in account_offsets.iter().zip(hot_account_metas.iter()) {
1394 let index = hot_storage
1395 .account_matches_owners(*account_offset, &owner_candidates)
1396 .unwrap();
1397 assert_eq!(
1398 owner_candidates[index],
1399 owner_addresses[account_meta.owner_offset().0 as usize]
1400 );
1401 }
1402
1403 const NUM_UNMATCHED_OWNERS: usize = 20;
1405 let unmatched_candidates: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
1406 .take(NUM_UNMATCHED_OWNERS)
1407 .collect();
1408
1409 for account_offset in account_offsets.iter() {
1410 assert_eq!(
1411 hot_storage.account_matches_owners(*account_offset, &unmatched_candidates),
1412 Err(MatchAccountOwnerError::NoMatch)
1413 );
1414 }
1415
1416 owner_candidates.extend(unmatched_candidates);
1419 owner_candidates.shuffle(&mut rng);
1420
1421 for (account_offset, account_meta) in account_offsets.iter().zip(hot_account_metas.iter()) {
1422 let index = hot_storage
1423 .account_matches_owners(*account_offset, &owner_candidates)
1424 .unwrap();
1425 assert_eq!(
1426 owner_candidates[index],
1427 owner_addresses[account_meta.owner_offset().0 as usize]
1428 );
1429 }
1430 }
1431
1432 #[test]
1433 fn test_get_stored_account_meta() {
1434 const NUM_ACCOUNTS: usize = 20;
1435 const NUM_OWNERS: usize = 10;
1436 let test_info = write_test_file(NUM_ACCOUNTS, NUM_OWNERS);
1437
1438 let file = TieredReadableFile::new(&test_info.file_path).unwrap();
1439 let hot_storage = HotStorageReader::new(file).unwrap();
1440
1441 for i in 0..NUM_ACCOUNTS {
1442 hot_storage
1443 .get_stored_account_meta_callback(IndexOffset(i as u32), |stored_account_meta| {
1444 assert_eq!(
1445 stored_account_meta.lamports(),
1446 test_info.metas[i].lamports()
1447 );
1448 assert_eq!(stored_account_meta.data().len(), test_info.datas[i].len());
1449 assert_eq!(stored_account_meta.data(), test_info.datas[i]);
1450 assert_eq!(
1451 *stored_account_meta.owner(),
1452 test_info.owners[test_info.metas[i].owner_offset().0 as usize]
1453 );
1454 assert_eq!(*stored_account_meta.pubkey(), test_info.addresses[i]);
1455 })
1456 .unwrap()
1457 .unwrap();
1458 }
1459 assert_matches!(
1462 hot_storage.get_stored_account_meta_callback(IndexOffset(NUM_ACCOUNTS as u32), |_| {
1463 panic!("unexpected");
1464 }),
1465 Ok(None)
1466 );
1467 }
1468
1469 #[test]
1470 fn test_get_account_shared_data() {
1471 const NUM_ACCOUNTS: usize = 20;
1472 const NUM_OWNERS: usize = 10;
1473 let test_info = write_test_file(NUM_ACCOUNTS, NUM_OWNERS);
1474
1475 let file = TieredReadableFile::new(&test_info.file_path).unwrap();
1476 let hot_storage = HotStorageReader::new(file).unwrap();
1477
1478 for i in 0..NUM_ACCOUNTS {
1479 let index_offset = IndexOffset(i as u32);
1480 let account = hot_storage
1481 .get_account_shared_data(index_offset)
1482 .unwrap()
1483 .unwrap();
1484
1485 assert_eq!(account.lamports(), test_info.metas[i].lamports());
1486 assert_eq!(account.data().len(), test_info.datas[i].len());
1487 assert_eq!(account.data(), test_info.datas[i]);
1488 assert_eq!(
1489 *account.owner(),
1490 test_info.owners[test_info.metas[i].owner_offset().0 as usize],
1491 );
1492 }
1493 assert_matches!(
1496 hot_storage.get_account_shared_data(IndexOffset(NUM_ACCOUNTS as u32)),
1497 Ok(None)
1498 );
1499 }
1500
1501 #[test]
1502 fn test_hot_storage_writer_twice_on_same_path() {
1503 let temp_dir = TempDir::new().unwrap();
1504 let path = temp_dir
1505 .path()
1506 .join("test_hot_storage_writer_twice_on_same_path");
1507
1508 assert_matches!(HotStorageWriter::new(&path), Ok(_));
1510 assert_matches!(HotStorageWriter::new(&path), Err(_));
1513 }
1514
1515 #[test]
1516 fn test_write_account_and_index_blocks() {
1517 let account_data_sizes = &[
1518 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000, 2000, 3000, 4000, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
1519 ];
1520
1521 let accounts: Vec<_> = account_data_sizes
1522 .iter()
1523 .map(|size| create_test_account(*size))
1524 .collect();
1525
1526 let account_refs: Vec<_> = accounts
1527 .iter()
1528 .map(|account| (&account.0.pubkey, &account.1))
1529 .collect();
1530
1531 let storable_accounts = (Slot::MAX, &account_refs[..]);
1533
1534 let temp_dir = TempDir::new().unwrap();
1535 let path = temp_dir.path().join("test_write_account_and_index_blocks");
1536 let stored_accounts_info = {
1537 let mut writer = HotStorageWriter::new(&path).unwrap();
1538 let stored_accounts_info = writer.write_accounts(&storable_accounts, 0).unwrap();
1539 writer.flush().unwrap();
1540 stored_accounts_info
1541 };
1542
1543 let file = TieredReadableFile::new(&path).unwrap();
1544 let hot_storage = HotStorageReader::new(file).unwrap();
1545
1546 let num_accounts = account_data_sizes.len();
1547 for i in 0..num_accounts {
1548 hot_storage
1549 .get_stored_account_meta_callback(IndexOffset(i as u32), |stored_account_meta| {
1550 storable_accounts.account_default_if_zero_lamport(i, |account| {
1551 verify_test_account(
1552 &stored_account_meta,
1553 &account.to_account_shared_data(),
1554 account.pubkey(),
1555 );
1556 });
1557 })
1558 .unwrap()
1559 .unwrap();
1560 }
1561 assert_matches!(
1564 hot_storage.get_stored_account_meta_callback(IndexOffset(num_accounts as u32), |_| {
1565 panic!("unexpected");
1566 }),
1567 Ok(None)
1568 );
1569
1570 for offset in stored_accounts_info.offsets {
1571 hot_storage
1572 .get_stored_account_meta_callback(
1573 IndexOffset(offset as u32),
1574 |stored_account_meta| {
1575 storable_accounts.account_default_if_zero_lamport(offset, |account| {
1576 verify_test_account(
1577 &stored_account_meta,
1578 &account.to_account_shared_data(),
1579 account.pubkey(),
1580 );
1581 });
1582 },
1583 )
1584 .unwrap()
1585 .unwrap();
1586 }
1587
1588 let mut i = 0;
1590 hot_storage
1591 .scan_accounts(|stored_meta| {
1592 storable_accounts.account_default_if_zero_lamport(i, |account| {
1593 verify_test_account(
1594 &stored_meta,
1595 &account.to_account_shared_data(),
1596 account.pubkey(),
1597 );
1598 });
1599 i += 1;
1600 })
1601 .unwrap();
1602
1603 let footer = hot_storage.footer();
1604
1605 let expected_size = footer.owners_block_offset as usize
1606 + std::mem::size_of::<Pubkey>() * footer.owner_count as usize
1607 + std::mem::size_of::<TieredStorageFooter>()
1608 + std::mem::size_of::<TieredStorageMagicNumber>();
1609
1610 assert!(!hot_storage.is_empty());
1611 assert_eq!(expected_size, hot_storage.len());
1612 }
1613}