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