solana_accounts_db/tiered_storage/
footer.rs

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
21/// The size of the footer struct + the magic number at the end.
22pub const FOOTER_SIZE: usize =
23    mem::size_of::<TieredStorageFooter>() + mem::size_of::<TieredStorageMagicNumber>();
24static_assertions::const_assert_eq!(mem::size_of::<TieredStorageFooter>(), 160);
25
26/// The size of the ending part of the footer.  This size should remain unchanged
27/// even when the footer's format changes.
28pub 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    // Temporarily comment out to avoid unimplemented!() block
46    // Cold = 1,
47}
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    // formats
71    /// The format of the account meta entry.
72    pub account_meta_format: AccountMetaFormat,
73    /// The format of the owners block.
74    pub owners_block_format: OwnersBlockFormat,
75    /// The format of the account index block.
76    pub index_block_format: IndexBlockFormat,
77    /// The format of the account block.
78    pub account_block_format: AccountBlockFormat,
79
80    // Account-block related
81    /// The number of account entries.
82    pub account_entry_count: u32,
83    /// The size of each account meta entry in bytes.
84    pub account_meta_entry_size: u32,
85    /// The default size of an account block before compression.
86    ///
87    /// If the size of one account (meta + data + optional fields) before
88    /// compression is bigger than this number, than it is considered a
89    /// blob account and it will have its own account block.
90    pub account_block_size: u64,
91
92    // Owner-related
93    /// The number of owners.
94    pub owner_count: u32,
95    /// The size of each owner entry.
96    pub owner_entry_size: u32,
97
98    // Offsets
99    // Note that offset to the account blocks is omitted as it's always 0.
100    /// The offset pointing to the first byte of the account index block.
101    pub index_block_offset: u64,
102    /// The offset pointing to the first byte of the owners block.
103    pub owners_block_offset: u64,
104
105    // account range
106    /// The smallest account address in this file.
107    pub min_account_address: Pubkey,
108    /// The largest account address in this file.
109    pub max_account_address: Pubkey,
110
111    /// A hash that represents a tiered accounts file for consistency check.
112    pub hash: Hash,
113
114    /// The format version of the tiered accounts file.
115    pub format_version: u64,
116    // The below fields belong to footer tail.
117    // The sum of their sizes should match FOOTER_TAIL_SIZE.
118    /// The size of the footer including the magic number.
119    pub footer_size: u64,
120    // This field is persisted in the storage but not in this struct.
121    // The number should match FILE_MAGIC_NUMBER.
122    // pub magic_number: u64,
123}
124
125// It is undefined behavior to read/write uninitialized bytes.
126// The `Pod` marker trait indicates there are no uninitialized bytes.
127// In order to safely guarantee a type is POD, it cannot have any padding.
128const _: () = 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>() // account_entry_count
135         + std::mem::size_of::<u32>() // account_meta_entry_size
136         + std::mem::size_of::<u64>() // account_block_size
137         + std::mem::size_of::<u32>() // owner_count
138         + std::mem::size_of::<u32>() // owner_entry_size
139         + std::mem::size_of::<u64>() // index_block_offset
140         + std::mem::size_of::<u64>() // owners_block_offset
141         + std::mem::size_of::<Pubkey>() // min_account_address
142         + std::mem::size_of::<Pubkey>() // max_account_address
143         + std::mem::size_of::<Hash>() // hash
144         + std::mem::size_of::<u64>() // format_version
145         + std::mem::size_of::<u64>(), // footer_size
146    "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        // SAFETY: The footer does not contain any uninitialized bytes.
182        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        // SAFETY: We sanitize the footer to ensure all the bytes are
218        // actually safe to interpret as a TieredStorageFooter.
219        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        // SAFETY: We sanitize the footer to ensure all the bytes are
251        // actually safe to interpret as a TieredStorageFooter.
252        let (footer, _offset) = unsafe { get_type::<TieredStorageFooter>(mmap, footer_offset)? };
253        Self::sanitize(footer)?;
254
255        Ok(footer)
256    }
257
258    /// Sanitizes the footer
259    ///
260    /// Since the various formats only have specific valid values, they must be sanitized
261    /// prior to use.  This ensures the formats are valid to interpret as (rust) enums.
262    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        // Since we just sanitized the formats within the footer,
282        // it is now safe to read them as (rust) enums.
283        //
284        // from https://doc.rust-lang.org/reference/items/enumerations.html#casting:
285        // > If an enumeration is unit-only (with no tuple and struct variants),
286        // > then its discriminant can be directly accessed with a numeric cast;
287        //
288        // from https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting:
289        // > If the enumeration specifies a primitive representation,
290        // > then the discriminant may be reliably accessed via unsafe pointer casting
291        Ok(())
292    }
293}
294
295/// Errors that can happen while sanitizing the footer
296#[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        // Persist the expected footer.
345        {
346            let mut file = TieredWritableFile::new(&path.path).unwrap();
347            expected_footer.write_footer_block(&mut file).unwrap();
348        }
349
350        // Reopen the same storage, and expect the persisted footer is
351        // the same as what we have written.
352        {
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        // test: all good
384        {
385            let footer = TieredStorageFooter::default();
386            let result = TieredStorageFooter::sanitize(&footer);
387            assert!(result.is_ok());
388        }
389
390        // test: bad account meta format
391        {
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        // test: bad owners block format
407        {
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        // test: bad index block format
423        {
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        // test: bad account block format
439        {
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}