use {
crate::tiered_storage::{
error::TieredStorageError,
file::{TieredReadableFile, TieredStorageMagicNumber, TieredWritableFile},
index::IndexBlockFormat,
mmap_utils::{get_pod, get_type},
owners::OwnersBlockFormat,
TieredStorageResult,
},
bytemuck::Zeroable,
memmap2::Mmap,
num_enum::TryFromPrimitiveError,
solana_sdk::{hash::Hash, pubkey::Pubkey},
std::{mem, path::Path, ptr},
thiserror::Error,
};
pub const FOOTER_FORMAT_VERSION: u64 = 1;
pub const FOOTER_SIZE: usize =
mem::size_of::<TieredStorageFooter>() + mem::size_of::<TieredStorageMagicNumber>();
static_assertions::const_assert_eq!(mem::size_of::<TieredStorageFooter>(), 160);
pub const FOOTER_TAIL_SIZE: usize = 24;
#[repr(u16)]
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
PartialEq,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
pub enum AccountMetaFormat {
#[default]
Hot = 0,
}
#[repr(u16)]
#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
Hash,
PartialEq,
num_enum::IntoPrimitive,
num_enum::TryFromPrimitive,
)]
pub enum AccountBlockFormat {
#[default]
AlignedRaw = 0,
Lz4 = 1,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
pub struct TieredStorageFooter {
pub account_meta_format: AccountMetaFormat,
pub owners_block_format: OwnersBlockFormat,
pub index_block_format: IndexBlockFormat,
pub account_block_format: AccountBlockFormat,
pub account_entry_count: u32,
pub account_meta_entry_size: u32,
pub account_block_size: u64,
pub owner_count: u32,
pub owner_entry_size: u32,
pub index_block_offset: u64,
pub owners_block_offset: u64,
pub min_account_address: Pubkey,
pub max_account_address: Pubkey,
pub hash: Hash,
pub format_version: u64,
pub footer_size: u64,
}
const _: () = assert!(
std::mem::size_of::<TieredStorageFooter>()
== std::mem::size_of::<AccountMetaFormat>()
+ std::mem::size_of::<OwnersBlockFormat>()
+ std::mem::size_of::<IndexBlockFormat>()
+ std::mem::size_of::<AccountBlockFormat>()
+ std::mem::size_of::<u32>() + std::mem::size_of::<u32>() + std::mem::size_of::<u64>() + std::mem::size_of::<u32>() + std::mem::size_of::<u32>() + std::mem::size_of::<u64>() + std::mem::size_of::<u64>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Pubkey>() + std::mem::size_of::<Hash>() + std::mem::size_of::<u64>() + std::mem::size_of::<u64>(), "TieredStorageFooter cannot have any padding"
);
impl Default for TieredStorageFooter {
fn default() -> Self {
Self {
account_meta_format: AccountMetaFormat::default(),
owners_block_format: OwnersBlockFormat::default(),
index_block_format: IndexBlockFormat::default(),
account_block_format: AccountBlockFormat::default(),
account_entry_count: 0,
account_meta_entry_size: 0,
account_block_size: 0,
owner_count: 0,
owner_entry_size: 0,
index_block_offset: 0,
owners_block_offset: 0,
hash: Hash::new_unique(),
min_account_address: Pubkey::default(),
max_account_address: Pubkey::default(),
format_version: FOOTER_FORMAT_VERSION,
footer_size: FOOTER_SIZE as u64,
}
}
}
impl TieredStorageFooter {
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
let file = TieredReadableFile::new(path)?;
Self::new_from_footer_block(&file)
}
pub fn write_footer_block(&self, file: &mut TieredWritableFile) -> TieredStorageResult<usize> {
let mut bytes_written = 0;
bytes_written += unsafe { file.write_type(self)? };
bytes_written += file.write_pod(&TieredStorageMagicNumber::default())?;
Ok(bytes_written)
}
pub fn new_from_footer_block(file: &TieredReadableFile) -> TieredStorageResult<Self> {
file.seek_from_end(-(FOOTER_TAIL_SIZE as i64))?;
let mut footer_version: u64 = 0;
file.read_pod(&mut footer_version)?;
if footer_version != FOOTER_FORMAT_VERSION {
return Err(TieredStorageError::InvalidFooterVersion(footer_version));
}
let mut footer_size: u64 = 0;
file.read_pod(&mut footer_size)?;
if footer_size != FOOTER_SIZE as u64 {
return Err(TieredStorageError::InvalidFooterSize(
footer_size,
FOOTER_SIZE as u64,
));
}
let mut magic_number = TieredStorageMagicNumber::zeroed();
file.read_pod(&mut magic_number)?;
if magic_number != TieredStorageMagicNumber::default() {
return Err(TieredStorageError::MagicNumberMismatch(
TieredStorageMagicNumber::default().0,
magic_number.0,
));
}
let mut footer = Self::default();
file.seek_from_end(-(footer_size as i64))?;
unsafe { file.read_type(&mut footer)? };
Self::sanitize(&footer)?;
Ok(footer)
}
pub fn new_from_mmap(mmap: &Mmap) -> TieredStorageResult<&TieredStorageFooter> {
let offset = mmap.len().saturating_sub(FOOTER_TAIL_SIZE);
let (footer_version, offset) = get_pod::<u64>(mmap, offset)?;
if *footer_version != FOOTER_FORMAT_VERSION {
return Err(TieredStorageError::InvalidFooterVersion(*footer_version));
}
let (&footer_size, offset) = get_pod::<u64>(mmap, offset)?;
if footer_size != FOOTER_SIZE as u64 {
return Err(TieredStorageError::InvalidFooterSize(
footer_size,
FOOTER_SIZE as u64,
));
}
let (magic_number, _offset) = get_pod::<TieredStorageMagicNumber>(mmap, offset)?;
if *magic_number != TieredStorageMagicNumber::default() {
return Err(TieredStorageError::MagicNumberMismatch(
TieredStorageMagicNumber::default().0,
magic_number.0,
));
}
let footer_offset = mmap.len().saturating_sub(footer_size as usize);
let (footer, _offset) = unsafe { get_type::<TieredStorageFooter>(mmap, footer_offset)? };
Self::sanitize(footer)?;
Ok(footer)
}
fn sanitize(footer: &Self) -> Result<(), SanitizeFooterError> {
let account_meta_format_u16 =
unsafe { &*(ptr::from_ref(&footer.account_meta_format).cast::<u16>()) };
let owners_block_format_u16 =
unsafe { &*(ptr::from_ref(&footer.owners_block_format).cast::<u16>()) };
let index_block_format_u16 =
unsafe { &*(ptr::from_ref(&footer.index_block_format).cast::<u16>()) };
let account_block_format_u16 =
unsafe { &*(ptr::from_ref(&footer.account_block_format).cast::<u16>()) };
_ = AccountMetaFormat::try_from(*account_meta_format_u16)
.map_err(SanitizeFooterError::InvalidAccountMetaFormat)?;
_ = OwnersBlockFormat::try_from(*owners_block_format_u16)
.map_err(SanitizeFooterError::InvalidOwnersBlockFormat)?;
_ = IndexBlockFormat::try_from(*index_block_format_u16)
.map_err(SanitizeFooterError::InvalidIndexBlockFormat)?;
_ = AccountBlockFormat::try_from(*account_block_format_u16)
.map_err(SanitizeFooterError::InvalidAccountBlockFormat)?;
Ok(())
}
}
#[derive(Error, Debug)]
pub enum SanitizeFooterError {
#[error("invalid account meta format: {0}")]
InvalidAccountMetaFormat(#[from] TryFromPrimitiveError<AccountMetaFormat>),
#[error("invalid owners block format: {0}")]
InvalidOwnersBlockFormat(#[from] TryFromPrimitiveError<OwnersBlockFormat>),
#[error("invalid index block format: {0}")]
InvalidIndexBlockFormat(#[from] TryFromPrimitiveError<IndexBlockFormat>),
#[error("invalid account block format: {0}")]
InvalidAccountBlockFormat(#[from] TryFromPrimitiveError<AccountBlockFormat>),
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
append_vec::test_utils::get_append_vec_path, tiered_storage::file::TieredWritableFile,
},
memoffset::offset_of,
solana_sdk::hash::Hash,
};
#[test]
fn test_footer() {
let path = get_append_vec_path("test_file_footer");
let expected_footer = TieredStorageFooter {
account_meta_format: AccountMetaFormat::Hot,
owners_block_format: OwnersBlockFormat::AddressesOnly,
index_block_format: IndexBlockFormat::AddressesThenOffsets,
account_block_format: AccountBlockFormat::AlignedRaw,
account_entry_count: 300,
account_meta_entry_size: 24,
account_block_size: 4096,
owner_count: 250,
owner_entry_size: 32,
index_block_offset: 1069600,
owners_block_offset: 1081200,
hash: Hash::new_unique(),
min_account_address: Pubkey::default(),
max_account_address: Pubkey::new_unique(),
format_version: FOOTER_FORMAT_VERSION,
footer_size: FOOTER_SIZE as u64,
};
{
let mut file = TieredWritableFile::new(&path.path).unwrap();
expected_footer.write_footer_block(&mut file).unwrap();
}
{
let footer = TieredStorageFooter::new_from_path(&path.path).unwrap();
assert_eq!(expected_footer, footer);
}
}
#[test]
fn test_footer_layout() {
assert_eq!(offset_of!(TieredStorageFooter, account_meta_format), 0x00);
assert_eq!(offset_of!(TieredStorageFooter, owners_block_format), 0x02);
assert_eq!(offset_of!(TieredStorageFooter, index_block_format), 0x04);
assert_eq!(offset_of!(TieredStorageFooter, account_block_format), 0x06);
assert_eq!(offset_of!(TieredStorageFooter, account_entry_count), 0x08);
assert_eq!(
offset_of!(TieredStorageFooter, account_meta_entry_size),
0x0C
);
assert_eq!(offset_of!(TieredStorageFooter, account_block_size), 0x10);
assert_eq!(offset_of!(TieredStorageFooter, owner_count), 0x18);
assert_eq!(offset_of!(TieredStorageFooter, owner_entry_size), 0x1C);
assert_eq!(offset_of!(TieredStorageFooter, index_block_offset), 0x20);
assert_eq!(offset_of!(TieredStorageFooter, owners_block_offset), 0x28);
assert_eq!(offset_of!(TieredStorageFooter, min_account_address), 0x30);
assert_eq!(offset_of!(TieredStorageFooter, max_account_address), 0x50);
assert_eq!(offset_of!(TieredStorageFooter, hash), 0x70);
assert_eq!(offset_of!(TieredStorageFooter, format_version), 0x90);
assert_eq!(offset_of!(TieredStorageFooter, footer_size), 0x98);
}
#[test]
fn test_sanitize() {
{
let footer = TieredStorageFooter::default();
let result = TieredStorageFooter::sanitize(&footer);
assert!(result.is_ok());
}
{
let mut footer = TieredStorageFooter::default();
unsafe {
std::ptr::write(
ptr::from_mut(&mut footer.account_meta_format).cast::<u16>(),
0xBAD0,
);
}
let result = TieredStorageFooter::sanitize(&footer);
assert!(matches!(
result,
Err(SanitizeFooterError::InvalidAccountMetaFormat(_))
));
}
{
let mut footer = TieredStorageFooter::default();
unsafe {
std::ptr::write(
ptr::from_mut(&mut footer.owners_block_format).cast::<u16>(),
0xBAD0,
);
}
let result = TieredStorageFooter::sanitize(&footer);
assert!(matches!(
result,
Err(SanitizeFooterError::InvalidOwnersBlockFormat(_))
));
}
{
let mut footer = TieredStorageFooter::default();
unsafe {
std::ptr::write(
ptr::from_mut(&mut footer.index_block_format).cast::<u16>(),
0xBAD0,
);
}
let result = TieredStorageFooter::sanitize(&footer);
assert!(matches!(
result,
Err(SanitizeFooterError::InvalidIndexBlockFormat(_))
));
}
{
let mut footer = TieredStorageFooter::default();
unsafe {
std::ptr::write(
ptr::from_mut(&mut footer.account_block_format).cast::<u16>(),
0xBAD0,
);
}
let result = TieredStorageFooter::sanitize(&footer);
assert!(matches!(
result,
Err(SanitizeFooterError::InvalidAccountBlockFormat(_))
));
}
}
}