1use {
2 crate::tiered_storage::{
3 error::TieredStorageError,
4 file::{TieredReadableFile, TieredStorageMagicNumber, TieredWritableFile},
5 index::IndexBlockFormat,
6 mmap_utils::{get_pod, get_type},
7 owners::OwnersBlockFormat,
8 TieredStorageResult,
9 },
10 bytemuck::Zeroable,
11 memmap2::Mmap,
12 num_enum::TryFromPrimitiveError,
13 solana_sdk::{hash::Hash, pubkey::Pubkey},
14 std::{mem, path::Path, ptr},
15 thiserror::Error,
16};
17
18pub const FOOTER_FORMAT_VERSION: u64 = 1;
19
20pub const FOOTER_SIZE: usize =
22 mem::size_of::<TieredStorageFooter>() + mem::size_of::<TieredStorageMagicNumber>();
23static_assertions::const_assert_eq!(mem::size_of::<TieredStorageFooter>(), 160);
24
25pub const FOOTER_TAIL_SIZE: usize = 24;
28
29#[repr(u16)]
30#[derive(
31 Clone,
32 Copy,
33 Debug,
34 Default,
35 Eq,
36 Hash,
37 PartialEq,
38 num_enum::IntoPrimitive,
39 num_enum::TryFromPrimitive,
40)]
41pub enum AccountMetaFormat {
42 #[default]
43 Hot = 0,
44 }
47
48#[repr(u16)]
49#[derive(
50 Clone,
51 Copy,
52 Debug,
53 Default,
54 Eq,
55 Hash,
56 PartialEq,
57 num_enum::IntoPrimitive,
58 num_enum::TryFromPrimitive,
59)]
60pub enum AccountBlockFormat {
61 #[default]
62 AlignedRaw = 0,
63 Lz4 = 1,
64}
65
66#[derive(Debug, PartialEq, Eq, Clone, Copy)]
67#[repr(C)]
68pub struct TieredStorageFooter {
69 pub account_meta_format: AccountMetaFormat,
72 pub owners_block_format: OwnersBlockFormat,
74 pub index_block_format: IndexBlockFormat,
76 pub account_block_format: AccountBlockFormat,
78
79 pub account_entry_count: u32,
82 pub account_meta_entry_size: u32,
84 pub account_block_size: u64,
90
91 pub owner_count: u32,
94 pub owner_entry_size: u32,
96
97 pub index_block_offset: u64,
101 pub owners_block_offset: u64,
103
104 pub min_account_address: Pubkey,
107 pub max_account_address: Pubkey,
109
110 pub hash: Hash,
112
113 pub format_version: u64,
115 pub footer_size: u64,
119 }
123
124const _: () = assert!(
128 std::mem::size_of::<TieredStorageFooter>()
129 == std::mem::size_of::<AccountMetaFormat>()
130 + std::mem::size_of::<OwnersBlockFormat>()
131 + std::mem::size_of::<IndexBlockFormat>()
132 + std::mem::size_of::<AccountBlockFormat>()
133 + 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"
146);
147
148impl Default for TieredStorageFooter {
149 fn default() -> Self {
150 Self {
151 account_meta_format: AccountMetaFormat::default(),
152 owners_block_format: OwnersBlockFormat::default(),
153 index_block_format: IndexBlockFormat::default(),
154 account_block_format: AccountBlockFormat::default(),
155 account_entry_count: 0,
156 account_meta_entry_size: 0,
157 account_block_size: 0,
158 owner_count: 0,
159 owner_entry_size: 0,
160 index_block_offset: 0,
161 owners_block_offset: 0,
162 hash: Hash::new_unique(),
163 min_account_address: Pubkey::default(),
164 max_account_address: Pubkey::default(),
165 format_version: FOOTER_FORMAT_VERSION,
166 footer_size: FOOTER_SIZE as u64,
167 }
168 }
169}
170
171impl TieredStorageFooter {
172 pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
173 let file = TieredReadableFile::new(path)?;
174 Self::new_from_footer_block(&file)
175 }
176
177 pub fn write_footer_block(&self, file: &mut TieredWritableFile) -> TieredStorageResult<usize> {
178 let mut bytes_written = 0;
179
180 bytes_written += unsafe { file.write_type(self)? };
182 bytes_written += file.write_pod(&TieredStorageMagicNumber::default())?;
183
184 Ok(bytes_written)
185 }
186
187 pub fn new_from_footer_block(file: &TieredReadableFile) -> TieredStorageResult<Self> {
188 file.seek_from_end(-(FOOTER_TAIL_SIZE as i64))?;
189
190 let mut footer_version: u64 = 0;
191 file.read_pod(&mut footer_version)?;
192 if footer_version != FOOTER_FORMAT_VERSION {
193 return Err(TieredStorageError::InvalidFooterVersion(footer_version));
194 }
195
196 let mut footer_size: u64 = 0;
197 file.read_pod(&mut footer_size)?;
198 if footer_size != FOOTER_SIZE as u64 {
199 return Err(TieredStorageError::InvalidFooterSize(
200 footer_size,
201 FOOTER_SIZE as u64,
202 ));
203 }
204
205 let mut magic_number = TieredStorageMagicNumber::zeroed();
206 file.read_pod(&mut magic_number)?;
207 if magic_number != TieredStorageMagicNumber::default() {
208 return Err(TieredStorageError::MagicNumberMismatch(
209 TieredStorageMagicNumber::default().0,
210 magic_number.0,
211 ));
212 }
213
214 let mut footer = Self::default();
215 file.seek_from_end(-(footer_size as i64))?;
216 unsafe { file.read_type(&mut footer)? };
219 Self::sanitize(&footer)?;
220
221 Ok(footer)
222 }
223
224 pub fn new_from_mmap(mmap: &Mmap) -> TieredStorageResult<&TieredStorageFooter> {
225 let offset = mmap.len().saturating_sub(FOOTER_TAIL_SIZE);
226
227 let (footer_version, offset) = get_pod::<u64>(mmap, offset)?;
228 if *footer_version != FOOTER_FORMAT_VERSION {
229 return Err(TieredStorageError::InvalidFooterVersion(*footer_version));
230 }
231
232 let (&footer_size, offset) = get_pod::<u64>(mmap, offset)?;
233 if footer_size != FOOTER_SIZE as u64 {
234 return Err(TieredStorageError::InvalidFooterSize(
235 footer_size,
236 FOOTER_SIZE as u64,
237 ));
238 }
239
240 let (magic_number, _offset) = get_pod::<TieredStorageMagicNumber>(mmap, offset)?;
241 if *magic_number != TieredStorageMagicNumber::default() {
242 return Err(TieredStorageError::MagicNumberMismatch(
243 TieredStorageMagicNumber::default().0,
244 magic_number.0,
245 ));
246 }
247
248 let footer_offset = mmap.len().saturating_sub(footer_size as usize);
249 let (footer, _offset) = unsafe { get_type::<TieredStorageFooter>(mmap, footer_offset)? };
252 Self::sanitize(footer)?;
253
254 Ok(footer)
255 }
256
257 fn sanitize(footer: &Self) -> Result<(), SanitizeFooterError> {
262 let account_meta_format_u16 =
263 unsafe { &*(ptr::from_ref(&footer.account_meta_format).cast::<u16>()) };
264 let owners_block_format_u16 =
265 unsafe { &*(ptr::from_ref(&footer.owners_block_format).cast::<u16>()) };
266 let index_block_format_u16 =
267 unsafe { &*(ptr::from_ref(&footer.index_block_format).cast::<u16>()) };
268 let account_block_format_u16 =
269 unsafe { &*(ptr::from_ref(&footer.account_block_format).cast::<u16>()) };
270
271 _ = AccountMetaFormat::try_from(*account_meta_format_u16)
272 .map_err(SanitizeFooterError::InvalidAccountMetaFormat)?;
273 _ = OwnersBlockFormat::try_from(*owners_block_format_u16)
274 .map_err(SanitizeFooterError::InvalidOwnersBlockFormat)?;
275 _ = IndexBlockFormat::try_from(*index_block_format_u16)
276 .map_err(SanitizeFooterError::InvalidIndexBlockFormat)?;
277 _ = AccountBlockFormat::try_from(*account_block_format_u16)
278 .map_err(SanitizeFooterError::InvalidAccountBlockFormat)?;
279
280 Ok(())
291 }
292}
293
294#[derive(Error, Debug)]
296pub enum SanitizeFooterError {
297 #[error("invalid account meta format: {0}")]
298 InvalidAccountMetaFormat(#[from] TryFromPrimitiveError<AccountMetaFormat>),
299
300 #[error("invalid owners block format: {0}")]
301 InvalidOwnersBlockFormat(#[from] TryFromPrimitiveError<OwnersBlockFormat>),
302
303 #[error("invalid index block format: {0}")]
304 InvalidIndexBlockFormat(#[from] TryFromPrimitiveError<IndexBlockFormat>),
305
306 #[error("invalid account block format: {0}")]
307 InvalidAccountBlockFormat(#[from] TryFromPrimitiveError<AccountBlockFormat>),
308}
309
310#[cfg(test)]
311mod tests {
312 use {
313 super::*,
314 crate::{
315 append_vec::test_utils::get_append_vec_path, tiered_storage::file::TieredWritableFile,
316 },
317 memoffset::offset_of,
318 solana_sdk::hash::Hash,
319 };
320
321 #[test]
322 fn test_footer() {
323 let path = get_append_vec_path("test_file_footer");
324 let expected_footer = TieredStorageFooter {
325 account_meta_format: AccountMetaFormat::Hot,
326 owners_block_format: OwnersBlockFormat::AddressesOnly,
327 index_block_format: IndexBlockFormat::AddressesThenOffsets,
328 account_block_format: AccountBlockFormat::AlignedRaw,
329 account_entry_count: 300,
330 account_meta_entry_size: 24,
331 account_block_size: 4096,
332 owner_count: 250,
333 owner_entry_size: 32,
334 index_block_offset: 1069600,
335 owners_block_offset: 1081200,
336 hash: Hash::new_unique(),
337 min_account_address: Pubkey::default(),
338 max_account_address: Pubkey::new_unique(),
339 format_version: FOOTER_FORMAT_VERSION,
340 footer_size: FOOTER_SIZE as u64,
341 };
342
343 {
345 let mut file = TieredWritableFile::new(&path.path).unwrap();
346 expected_footer.write_footer_block(&mut file).unwrap();
347 }
348
349 {
352 let footer = TieredStorageFooter::new_from_path(&path.path).unwrap();
353 assert_eq!(expected_footer, footer);
354 }
355 }
356
357 #[test]
358 fn test_footer_layout() {
359 assert_eq!(offset_of!(TieredStorageFooter, account_meta_format), 0x00);
360 assert_eq!(offset_of!(TieredStorageFooter, owners_block_format), 0x02);
361 assert_eq!(offset_of!(TieredStorageFooter, index_block_format), 0x04);
362 assert_eq!(offset_of!(TieredStorageFooter, account_block_format), 0x06);
363 assert_eq!(offset_of!(TieredStorageFooter, account_entry_count), 0x08);
364 assert_eq!(
365 offset_of!(TieredStorageFooter, account_meta_entry_size),
366 0x0C
367 );
368 assert_eq!(offset_of!(TieredStorageFooter, account_block_size), 0x10);
369 assert_eq!(offset_of!(TieredStorageFooter, owner_count), 0x18);
370 assert_eq!(offset_of!(TieredStorageFooter, owner_entry_size), 0x1C);
371 assert_eq!(offset_of!(TieredStorageFooter, index_block_offset), 0x20);
372 assert_eq!(offset_of!(TieredStorageFooter, owners_block_offset), 0x28);
373 assert_eq!(offset_of!(TieredStorageFooter, min_account_address), 0x30);
374 assert_eq!(offset_of!(TieredStorageFooter, max_account_address), 0x50);
375 assert_eq!(offset_of!(TieredStorageFooter, hash), 0x70);
376 assert_eq!(offset_of!(TieredStorageFooter, format_version), 0x90);
377 assert_eq!(offset_of!(TieredStorageFooter, footer_size), 0x98);
378 }
379
380 #[test]
381 fn test_sanitize() {
382 {
384 let footer = TieredStorageFooter::default();
385 let result = TieredStorageFooter::sanitize(&footer);
386 assert!(result.is_ok());
387 }
388
389 {
391 let mut footer = TieredStorageFooter::default();
392 unsafe {
393 std::ptr::write(
394 ptr::from_mut(&mut footer.account_meta_format).cast::<u16>(),
395 0xBAD0,
396 );
397 }
398 let result = TieredStorageFooter::sanitize(&footer);
399 assert!(matches!(
400 result,
401 Err(SanitizeFooterError::InvalidAccountMetaFormat(_))
402 ));
403 }
404
405 {
407 let mut footer = TieredStorageFooter::default();
408 unsafe {
409 std::ptr::write(
410 ptr::from_mut(&mut footer.owners_block_format).cast::<u16>(),
411 0xBAD0,
412 );
413 }
414 let result = TieredStorageFooter::sanitize(&footer);
415 assert!(matches!(
416 result,
417 Err(SanitizeFooterError::InvalidOwnersBlockFormat(_))
418 ));
419 }
420
421 {
423 let mut footer = TieredStorageFooter::default();
424 unsafe {
425 std::ptr::write(
426 ptr::from_mut(&mut footer.index_block_format).cast::<u16>(),
427 0xBAD0,
428 );
429 }
430 let result = TieredStorageFooter::sanitize(&footer);
431 assert!(matches!(
432 result,
433 Err(SanitizeFooterError::InvalidIndexBlockFormat(_))
434 ));
435 }
436
437 {
439 let mut footer = TieredStorageFooter::default();
440 unsafe {
441 std::ptr::write(
442 ptr::from_mut(&mut footer.account_block_format).cast::<u16>(),
443 0xBAD0,
444 );
445 }
446 let result = TieredStorageFooter::sanitize(&footer);
447 assert!(matches!(
448 result,
449 Err(SanitizeFooterError::InvalidAccountBlockFormat(_))
450 ));
451 }
452 }
453}