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