#![allow(dead_code)]
use {
crate::{
account_storage::meta::StoredMetaWriteVersion,
tiered_storage::{
byte_block,
footer::{
AccountBlockFormat, AccountMetaFormat, OwnersBlockFormat, TieredStorageFooter,
},
index::AccountIndexFormat,
meta::{AccountMetaFlags, AccountMetaOptionalFields, TieredAccountMeta},
TieredStorageFormat, TieredStorageResult,
},
},
memmap2::{Mmap, MmapOptions},
modular_bitfield::prelude::*,
solana_sdk::{hash::Hash, stake_history::Epoch},
std::{fs::OpenOptions, option::Option, path::Path},
};
pub const HOT_FORMAT: TieredStorageFormat = TieredStorageFormat {
meta_entry_size: std::mem::size_of::<HotAccountMeta>(),
account_meta_format: AccountMetaFormat::Hot,
owners_block_format: OwnersBlockFormat::LocalIndex,
account_index_format: AccountIndexFormat::AddressAndOffset,
account_block_format: AccountBlockFormat::AlignedRaw,
};
const MAX_HOT_PADDING: u8 = 7;
const MAX_HOT_OWNER_INDEX: u32 = (1 << 29) - 1;
#[bitfield(bits = 32)]
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
struct HotMetaPackedFields {
padding: B3,
owner_index: B29,
}
#[derive(Debug, PartialEq, Eq)]
#[repr(C)]
pub struct HotAccountMeta {
lamports: u64,
packed_fields: HotMetaPackedFields,
flags: AccountMetaFlags,
}
impl TieredAccountMeta for HotAccountMeta {
fn new() -> Self {
HotAccountMeta {
lamports: 0,
packed_fields: HotMetaPackedFields::default(),
flags: AccountMetaFlags::new(),
}
}
fn with_lamports(mut self, lamports: u64) -> Self {
self.lamports = lamports;
self
}
fn with_account_data_padding(mut self, padding: u8) -> Self {
if padding > MAX_HOT_PADDING {
panic!("padding exceeds MAX_HOT_PADDING");
}
self.packed_fields.set_padding(padding);
self
}
fn with_owner_index(mut self, owner_index: u32) -> Self {
if owner_index > MAX_HOT_OWNER_INDEX {
panic!("owner_index exceeds MAX_HOT_OWNER_INDEX");
}
self.packed_fields.set_owner_index(owner_index);
self
}
fn with_account_data_size(self, _account_data_size: u64) -> Self {
self
}
fn with_flags(mut self, flags: &AccountMetaFlags) -> Self {
self.flags = *flags;
self
}
fn lamports(&self) -> u64 {
self.lamports
}
fn account_data_padding(&self) -> u8 {
self.packed_fields.padding()
}
fn owner_index(&self) -> u32 {
self.packed_fields.owner_index()
}
fn flags(&self) -> &AccountMetaFlags {
&self.flags
}
fn supports_shared_account_block() -> bool {
false
}
fn rent_epoch(&self, account_block: &[u8]) -> Option<Epoch> {
self.flags()
.has_rent_epoch()
.then(|| {
let offset = self.optional_fields_offset(account_block)
+ AccountMetaOptionalFields::rent_epoch_offset(self.flags());
byte_block::read_type::<Epoch>(account_block, offset).copied()
})
.flatten()
}
fn account_hash<'a>(&self, account_block: &'a [u8]) -> Option<&'a Hash> {
self.flags()
.has_account_hash()
.then(|| {
let offset = self.optional_fields_offset(account_block)
+ AccountMetaOptionalFields::account_hash_offset(self.flags());
byte_block::read_type::<Hash>(account_block, offset)
})
.flatten()
}
fn write_version(&self, account_block: &[u8]) -> Option<StoredMetaWriteVersion> {
self.flags
.has_write_version()
.then(|| {
let offset = self.optional_fields_offset(account_block)
+ AccountMetaOptionalFields::write_version_offset(self.flags());
byte_block::read_type::<StoredMetaWriteVersion>(account_block, offset).copied()
})
.flatten()
}
fn optional_fields_offset(&self, account_block: &[u8]) -> usize {
account_block
.len()
.saturating_sub(AccountMetaOptionalFields::size_from_flags(&self.flags))
}
fn account_data_size(&self, account_block: &[u8]) -> usize {
self.optional_fields_offset(account_block)
.saturating_sub(self.account_data_padding() as usize)
}
fn account_data<'a>(&self, account_block: &'a [u8]) -> &'a [u8] {
&account_block[..self.account_data_size(account_block)]
}
}
#[derive(Debug)]
pub struct HotStorageReader {
mmap: Mmap,
footer: TieredStorageFooter,
}
impl HotStorageReader {
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
let file = OpenOptions::new().read(true).open(path)?;
let mmap = unsafe { MmapOptions::new().map(&file)? };
let footer = TieredStorageFooter::new_from_mmap(&mmap)?.clone();
Ok(Self { mmap, footer })
}
pub fn footer(&self) -> &TieredStorageFooter {
&self.footer
}
pub fn num_accounts(&self) -> usize {
self.footer.account_entry_count as usize
}
}
#[cfg(test)]
pub mod tests {
use {
super::*,
crate::{
account_storage::meta::StoredMetaWriteVersion,
tiered_storage::{
byte_block::ByteBlockWriter,
footer::AccountBlockFormat,
meta::{AccountMetaFlags, AccountMetaOptionalFields, TieredAccountMeta},
},
},
::solana_sdk::{hash::Hash, stake_history::Epoch},
memoffset::offset_of,
};
#[test]
fn test_hot_account_meta_layout() {
assert_eq!(offset_of!(HotAccountMeta, lamports), 0x00);
assert_eq!(offset_of!(HotAccountMeta, packed_fields), 0x08);
assert_eq!(offset_of!(HotAccountMeta, flags), 0x0C);
assert_eq!(std::mem::size_of::<HotAccountMeta>(), 16);
}
#[test]
fn test_packed_fields() {
const TEST_PADDING: u8 = 7;
const TEST_OWNER_INDEX: u32 = 0x1fff_ef98;
let mut packed_fields = HotMetaPackedFields::default();
packed_fields.set_padding(TEST_PADDING);
packed_fields.set_owner_index(TEST_OWNER_INDEX);
assert_eq!(packed_fields.padding(), TEST_PADDING);
assert_eq!(packed_fields.owner_index(), TEST_OWNER_INDEX);
}
#[test]
fn test_packed_fields_max_values() {
let mut packed_fields = HotMetaPackedFields::default();
packed_fields.set_padding(MAX_HOT_PADDING);
packed_fields.set_owner_index(MAX_HOT_OWNER_INDEX);
assert_eq!(packed_fields.padding(), MAX_HOT_PADDING);
assert_eq!(packed_fields.owner_index(), MAX_HOT_OWNER_INDEX);
}
#[test]
fn test_hot_meta_max_values() {
let meta = HotAccountMeta::new()
.with_account_data_padding(MAX_HOT_PADDING)
.with_owner_index(MAX_HOT_OWNER_INDEX);
assert_eq!(meta.account_data_padding(), MAX_HOT_PADDING);
assert_eq!(meta.owner_index(), MAX_HOT_OWNER_INDEX);
}
#[test]
#[should_panic(expected = "padding exceeds MAX_HOT_PADDING")]
fn test_hot_meta_padding_exceeds_limit() {
HotAccountMeta::new().with_account_data_padding(MAX_HOT_PADDING + 1);
}
#[test]
#[should_panic(expected = "owner_index exceeds MAX_HOT_OWNER_INDEX")]
fn test_hot_meta_owner_index_exceeds_limit() {
HotAccountMeta::new().with_owner_index(MAX_HOT_OWNER_INDEX + 1);
}
#[test]
fn test_hot_account_meta() {
const TEST_LAMPORTS: u64 = 2314232137;
const TEST_PADDING: u8 = 5;
const TEST_OWNER_INDEX: u32 = 0x1fef_1234;
const TEST_RENT_EPOCH: Epoch = 7;
let optional_fields = AccountMetaOptionalFields {
rent_epoch: Some(TEST_RENT_EPOCH),
account_hash: Some(Hash::new_unique()),
write_version: None,
};
let flags = AccountMetaFlags::new_from(&optional_fields);
let meta = HotAccountMeta::new()
.with_lamports(TEST_LAMPORTS)
.with_account_data_padding(TEST_PADDING)
.with_owner_index(TEST_OWNER_INDEX)
.with_flags(&flags);
assert_eq!(meta.lamports(), TEST_LAMPORTS);
assert_eq!(meta.account_data_padding(), TEST_PADDING);
assert_eq!(meta.owner_index(), TEST_OWNER_INDEX);
assert_eq!(*meta.flags(), flags);
}
#[test]
fn test_hot_account_meta_full() {
let account_data = [11u8; 83];
let padding = [0u8; 5];
const TEST_LAMPORT: u64 = 2314232137;
const OWNER_INDEX: u32 = 0x1fef_1234;
const TEST_RENT_EPOCH: Epoch = 7;
const TEST_WRITE_VERSION: StoredMetaWriteVersion = 0;
let optional_fields = AccountMetaOptionalFields {
rent_epoch: Some(TEST_RENT_EPOCH),
account_hash: Some(Hash::new_unique()),
write_version: Some(TEST_WRITE_VERSION),
};
let flags = AccountMetaFlags::new_from(&optional_fields);
let expected_meta = HotAccountMeta::new()
.with_lamports(TEST_LAMPORT)
.with_account_data_padding(padding.len().try_into().unwrap())
.with_owner_index(OWNER_INDEX)
.with_flags(&flags);
let mut writer = ByteBlockWriter::new(AccountBlockFormat::AlignedRaw);
writer.write_type(&expected_meta).unwrap();
writer.write_type(&account_data).unwrap();
writer.write_type(&padding).unwrap();
writer.write_optional_fields(&optional_fields).unwrap();
let buffer = writer.finish().unwrap();
let meta = byte_block::read_type::<HotAccountMeta>(&buffer, 0).unwrap();
assert_eq!(expected_meta, *meta);
assert!(meta.flags().has_rent_epoch());
assert!(meta.flags().has_account_hash());
assert!(meta.flags().has_write_version());
assert_eq!(meta.account_data_padding() as usize, padding.len());
let account_block = &buffer[std::mem::size_of::<HotAccountMeta>()..];
assert_eq!(
meta.optional_fields_offset(account_block),
account_block
.len()
.saturating_sub(AccountMetaOptionalFields::size_from_flags(&flags))
);
assert_eq!(account_data.len(), meta.account_data_size(account_block));
assert_eq!(account_data, meta.account_data(account_block));
assert_eq!(meta.rent_epoch(account_block), optional_fields.rent_epoch);
assert_eq!(
*(meta.account_hash(account_block).unwrap()),
optional_fields.account_hash.unwrap()
);
assert_eq!(
meta.write_version(account_block),
optional_fields.write_version
);
}
}