solana_accounts_db/
append_vec.rs

1//! Persistent storage for accounts.
2//!
3//! For more information, see:
4//!
5//! <https://docs.solanalabs.com/implemented-proposals/persistent-account-storage>
6
7use {
8    crate::{
9        account_storage::meta::{AccountMeta, StoredAccountMeta, StoredMeta},
10        accounts_file::{
11            AccountsFileError, InternalsForArchive, MatchAccountOwnerError, Result, StorageAccess,
12            StoredAccountsInfo, ALIGN_BOUNDARY_OFFSET,
13        },
14        accounts_hash::AccountHash,
15        accounts_index::ZeroLamport,
16        buffered_reader::{BufferedReader, BufferedReaderStatus},
17        file_io::read_into_buffer,
18        storable_accounts::StorableAccounts,
19        u64_align,
20    },
21    log::*,
22    memmap2::MmapMut,
23    solana_pubkey::Pubkey,
24    solana_sdk::{
25        account::{AccountSharedData, ReadableAccount, WritableAccount},
26        hash::Hash,
27        stake_history::Epoch,
28        system_instruction::MAX_PERMITTED_DATA_LENGTH,
29    },
30    std::{
31        self,
32        convert::TryFrom,
33        fs::{remove_file, File, OpenOptions},
34        io::{Seek, SeekFrom, Write},
35        mem::{self, MaybeUninit},
36        path::{Path, PathBuf},
37        ptr, slice,
38        sync::{
39            atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
40            Mutex,
41        },
42    },
43    thiserror::Error,
44};
45
46pub mod test_utils;
47#[cfg(test)]
48use solana_sdk::account::accounts_equal;
49
50/// size of the fixed sized fields in an append vec
51/// we need to add data len and align it to get the actual stored size
52pub const STORE_META_OVERHEAD: usize = 136;
53
54// Ensure the STORE_META_OVERHEAD constant remains accurate
55const _: () = assert!(
56    STORE_META_OVERHEAD
57        == mem::size_of::<StoredMeta>()
58            + mem::size_of::<AccountMeta>()
59            + mem::size_of::<AccountHash>()
60);
61
62/// Returns the size this item will take to store plus possible alignment padding bytes before the next entry.
63/// fixed-size portion of per-account data written
64/// plus 'data_len', aligned to next boundary
65pub fn aligned_stored_size(data_len: usize) -> usize {
66    u64_align!(STORE_META_OVERHEAD + data_len)
67}
68
69pub const MAXIMUM_APPEND_VEC_FILE_SIZE: u64 = 16 * 1024 * 1024 * 1024; // 16 GiB
70
71#[derive(Error, Debug)]
72/// An enum for AppendVec related errors.
73pub enum AppendVecError {
74    #[error("too small file size {0} for AppendVec")]
75    FileSizeTooSmall(usize),
76
77    #[error("too large file size {0} for AppendVec")]
78    FileSizeTooLarge(usize),
79
80    #[error("incorrect layout/length/data in the appendvec at path {}", .0.display())]
81    IncorrectLayout(PathBuf),
82
83    #[error("offset ({0}) is larger than file size ({1})")]
84    OffsetOutOfBounds(usize, usize),
85}
86
87/// A slice whose contents are known to be valid.
88/// The slice contains no undefined bytes.
89#[derive(Debug, Copy, Clone)]
90pub(crate) struct ValidSlice<'a>(&'a [u8]);
91
92impl<'a> ValidSlice<'a> {
93    pub(crate) fn new(data: &'a [u8]) -> Self {
94        Self(data)
95    }
96
97    pub(crate) fn len(&self) -> usize {
98        self.0.len()
99    }
100
101    #[cfg(all(unix, test))]
102    pub(crate) fn slice(&self) -> &[u8] {
103        self.0
104    }
105}
106
107/// References to account data stored elsewhere. Getting an `Account` requires cloning
108/// (see `StoredAccountMeta::clone_account()`).
109#[derive(PartialEq, Eq, Debug)]
110pub struct AppendVecStoredAccountMeta<'append_vec> {
111    pub meta: &'append_vec StoredMeta,
112    /// account data
113    pub account_meta: &'append_vec AccountMeta,
114    pub(crate) data: &'append_vec [u8],
115    pub(crate) offset: usize,
116    pub(crate) stored_size: usize,
117    pub(crate) hash: &'append_vec AccountHash,
118}
119
120impl<'append_vec> AppendVecStoredAccountMeta<'append_vec> {
121    pub fn pubkey(&self) -> &'append_vec Pubkey {
122        &self.meta.pubkey
123    }
124
125    pub fn hash(&self) -> &'append_vec AccountHash {
126        self.hash
127    }
128
129    pub fn stored_size(&self) -> usize {
130        self.stored_size
131    }
132
133    pub fn offset(&self) -> usize {
134        self.offset
135    }
136
137    pub fn data(&self) -> &'append_vec [u8] {
138        self.data
139    }
140
141    pub fn data_len(&self) -> u64 {
142        self.meta.data_len
143    }
144
145    pub fn meta(&self) -> &StoredMeta {
146        self.meta
147    }
148
149    pub(crate) fn sanitize(&self) -> bool {
150        self.sanitize_executable() && self.sanitize_lamports()
151    }
152
153    fn sanitize_executable(&self) -> bool {
154        // Sanitize executable to ensure higher 7-bits are cleared correctly.
155        self.ref_executable_byte() & !1 == 0
156    }
157
158    fn sanitize_lamports(&self) -> bool {
159        // Sanitize 0 lamports to ensure to be same as AccountSharedData::default()
160        self.account_meta.lamports != 0
161            || self.to_account_shared_data() == AccountSharedData::default()
162    }
163
164    fn ref_executable_byte(&self) -> &u8 {
165        // Use extra references to avoid value silently clamped to 1 (=true) and 0 (=false)
166        // Yes, this really happens; see test_new_from_file_crafted_executable
167        let executable_bool: &bool = &self.account_meta.executable;
168        let executable_bool_ptr = ptr::from_ref(executable_bool);
169        // UNSAFE: Force to interpret mmap-backed bool as u8 to really read the actual memory content
170        let executable_byte: &u8 = unsafe { &*(executable_bool_ptr.cast()) };
171        executable_byte
172    }
173}
174
175impl<'append_vec> ReadableAccount for AppendVecStoredAccountMeta<'append_vec> {
176    fn lamports(&self) -> u64 {
177        self.account_meta.lamports
178    }
179    fn data(&self) -> &'append_vec [u8] {
180        self.data()
181    }
182    fn owner(&self) -> &'append_vec Pubkey {
183        &self.account_meta.owner
184    }
185    fn executable(&self) -> bool {
186        self.account_meta.executable
187    }
188    fn rent_epoch(&self) -> Epoch {
189        self.account_meta.rent_epoch
190    }
191}
192
193/// info from an entry useful for building an index
194pub(crate) struct IndexInfo {
195    /// size of entry, aligned to next u64
196    /// This matches the return of `get_account`
197    pub stored_size_aligned: usize,
198    /// info on the entry
199    pub index_info: IndexInfoInner,
200}
201
202/// info from an entry useful for building an index
203pub(crate) struct IndexInfoInner {
204    /// offset to this entry
205    pub offset: usize,
206    pub pubkey: Pubkey,
207    pub lamports: u64,
208    pub rent_epoch: Epoch,
209    pub executable: bool,
210    pub data_len: u64,
211}
212
213/// offsets to help navigate the persisted format of `AppendVec`
214#[derive(Debug)]
215struct AccountOffsets {
216    /// offset to the end of the &[u8] data
217    offset_to_end_of_data: usize,
218    /// offset to the next account. This will be aligned.
219    next_account_offset: usize,
220    /// # of bytes (aligned) to store this account, including variable sized data
221    stored_size_aligned: usize,
222}
223
224#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
225#[derive(Debug)]
226enum AppendVecFileBacking {
227    /// A file-backed block of memory that is used to store the data for each appended item.
228    Mmap(Mmap),
229    /// This was opened as a read only file
230    #[cfg_attr(not(unix), allow(dead_code))]
231    File(File),
232}
233
234#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
235#[derive(Debug)]
236struct Mmap {
237    mmap: MmapMut,
238    /// Flags if the mmap is dirty or not.
239    /// Since fastboot requires that all mmaps are flushed to disk, be smart about it.
240    /// AppendVecs are (almost) always write-once.  The common case is that an AppendVec
241    /// will only need to be flushed once.  This avoids unnecessary syscalls/kernel work
242    /// when nothing in the AppendVec has changed.
243    is_dirty: AtomicBool,
244}
245
246/// A thread-safe, file-backed block of memory used to store `Account` instances. Append operations
247/// are serialized such that only one thread updates the internal `append_lock` at a time. No
248/// restrictions are placed on reading. That is, one may read items from one thread while another
249/// is appending new items.
250#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
251#[derive(Debug)]
252pub struct AppendVec {
253    /// The file path where the data is stored.
254    path: PathBuf,
255
256    /// access the file data
257    backing: AppendVecFileBacking,
258
259    /// A lock used to serialize append operations.
260    append_lock: Mutex<()>,
261
262    /// The number of bytes used to store items, not the number of items.
263    current_len: AtomicUsize,
264
265    /// The number of bytes available for storing items.
266    file_size: u64,
267
268    /// if true, remove file when dropped
269    remove_file_on_drop: AtomicBool,
270}
271
272const PAGE_SIZE: u64 = 4 * 1024;
273/// big enough for 3x the largest account size
274const SCAN_BUFFER_SIZE: usize =
275    page_align((STORE_META_OVERHEAD as u64 + MAX_PERMITTED_DATA_LENGTH) * 3) as usize;
276const fn page_align(size: u64) -> u64 {
277    (size + (PAGE_SIZE - 1)) & !(PAGE_SIZE - 1)
278}
279
280/// Buffer size to use when scanning *without* needing account data
281///
282/// When scanning without needing account data, it is desirable to only read the account metadata
283/// and skip over the account data.  In theory, we could read a single account's metadata at a time,
284/// then skip ahead to the next account, entirely bypassing the account's data.  However this comes
285/// at the cost of requiring one syscall per scanning each account, which is expensive.  Ideally
286/// we'd like to use the fewest syscalls and also read the least amount of extraneous account data.
287/// As a compromise, we use a much smaller buffer, yet still large enough to amortize syscall cost.
288///
289/// On mnb, the overwhelming majority of accounts are token accounts, which use 165 bytes of data.
290/// Including storage overhead and alignment, that's 304 bytes per account.
291/// Per slot, *with* rent rewrites, we store 1,200 to 1,500 accounts.  With a 64 KiB buffer, we'd
292/// be able to hold about 215 accounts, so there would not be many syscalls needed to scan
293/// the file.  Since we also expect some larger accounts, this will also avoid reading/copying
294/// large account data.  This should be a decent starting value, and can be modified over time.
295#[cfg_attr(feature = "dev-context-only-utils", qualifier_attr::qualifiers(pub))]
296const SCAN_BUFFER_SIZE_WITHOUT_DATA: usize = 1 << 16;
297
298pub struct AppendVecStat {
299    pub mmap_files_open: AtomicU64,
300    pub mmap_files_dirty: AtomicU64,
301    pub open_as_file_io: AtomicU64,
302}
303
304lazy_static! {
305    pub static ref APPEND_VEC_STATS: AppendVecStat = AppendVecStat {
306        mmap_files_open: AtomicU64::new(0),
307        mmap_files_dirty: AtomicU64::new(0),
308        open_as_file_io: AtomicU64::new(0),
309    };
310}
311
312impl Drop for AppendVec {
313    fn drop(&mut self) {
314        APPEND_VEC_STATS
315            .mmap_files_open
316            .fetch_sub(1, Ordering::Relaxed);
317        match &self.backing {
318            AppendVecFileBacking::Mmap(mmap_only) => {
319                if mmap_only.is_dirty.load(Ordering::Acquire) {
320                    APPEND_VEC_STATS
321                        .mmap_files_dirty
322                        .fetch_sub(1, Ordering::Relaxed);
323                }
324            }
325            AppendVecFileBacking::File(_) => {
326                APPEND_VEC_STATS
327                    .open_as_file_io
328                    .fetch_sub(1, Ordering::Relaxed);
329            }
330        }
331        if self.remove_file_on_drop.load(Ordering::Acquire) {
332            // If we're reopening in readonly mode, we don't delete the file. See
333            // AppendVec::reopen_as_readonly.
334            if let Err(_err) = remove_file(&self.path) {
335                // promote this to panic soon.
336                // disabled due to many false positive warnings while running tests.
337                // blocked by rpc's upgrade to jsonrpc v17
338                //error!("AppendVec failed to remove {}: {err}", &self.path.display());
339                inc_new_counter_info!("append_vec_drop_fail", 1);
340            }
341        }
342    }
343}
344
345impl AppendVec {
346    pub fn new(file: impl Into<PathBuf>, create: bool, size: usize) -> Self {
347        let file = file.into();
348        let initial_len = 0;
349        AppendVec::sanitize_len_and_size(initial_len, size).unwrap();
350
351        if create {
352            let _ignored = remove_file(&file);
353        }
354
355        let mut data = OpenOptions::new()
356            .read(true)
357            .write(true)
358            .create(create)
359            .open(&file)
360            .map_err(|e| {
361                panic!(
362                    "Unable to {} data file {} in current dir({:?}): {:?}",
363                    if create { "create" } else { "open" },
364                    file.display(),
365                    std::env::current_dir(),
366                    e
367                );
368            })
369            .unwrap();
370
371        // Theoretical performance optimization: write a zero to the end of
372        // the file so that we won't have to resize it later, which may be
373        // expensive.
374        data.seek(SeekFrom::Start((size - 1) as u64)).unwrap();
375        data.write_all(&[0]).unwrap();
376        data.rewind().unwrap();
377        data.flush().unwrap();
378
379        //UNSAFE: Required to create a Mmap
380        let mmap = unsafe { MmapMut::map_mut(&data) };
381        let mmap = mmap.unwrap_or_else(|e| {
382            error!(
383                "Failed to map the data file (size: {}): {}.\n
384                    Please increase sysctl vm.max_map_count or equivalent for your platform.",
385                size, e
386            );
387            std::process::exit(1);
388        });
389        APPEND_VEC_STATS
390            .mmap_files_open
391            .fetch_add(1, Ordering::Relaxed);
392
393        AppendVec {
394            path: file,
395            backing: AppendVecFileBacking::Mmap(Mmap {
396                mmap,
397                is_dirty: AtomicBool::new(false),
398            }),
399            // This mutex forces append to be single threaded, but concurrent with reads
400            // See UNSAFE usage in `append_ptr`
401            append_lock: Mutex::new(()),
402            current_len: AtomicUsize::new(initial_len),
403            file_size: size as u64,
404            remove_file_on_drop: AtomicBool::new(true),
405        }
406    }
407
408    fn sanitize_len_and_size(current_len: usize, file_size: usize) -> Result<()> {
409        if file_size == 0 {
410            Err(AccountsFileError::AppendVecError(
411                AppendVecError::FileSizeTooSmall(file_size),
412            ))
413        } else if usize::try_from(MAXIMUM_APPEND_VEC_FILE_SIZE)
414            .map(|max| file_size > max)
415            .unwrap_or(true)
416        {
417            Err(AccountsFileError::AppendVecError(
418                AppendVecError::FileSizeTooLarge(file_size),
419            ))
420        } else if current_len > file_size {
421            Err(AccountsFileError::AppendVecError(
422                AppendVecError::OffsetOutOfBounds(current_len, file_size),
423            ))
424        } else {
425            Ok(())
426        }
427    }
428
429    pub fn dead_bytes_due_to_zero_lamport_single_ref(&self, count: usize) -> usize {
430        aligned_stored_size(0) * count
431    }
432
433    pub fn flush(&self) -> Result<()> {
434        match &self.backing {
435            AppendVecFileBacking::Mmap(mmap_only) => {
436                // Check to see if the mmap is actually dirty before flushing.
437                let should_flush = mmap_only.is_dirty.swap(false, Ordering::AcqRel);
438                if should_flush {
439                    mmap_only.mmap.flush()?;
440                    APPEND_VEC_STATS
441                        .mmap_files_dirty
442                        .fetch_sub(1, Ordering::Relaxed);
443                }
444                Ok(())
445            }
446            AppendVecFileBacking::File(_file) => {
447                // File also means read only, so nothing to flush.
448                Ok(())
449            }
450        }
451    }
452
453    pub fn reset(&self) {
454        // This mutex forces append to be single threaded, but concurrent with reads
455        // See UNSAFE usage in `append_ptr`
456        let _lock = self.append_lock.lock().unwrap();
457        self.current_len.store(0, Ordering::Release);
458    }
459
460    /// when we can use file i/o as opposed to mmap, this is the trigger to tell us
461    /// that no more appending will occur and we can close the initial mmap.
462    #[cfg_attr(not(unix), allow(dead_code))]
463    pub(crate) fn reopen_as_readonly(&self) -> Option<Self> {
464        #[cfg(not(unix))]
465        // must open as mmmap on non-unix
466        return None;
467
468        #[cfg(unix)]
469        match &self.backing {
470            // already a file, so already read-only
471            AppendVecFileBacking::File(_file) => None,
472            AppendVecFileBacking::Mmap(_mmap) => {
473                // we are a map, so re-open as a file
474                self.flush().expect("flush must succeed");
475                // we are re-opening the file, so don't remove the file on disk when the old mmapped one is dropped
476                self.remove_file_on_drop.store(false, Ordering::Release);
477
478                // The file should have already been sanitized. Don't need to check when we open the file again.
479                AppendVec::new_from_file_unchecked(
480                    self.path.clone(),
481                    self.len(),
482                    StorageAccess::File,
483                )
484                .ok()
485            }
486        }
487    }
488
489    /// how many more bytes can be stored in this append vec
490    pub fn remaining_bytes(&self) -> u64 {
491        self.capacity()
492            .saturating_sub(u64_align!(self.len()) as u64)
493    }
494
495    pub fn len(&self) -> usize {
496        self.current_len.load(Ordering::Acquire)
497    }
498
499    pub fn is_empty(&self) -> bool {
500        self.len() == 0
501    }
502
503    pub fn capacity(&self) -> u64 {
504        self.file_size
505    }
506
507    pub fn new_from_file(
508        path: impl Into<PathBuf>,
509        current_len: usize,
510        storage_access: StorageAccess,
511    ) -> Result<(Self, usize)> {
512        let path = path.into();
513        let new = Self::new_from_file_unchecked(path, current_len, storage_access)?;
514
515        let (sanitized, num_accounts) = new.sanitize_layout_and_length();
516        if !sanitized {
517            return Err(AccountsFileError::AppendVecError(
518                AppendVecError::IncorrectLayout(new.path.clone()),
519            ));
520        }
521
522        Ok((new, num_accounts))
523    }
524
525    /// Creates an appendvec from file without performing sanitize checks or counting the number of accounts
526    #[cfg_attr(not(unix), allow(unused_variables))]
527    pub fn new_from_file_unchecked(
528        path: impl Into<PathBuf>,
529        current_len: usize,
530        storage_access: StorageAccess,
531    ) -> Result<Self> {
532        let path = path.into();
533        let file_size = std::fs::metadata(&path)?.len();
534        Self::sanitize_len_and_size(current_len, file_size as usize)?;
535
536        let data = OpenOptions::new()
537            .read(true)
538            .write(true)
539            .create(false)
540            .open(&path)?;
541
542        #[cfg(unix)]
543        // we must use mmap on non-linux
544        if storage_access == StorageAccess::File {
545            APPEND_VEC_STATS
546                .mmap_files_open
547                .fetch_add(1, Ordering::Relaxed);
548            APPEND_VEC_STATS
549                .open_as_file_io
550                .fetch_add(1, Ordering::Relaxed);
551
552            return Ok(AppendVec {
553                path,
554                backing: AppendVecFileBacking::File(data),
555                append_lock: Mutex::new(()),
556                current_len: AtomicUsize::new(current_len),
557                file_size,
558                remove_file_on_drop: AtomicBool::new(true),
559            });
560        }
561
562        let mmap = unsafe {
563            let result = MmapMut::map_mut(&data);
564            if result.is_err() {
565                // for vm.max_map_count, error is: {code: 12, kind: Other, message: "Cannot allocate memory"}
566                info!("memory map error: {:?}. This may be because vm.max_map_count is not set correctly.", result);
567            }
568            result?
569        };
570        APPEND_VEC_STATS
571            .mmap_files_open
572            .fetch_add(1, Ordering::Relaxed);
573
574        Ok(AppendVec {
575            path,
576            backing: AppendVecFileBacking::Mmap(Mmap {
577                mmap,
578                is_dirty: AtomicBool::new(false),
579            }),
580            append_lock: Mutex::new(()),
581            current_len: AtomicUsize::new(current_len),
582            file_size,
583            remove_file_on_drop: AtomicBool::new(true),
584        })
585    }
586
587    /// Opens the AppendVec at `path` for use by `store-tool`
588    #[cfg(feature = "dev-context-only-utils")]
589    pub fn new_for_store_tool(path: impl Into<PathBuf>) -> Result<Self> {
590        let path = path.into();
591        let file_size = std::fs::metadata(&path)?.len();
592        Self::new_from_file_unchecked(path, file_size as usize, StorageAccess::default())
593    }
594
595    fn sanitize_layout_and_length(&self) -> (bool, usize) {
596        // This discards allocated accounts immediately after check at each loop iteration.
597        //
598        // This code should not reuse AppendVec.accounts() method as the current form or
599        // extend it to be reused here because it would allow attackers to accumulate
600        // some measurable amount of memory needlessly.
601        let mut num_accounts = 0;
602        let mut matches = true;
603        let mut last_offset = 0;
604        self.scan_accounts(|account| {
605            if !matches || !account.sanitize() {
606                matches = false;
607                return;
608            }
609            last_offset = account.offset() + account.stored_size();
610            num_accounts += 1;
611        });
612        if !matches {
613            return (false, num_accounts);
614        }
615        let aligned_current_len = u64_align!(self.current_len.load(Ordering::Acquire));
616
617        (last_offset == aligned_current_len, num_accounts)
618    }
619
620    /// Get a reference to the data at `offset` of `size` bytes if that slice
621    /// doesn't overrun the internal buffer. Otherwise return None.
622    /// Also return the offset of the first byte after the requested data that
623    /// falls on a 64-byte boundary.
624    fn get_slice(slice: ValidSlice, offset: usize, size: usize) -> Option<(&[u8], usize)> {
625        // SAFETY: Wrapping math is safe here because if `end` does wrap, the Range
626        // parameter to `.get()` will be invalid, and `.get()` will correctly return None.
627        let end = offset.wrapping_add(size);
628        slice
629            .0
630            .get(offset..end)
631            .map(|subslice| (subslice, u64_align!(end)))
632    }
633
634    /// Copy `len` bytes from `src` to the first 64-byte boundary after position `offset` of
635    /// the internal buffer. Then update `offset` to the first byte after the copied data.
636    fn append_ptr(&self, offset: &mut usize, src: *const u8, len: usize) {
637        let pos = u64_align!(*offset);
638        match &self.backing {
639            AppendVecFileBacking::Mmap(mmap_only) => {
640                let data = &mmap_only.mmap[pos..(pos + len)];
641                //UNSAFE: This mut append is safe because only 1 thread can append at a time
642                //Mutex<()> guarantees exclusive write access to the memory occupied in
643                //the range.
644                unsafe {
645                    let dst = data.as_ptr() as *mut _;
646                    ptr::copy(src, dst, len);
647                };
648                *offset = pos + len;
649            }
650            AppendVecFileBacking::File(_file) => {
651                unimplemented!();
652            }
653        }
654    }
655
656    /// Copy each value in `vals`, in order, to the first 64-byte boundary after position `offset`.
657    /// If there is sufficient space, then update `offset` and the internal `current_len` to the
658    /// first byte after the copied data and return the starting position of the copied data.
659    /// Otherwise return None and leave `offset` unchanged.
660    fn append_ptrs_locked(&self, offset: &mut usize, vals: &[(*const u8, usize)]) -> Option<usize> {
661        let mut end = *offset;
662        for val in vals {
663            end = u64_align!(end);
664            end += val.1;
665        }
666
667        if (self.file_size as usize) < end {
668            return None;
669        }
670
671        let pos = u64_align!(*offset);
672        for val in vals {
673            self.append_ptr(offset, val.0, val.1)
674        }
675        self.current_len.store(*offset, Ordering::Release);
676        Some(pos)
677    }
678
679    /// Return a reference to the type at `offset` if its data doesn't overrun the internal buffer.
680    /// Otherwise return None. Also return the offset of the first byte after the requested data
681    /// that falls on a 64-byte boundary.
682    fn get_type<T>(slice: ValidSlice, offset: usize) -> Option<(&T, usize)> {
683        let (data, next) = Self::get_slice(slice, offset, mem::size_of::<T>())?;
684        let ptr = data.as_ptr().cast();
685        //UNSAFE: The cast is safe because the slice is aligned and fits into the memory
686        //and the lifetime of the &T is tied to self, which holds the underlying memory map
687        Some((unsafe { &*ptr }, next))
688    }
689
690    /// MmapMut could have more capacity than `len()` knows is valid.
691    /// Return the subset of `mmap` that is known to be valid.
692    /// This allows comparisons against the slice len.
693    fn get_valid_slice_from_mmap<'a>(&self, mmap: &'a MmapMut) -> ValidSlice<'a> {
694        ValidSlice(&mmap[..self.len()])
695    }
696
697    /// calls `callback` with the stored account metadata for the account at `offset` if its data doesn't overrun
698    /// the internal buffer. Otherwise return None.
699    pub fn get_stored_account_meta_callback<Ret>(
700        &self,
701        offset: usize,
702        mut callback: impl for<'local> FnMut(StoredAccountMeta<'local>) -> Ret,
703    ) -> Option<Ret> {
704        match &self.backing {
705            AppendVecFileBacking::Mmap(Mmap { mmap, .. }) => {
706                let slice = self.get_valid_slice_from_mmap(mmap);
707                let (meta, next): (&StoredMeta, _) = Self::get_type(slice, offset)?;
708                let (account_meta, next): (&AccountMeta, _) = Self::get_type(slice, next)?;
709                let (hash, next): (&AccountHash, _) = Self::get_type(slice, next)?;
710                let (data, next) = Self::get_slice(slice, next, meta.data_len as usize)?;
711                let stored_size = next - offset;
712                Some(callback(StoredAccountMeta::AppendVec(
713                    AppendVecStoredAccountMeta {
714                        meta,
715                        account_meta,
716                        data,
717                        offset,
718                        stored_size,
719                        hash,
720                    },
721                )))
722            }
723            AppendVecFileBacking::File(file) => {
724                // 4096 was just picked to be a single page size
725                let mut buf = [MaybeUninit::<u8>::uninit(); PAGE_SIZE as usize];
726                // SAFETY: `read_into_buffer` will only write to uninitialized memory.
727                let bytes_read = read_into_buffer(file, self.len(), offset, unsafe {
728                    slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, buf.len())
729                })
730                .ok()?;
731                // SAFETY: we only read the initialized portion.
732                let valid_bytes = ValidSlice(unsafe {
733                    slice::from_raw_parts(buf.as_ptr() as *const u8, bytes_read)
734                });
735                let (meta, next): (&StoredMeta, _) = Self::get_type(valid_bytes, 0)?;
736                let (account_meta, next): (&AccountMeta, _) = Self::get_type(valid_bytes, next)?;
737                let (hash, next): (&AccountHash, _) = Self::get_type(valid_bytes, next)?;
738                let data_len = meta.data_len;
739                let remaining_bytes_for_data = bytes_read - next;
740                Some(if remaining_bytes_for_data >= data_len as usize {
741                    // we already read enough data to load this account
742                    let (data, next) = Self::get_slice(valid_bytes, next, meta.data_len as usize)?;
743                    let stored_size = next;
744                    let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
745                        meta,
746                        account_meta,
747                        data,
748                        offset,
749                        stored_size,
750                        hash,
751                    });
752                    callback(account)
753                } else {
754                    // not enough was read from file to get `data`
755                    assert!(data_len <= MAX_PERMITTED_DATA_LENGTH, "{data_len}");
756                    let mut data: Box<[MaybeUninit<u8>]> = Box::new_uninit_slice(data_len as usize);
757                    // instead, we could piece together what we already read here. Maybe we just needed 1 more byte.
758                    // Note here `next` is a 0-based offset from the beginning of this account.
759                    // SAFETY: `read_into_buffer` will only write to uninitialized memory.
760                    let bytes_read = read_into_buffer(file, self.len(), offset + next, unsafe {
761                        slice::from_raw_parts_mut(data.as_mut_ptr() as *mut u8, data_len as usize)
762                    })
763                    .ok()?;
764                    if bytes_read < data_len as usize {
765                        // eof or otherwise couldn't read all the data
766                        return None;
767                    }
768                    // SAFETY: we've just checked that `bytes_read` is at least `data_len`.
769                    let data = unsafe { data.assume_init() };
770                    let stored_size = aligned_stored_size(data_len as usize);
771                    let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
772                        meta,
773                        account_meta,
774                        data: &data[..],
775                        offset,
776                        stored_size,
777                        hash,
778                    });
779                    callback(account)
780                })
781            }
782        }
783    }
784
785    /// return an `AccountSharedData` for an account at `offset`.
786    /// This fn can efficiently return exactly what is needed by a caller.
787    /// This is on the critical path of tx processing for accounts not in the read or write caches.
788    pub(crate) fn get_account_shared_data(&self, offset: usize) -> Option<AccountSharedData> {
789        match &self.backing {
790            AppendVecFileBacking::Mmap(_) => self
791                .get_stored_account_meta_callback(offset, |account| {
792                    account.to_account_shared_data()
793                }),
794            AppendVecFileBacking::File(file) => {
795                let mut buf = MaybeUninit::<[u8; PAGE_SIZE as usize]>::uninit();
796                let bytes_read =
797                    read_into_buffer(file, self.len(), offset, unsafe { &mut *buf.as_mut_ptr() })
798                        .ok()?;
799                // SAFETY: we only read the initialized portion.
800                let valid_bytes = ValidSlice(unsafe {
801                    slice::from_raw_parts(buf.as_ptr() as *const u8, bytes_read)
802                });
803                let (meta, next): (&StoredMeta, _) = Self::get_type(valid_bytes, 0)?;
804                let (account_meta, next): (&AccountMeta, _) = Self::get_type(valid_bytes, next)?;
805                let (hash, next): (&AccountHash, _) = Self::get_type(valid_bytes, next)?;
806                let data_len = meta.data_len;
807                let remaining_bytes_for_data = bytes_read - next;
808                Some(if remaining_bytes_for_data >= data_len as usize {
809                    // we already read enough data to load this account
810                    let (data, next) = Self::get_slice(valid_bytes, next, meta.data_len as usize)?;
811                    let stored_size = next;
812                    let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
813                        meta,
814                        account_meta,
815                        data,
816                        offset,
817                        stored_size,
818                        hash,
819                    });
820                    // data is within `buf`, so just allocate a new vec for data
821                    account.to_account_shared_data()
822                } else {
823                    // not enough was read from file to get `data`
824                    assert!(data_len <= MAX_PERMITTED_DATA_LENGTH, "{data_len}");
825                    let mut data = Vec::with_capacity(data_len as usize);
826                    let slice = data.spare_capacity_mut();
827                    // Note here `next` is a 0-based offset from the beginning of this account.
828                    // SAFETY: `read_into_buffer` will only write to uninitialized memory.
829                    let bytes_read = read_into_buffer(file, self.len(), offset + next, unsafe {
830                        slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, data_len as usize)
831                    })
832                    .ok()?;
833                    if bytes_read < data_len as usize {
834                        // eof or otherwise couldn't read all the data
835                        return None;
836                    }
837                    // SAFETY: we've just checked that `bytes_read` is at least `data_len`.
838                    unsafe { data.set_len(data_len as usize) };
839                    AccountSharedData::create(
840                        account_meta.lamports,
841                        data,
842                        account_meta.owner,
843                        account_meta.executable,
844                        account_meta.rent_epoch,
845                    )
846                })
847            }
848        }
849    }
850
851    /// Return Ok(index_of_matching_owner) if the account owner at `offset` is one of the pubkeys in `owners`.
852    /// Return Err(MatchAccountOwnerError::NoMatch) if the account has 0 lamports or the owner is not one of
853    /// the pubkeys in `owners`.
854    /// Return Err(MatchAccountOwnerError::UnableToLoad) if the `offset` value causes a data overrun.
855    pub fn account_matches_owners(
856        &self,
857        offset: usize,
858        owners: &[Pubkey],
859    ) -> std::result::Result<usize, MatchAccountOwnerError> {
860        self.get_stored_account_meta_callback(offset, |stored_account_meta| {
861            if stored_account_meta.lamports() == 0 {
862                Err(MatchAccountOwnerError::NoMatch)
863            } else {
864                owners
865                    .iter()
866                    .position(|entry| stored_account_meta.owner() == entry)
867                    .ok_or(MatchAccountOwnerError::NoMatch)
868            }
869        })
870        .unwrap_or(Err(MatchAccountOwnerError::UnableToLoad))
871    }
872
873    #[cfg(test)]
874    pub fn get_account_test(
875        &self,
876        offset: usize,
877    ) -> Option<(StoredMeta, solana_sdk::account::AccountSharedData)> {
878        let sizes = self.get_account_sizes(&[offset]);
879        let result = self.get_stored_account_meta_callback(offset, |r_callback| {
880            let r2 = self.get_account_shared_data(offset);
881            assert!(accounts_equal(&r_callback, r2.as_ref().unwrap()));
882            assert_eq!(sizes, vec![r_callback.stored_size()]);
883            let meta = r_callback.meta().clone();
884            Some((meta, r_callback.to_account_shared_data()))
885        });
886        if result.is_none() {
887            assert!(self
888                .get_stored_account_meta_callback(offset, |_| {})
889                .is_none());
890            assert!(self.get_account_shared_data(offset).is_none());
891            // it has different rules for checking len and returning None
892            assert!(sizes.is_empty());
893        }
894        result.flatten()
895    }
896
897    /// Returns the path to the file where the data is stored
898    pub fn path(&self) -> &Path {
899        self.path.as_path()
900    }
901
902    /// help with the math of offsets when navigating the on-disk layout in an AppendVec.
903    /// data is at the end of each account and is variable sized
904    /// the next account is then aligned on a 64 bit boundary.
905    /// With these helpers, we can skip over reading some of the data depending on what the caller wants.
906    ///
907    /// *Safety* - The caller must ensure that the `stored_meta.data_len` won't overflow the calculation.
908    fn next_account_offset(start_offset: usize, stored_meta: &StoredMeta) -> AccountOffsets {
909        let stored_size_unaligned = STORE_META_OVERHEAD
910            .checked_add(stored_meta.data_len as usize)
911            .expect("stored size cannot overflow");
912        let stored_size_aligned = u64_align!(stored_size_unaligned);
913        let offset_to_end_of_data = start_offset + stored_size_unaligned;
914        let next_account_offset = start_offset + stored_size_aligned;
915
916        AccountOffsets {
917            next_account_offset,
918            offset_to_end_of_data,
919            stored_size_aligned,
920        }
921    }
922
923    /// Iterate over all accounts and call `callback` with `IndexInfo` for each.
924    /// This fn can help generate an index of the data in this storage.
925    pub(crate) fn scan_index(&self, mut callback: impl FnMut(IndexInfo)) {
926        // self.len() is an atomic load, so only do it once
927        let self_len = self.len();
928        match &self.backing {
929            AppendVecFileBacking::Mmap(Mmap { mmap, .. }) => {
930                let mut offset = 0;
931                let slice = self.get_valid_slice_from_mmap(mmap);
932                loop {
933                    let Some((stored_meta, next)) = Self::get_type::<StoredMeta>(slice, offset)
934                    else {
935                        // eof
936                        break;
937                    };
938                    let Some((account_meta, _)) = Self::get_type::<AccountMeta>(slice, next) else {
939                        // eof
940                        break;
941                    };
942                    if account_meta.lamports == 0 && stored_meta.pubkey == Pubkey::default() {
943                        // we passed the last useful account
944                        break;
945                    }
946                    let next = Self::next_account_offset(offset, stored_meta);
947                    if next.offset_to_end_of_data > self_len {
948                        // data doesn't fit, so don't include this account
949                        break;
950                    }
951                    callback(IndexInfo {
952                        index_info: {
953                            IndexInfoInner {
954                                pubkey: stored_meta.pubkey,
955                                lamports: account_meta.lamports,
956                                offset,
957                                data_len: stored_meta.data_len,
958                                executable: account_meta.executable,
959                                rent_epoch: account_meta.rent_epoch,
960                            }
961                        },
962                        stored_size_aligned: next.stored_size_aligned,
963                    });
964                    offset = next.next_account_offset;
965                }
966            }
967            AppendVecFileBacking::File(file) => {
968                let buffer_size = std::cmp::min(SCAN_BUFFER_SIZE, self_len);
969                let mut reader =
970                    BufferedReader::new(buffer_size, self_len, file, STORE_META_OVERHEAD);
971                while reader.read().ok() == Some(BufferedReaderStatus::Success) {
972                    let (offset, bytes) = reader.get_offset_and_data();
973                    let (stored_meta, next) = Self::get_type::<StoredMeta>(bytes, 0).unwrap();
974                    let (account_meta, _) = Self::get_type::<AccountMeta>(bytes, next).unwrap();
975                    if account_meta.lamports == 0 && stored_meta.pubkey == Pubkey::default() {
976                        // we passed the last useful account
977                        break;
978                    }
979                    let next = Self::next_account_offset(offset, stored_meta);
980                    if next.offset_to_end_of_data > self_len {
981                        // data doesn't fit, so don't include this account
982                        break;
983                    }
984                    callback(IndexInfo {
985                        index_info: {
986                            IndexInfoInner {
987                                pubkey: stored_meta.pubkey,
988                                lamports: account_meta.lamports,
989                                offset,
990                                data_len: stored_meta.data_len,
991                                executable: account_meta.executable,
992                                rent_epoch: account_meta.rent_epoch,
993                            }
994                        },
995                        stored_size_aligned: next.stored_size_aligned,
996                    });
997                    reader.advance_offset(next.stored_size_aligned);
998                }
999            }
1000        }
1001    }
1002
1003    /// Iterate over all accounts and call `callback` with each account.
1004    #[allow(clippy::blocks_in_conditions)]
1005    pub fn scan_accounts(&self, mut callback: impl for<'local> FnMut(StoredAccountMeta<'local>)) {
1006        match &self.backing {
1007            AppendVecFileBacking::Mmap(_mmap) => {
1008                let mut offset = 0;
1009                while self
1010                    .get_stored_account_meta_callback(offset, |account| {
1011                        offset += account.stored_size();
1012                        if account.is_zero_lamport() && account.pubkey() == &Pubkey::default() {
1013                            // we passed the last useful account
1014                            return false;
1015                        }
1016
1017                        callback(account);
1018                        true
1019                    })
1020                    .unwrap_or_default()
1021                {}
1022            }
1023            AppendVecFileBacking::File(file) => {
1024                let mut reader =
1025                    BufferedReader::new(SCAN_BUFFER_SIZE, self.len(), file, STORE_META_OVERHEAD);
1026                while reader.read().ok() == Some(BufferedReaderStatus::Success) {
1027                    let (offset, bytes_subset) = reader.get_offset_and_data();
1028                    let (meta, next): (&StoredMeta, _) = Self::get_type(bytes_subset, 0).unwrap();
1029                    let (account_meta, next): (&AccountMeta, _) =
1030                        Self::get_type(bytes_subset, next).unwrap();
1031                    let (hash, next): (&AccountHash, _) =
1032                        Self::get_type(bytes_subset, next).unwrap();
1033                    let data_len = meta.data_len;
1034                    if bytes_subset.len() - next >= data_len as usize {
1035                        // we already read enough data to load this account
1036                        let data = &bytes_subset.0[next..(next + data_len as usize)];
1037                        let stored_size = u64_align!(next + (data_len as usize));
1038                        let account = StoredAccountMeta::AppendVec(AppendVecStoredAccountMeta {
1039                            meta,
1040                            account_meta,
1041                            data,
1042                            offset,
1043                            stored_size,
1044                            hash,
1045                        });
1046                        callback(account);
1047                        reader.advance_offset(stored_size);
1048                    } else {
1049                        // fall through and read the whole account again. we need refs for StoredMeta and data.
1050                        reader.set_required_data_len(
1051                            STORE_META_OVERHEAD.saturating_add(data_len as usize),
1052                        )
1053                    }
1054                }
1055            }
1056        }
1057    }
1058
1059    /// for each offset in `sorted_offsets`, get the size of the account. No other information is needed for the account.
1060    pub(crate) fn get_account_sizes(&self, sorted_offsets: &[usize]) -> Vec<usize> {
1061        // self.len() is an atomic load, so only do it once
1062        let self_len = self.len();
1063        let mut account_sizes = Vec::with_capacity(sorted_offsets.len());
1064        match &self.backing {
1065            AppendVecFileBacking::Mmap(Mmap { mmap, .. }) => {
1066                let slice = self.get_valid_slice_from_mmap(mmap);
1067                for &offset in sorted_offsets {
1068                    let Some((stored_meta, _)) = Self::get_type::<StoredMeta>(slice, offset) else {
1069                        break;
1070                    };
1071                    let next = Self::next_account_offset(offset, stored_meta);
1072                    if next.offset_to_end_of_data > self_len {
1073                        // data doesn't fit, so don't include
1074                        break;
1075                    }
1076                    account_sizes.push(next.stored_size_aligned);
1077                }
1078            }
1079            AppendVecFileBacking::File(file) => {
1080                let mut buffer = [MaybeUninit::<u8>::uninit(); mem::size_of::<StoredMeta>()];
1081                for &offset in sorted_offsets {
1082                    // SAFETY: `read_into_buffer` will only write to uninitialized memory.
1083                    let Some(bytes_read) = read_into_buffer(file, self_len, offset, unsafe {
1084                        slice::from_raw_parts_mut(
1085                            buffer.as_mut_ptr() as *mut u8,
1086                            mem::size_of::<StoredMeta>(),
1087                        )
1088                    })
1089                    .ok() else {
1090                        break;
1091                    };
1092                    // SAFETY: we only read the initialized portion.
1093                    let bytes = ValidSlice(unsafe {
1094                        slice::from_raw_parts(buffer.as_ptr() as *const u8, bytes_read)
1095                    });
1096                    let Some((stored_meta, _)) = Self::get_type::<StoredMeta>(bytes, 0) else {
1097                        break;
1098                    };
1099                    let next = Self::next_account_offset(offset, stored_meta);
1100                    if next.offset_to_end_of_data > self_len {
1101                        // data doesn't fit, so don't include
1102                        break;
1103                    }
1104                    account_sizes.push(next.stored_size_aligned);
1105                }
1106            }
1107        }
1108        account_sizes
1109    }
1110
1111    /// iterate over all pubkeys and call `callback`.
1112    /// This iteration does not deserialize and populate each field in `StoredAccountMeta`.
1113    /// `data` is completely ignored, for example.
1114    /// Also, no references have to be maintained/returned from an iterator function.
1115    /// This fn can operate on a batch of data at once.
1116    pub fn scan_pubkeys(&self, mut callback: impl FnMut(&Pubkey)) {
1117        // self.len() is an atomic load, so only do it once
1118        let self_len = self.len();
1119        match &self.backing {
1120            AppendVecFileBacking::Mmap(Mmap { mmap, .. }) => {
1121                let mut offset = 0;
1122                let slice = self.get_valid_slice_from_mmap(mmap);
1123                loop {
1124                    let Some((stored_meta, _)) = Self::get_type::<StoredMeta>(slice, offset) else {
1125                        // eof
1126                        break;
1127                    };
1128                    let next = Self::next_account_offset(offset, stored_meta);
1129                    if next.offset_to_end_of_data > self_len {
1130                        // data doesn't fit, so don't include this pubkey
1131                        break;
1132                    }
1133                    callback(&stored_meta.pubkey);
1134                    offset = next.next_account_offset;
1135                }
1136            }
1137            AppendVecFileBacking::File(file) => {
1138                let buffer_size = std::cmp::min(SCAN_BUFFER_SIZE_WITHOUT_DATA, self_len);
1139                let mut reader =
1140                    BufferedReader::new(buffer_size, self_len, file, STORE_META_OVERHEAD);
1141                while reader.read().ok() == Some(BufferedReaderStatus::Success) {
1142                    let (offset, bytes) = reader.get_offset_and_data();
1143                    let (stored_meta, _) = Self::get_type::<StoredMeta>(bytes, 0).unwrap();
1144                    let next = Self::next_account_offset(offset, stored_meta);
1145                    if next.offset_to_end_of_data > self.len() {
1146                        // data doesn't fit, so don't include this pubkey
1147                        break;
1148                    }
1149                    callback(&stored_meta.pubkey);
1150                    // since we only needed to read the pubkey, skip ahead to the next account
1151                    reader.advance_offset(next.stored_size_aligned);
1152                }
1153            }
1154        }
1155    }
1156
1157    /// Copy each account metadata, account and hash to the internal buffer.
1158    /// If there is no room to write the first entry, None is returned.
1159    /// Otherwise, returns the starting offset of each account metadata.
1160    /// Plus, the final return value is the offset where the next entry would be appended.
1161    /// So, return.len() is 1 + (number of accounts written)
1162    /// After each account is appended, the internal `current_len` is updated
1163    /// and will be available to other threads.
1164    pub fn append_accounts<'a>(
1165        &self,
1166        accounts: &impl StorableAccounts<'a>,
1167        skip: usize,
1168    ) -> Option<StoredAccountsInfo> {
1169        let _lock = self.append_lock.lock().unwrap();
1170        let default_hash = Hash::default();
1171        let mut offset = self.len();
1172        let len = accounts.len();
1173        // Here we have `len - skip` number of accounts.  The +1 extra capacity
1174        // is for storing the aligned offset of the last-plus-one entry,
1175        // which is used to compute the size of the last stored account.
1176        let offsets_len = len - skip + 1;
1177        let mut offsets = Vec::with_capacity(offsets_len);
1178        let mut stop = false;
1179        for i in skip..len {
1180            if stop {
1181                break;
1182            }
1183            accounts.account_default_if_zero_lamport(i, |account| {
1184                let account_meta = AccountMeta {
1185                    lamports: account.lamports(),
1186                    owner: *account.owner(),
1187                    rent_epoch: account.rent_epoch(),
1188                    executable: account.executable(),
1189                };
1190
1191                let stored_meta = StoredMeta {
1192                    pubkey: *account.pubkey(),
1193                    data_len: account.data().len() as u64,
1194                    write_version_obsolete: 0,
1195                };
1196                let stored_meta_ptr = ptr::from_ref(&stored_meta).cast();
1197                let account_meta_ptr = ptr::from_ref(&account_meta).cast();
1198                let hash_ptr = bytemuck::bytes_of(&default_hash).as_ptr();
1199                let data_ptr = account.data().as_ptr();
1200                let ptrs = [
1201                    (stored_meta_ptr, mem::size_of::<StoredMeta>()),
1202                    (account_meta_ptr, mem::size_of::<AccountMeta>()),
1203                    (hash_ptr, mem::size_of::<AccountHash>()),
1204                    (data_ptr, stored_meta.data_len as usize),
1205                ];
1206                if let Some(start_offset) = self.append_ptrs_locked(&mut offset, &ptrs) {
1207                    offsets.push(start_offset)
1208                } else {
1209                    stop = true;
1210                }
1211            });
1212        }
1213
1214        match &self.backing {
1215            AppendVecFileBacking::Mmap(mmap_only) => {
1216                if !offsets.is_empty() {
1217                    // If we've actually written to the AppendVec, make sure we mark it as dirty.
1218                    // This ensures we properly flush it later.
1219                    // As an optimization to reduce unnecessary cache line invalidations,
1220                    // only write the `is_dirty` atomic if currently *not* dirty.
1221                    // (This also ensures the 'dirty counter' datapoint is correct.)
1222                    if !mmap_only.is_dirty.load(Ordering::Acquire) {
1223                        mmap_only.is_dirty.store(true, Ordering::Release);
1224                        APPEND_VEC_STATS
1225                            .mmap_files_dirty
1226                            .fetch_add(1, Ordering::Relaxed);
1227                    }
1228                }
1229            }
1230            AppendVecFileBacking::File(_) => {}
1231        }
1232
1233        (!offsets.is_empty()).then(|| {
1234            // The last entry in the offsets needs to be the u64 aligned `offset`, because that's
1235            // where the *next* entry will begin to be stored.
1236            // This is used to compute the size of the last stored account; make sure to remove
1237            // it afterwards!
1238            offsets.push(u64_align!(offset));
1239            let size = offsets.windows(2).map(|offset| offset[1] - offset[0]).sum();
1240            offsets.pop();
1241
1242            StoredAccountsInfo { offsets, size }
1243        })
1244    }
1245
1246    pub(crate) fn can_append(&self) -> bool {
1247        match &self.backing {
1248            AppendVecFileBacking::File(_file) => false,
1249            AppendVecFileBacking::Mmap(_mmap) => true,
1250        }
1251    }
1252
1253    /// Returns the way to access this accounts file when archiving
1254    pub(crate) fn internals_for_archive(&self) -> InternalsForArchive {
1255        match &self.backing {
1256            AppendVecFileBacking::File(_file) => InternalsForArchive::FileIo(self.path()),
1257            // note this returns the entire mmap slice, even bytes that we consider invalid
1258            AppendVecFileBacking::Mmap(Mmap { mmap, .. }) => InternalsForArchive::Mmap(mmap),
1259        }
1260    }
1261}
1262
1263#[cfg(test)]
1264pub mod tests {
1265    use {
1266        super::{test_utils::*, *},
1267        assert_matches::assert_matches,
1268        memoffset::offset_of,
1269        rand::{thread_rng, Rng},
1270        solana_sdk::{
1271            account::{Account, AccountSharedData},
1272            clock::Slot,
1273        },
1274        std::{mem::ManuallyDrop, time::Instant},
1275        test_case::test_case,
1276    };
1277
1278    impl AppendVec {
1279        pub(crate) fn set_current_len_for_tests(&self, len: usize) {
1280            self.current_len.store(len, Ordering::Release);
1281        }
1282
1283        fn append_account_test(&self, data: &(StoredMeta, AccountSharedData)) -> Option<usize> {
1284            let slot_ignored = Slot::MAX;
1285            let accounts = [(&data.0.pubkey, &data.1)];
1286            let slice = &accounts[..];
1287            let storable_accounts = (slot_ignored, slice);
1288
1289            self.append_accounts(&storable_accounts, 0)
1290                .map(|res| res.offsets[0])
1291        }
1292    }
1293
1294    impl StoredAccountMeta<'_> {
1295        pub(crate) fn ref_executable_byte(&self) -> &u8 {
1296            match self {
1297                Self::AppendVec(av) => av.ref_executable_byte(),
1298                // Tests currently only cover AppendVec.
1299                Self::Hot(_) => unreachable!(),
1300            }
1301        }
1302    }
1303
1304    impl AppendVecStoredAccountMeta<'_> {
1305        fn set_data_len_unsafe(&self, new_data_len: u64) {
1306            // UNSAFE: cast away & (= const ref) to &mut to force to mutate append-only (=read-only) AppendVec
1307            unsafe {
1308                #[allow(invalid_reference_casting)]
1309                ptr::write(
1310                    std::mem::transmute::<*const u64, *mut u64>(&self.meta.data_len),
1311                    new_data_len,
1312                );
1313            }
1314        }
1315
1316        fn get_executable_byte(&self) -> u8 {
1317            let executable_bool: bool = self.executable();
1318            // UNSAFE: Force to interpret mmap-backed bool as u8 to really read the actual memory content
1319            let executable_byte: u8 = unsafe { std::mem::transmute::<bool, u8>(executable_bool) };
1320            executable_byte
1321        }
1322
1323        fn set_executable_as_byte(&self, new_executable_byte: u8) {
1324            // UNSAFE: Force to interpret mmap-backed &bool as &u8 to write some crafted value;
1325            unsafe {
1326                #[allow(invalid_reference_casting)]
1327                ptr::write(
1328                    std::mem::transmute::<*const bool, *mut u8>(&self.account_meta.executable),
1329                    new_executable_byte,
1330                );
1331            }
1332        }
1333    }
1334
1335    // Hash is [u8; 32], which has no alignment
1336    static_assertions::assert_eq_align!(u64, StoredMeta, AccountMeta);
1337
1338    #[test]
1339    fn test_account_meta_default() {
1340        let def1 = AccountMeta::default();
1341        let def2 = AccountMeta::from(&Account::default());
1342        assert_eq!(&def1, &def2);
1343        let def2 = AccountMeta::from(&AccountSharedData::default());
1344        assert_eq!(&def1, &def2);
1345        let def2 = AccountMeta::from(Some(&AccountSharedData::default()));
1346        assert_eq!(&def1, &def2);
1347        let none: Option<&AccountSharedData> = None;
1348        let def2 = AccountMeta::from(none);
1349        assert_eq!(&def1, &def2);
1350    }
1351
1352    #[test]
1353    fn test_account_meta_non_default() {
1354        let def1 = AccountMeta {
1355            lamports: 1,
1356            owner: Pubkey::new_unique(),
1357            executable: true,
1358            rent_epoch: 3,
1359        };
1360        let def2_account = Account {
1361            lamports: def1.lamports,
1362            owner: def1.owner,
1363            executable: def1.executable,
1364            rent_epoch: def1.rent_epoch,
1365            data: Vec::new(),
1366        };
1367        let def2 = AccountMeta::from(&def2_account);
1368        assert_eq!(&def1, &def2);
1369        let def2 = AccountMeta::from(&AccountSharedData::from(def2_account.clone()));
1370        assert_eq!(&def1, &def2);
1371        let def2 = AccountMeta::from(Some(&AccountSharedData::from(def2_account)));
1372        assert_eq!(&def1, &def2);
1373    }
1374
1375    #[test]
1376    #[should_panic(expected = "AppendVecError(FileSizeTooSmall(0))")]
1377    fn test_append_vec_new_bad_size() {
1378        let path = get_append_vec_path("test_append_vec_new_bad_size");
1379        let _av = AppendVec::new(&path.path, true, 0);
1380    }
1381
1382    #[test_case(StorageAccess::Mmap)]
1383    #[test_case(StorageAccess::File)]
1384    fn test_append_vec_new_from_file_bad_size(storage_access: StorageAccess) {
1385        let file = get_append_vec_path("test_append_vec_new_from_file_bad_size");
1386        let path = &file.path;
1387
1388        let _data = OpenOptions::new()
1389            .read(true)
1390            .write(true)
1391            .create_new(true)
1392            .open(path)
1393            .expect("create a test file for mmap");
1394
1395        let result = AppendVec::new_from_file(path, 0, storage_access);
1396        assert_matches!(result, Err(ref message) if message.to_string().contains("too small file size 0 for AppendVec"));
1397    }
1398
1399    #[test]
1400    fn test_append_vec_sanitize_len_and_size_too_small() {
1401        const LEN: usize = 0;
1402        const SIZE: usize = 0;
1403        let result = AppendVec::sanitize_len_and_size(LEN, SIZE);
1404        assert_matches!(result, Err(ref message) if message.to_string().contains("too small file size 0 for AppendVec"));
1405    }
1406
1407    #[test]
1408    fn test_append_vec_sanitize_len_and_size_maximum() {
1409        const LEN: usize = 0;
1410        const SIZE: usize = 16 * 1024 * 1024 * 1024;
1411        let result = AppendVec::sanitize_len_and_size(LEN, SIZE);
1412        assert_matches!(result, Ok(_));
1413    }
1414
1415    #[test]
1416    fn test_append_vec_sanitize_len_and_size_too_large() {
1417        const LEN: usize = 0;
1418        const SIZE: usize = 16 * 1024 * 1024 * 1024 + 1;
1419        let result = AppendVec::sanitize_len_and_size(LEN, SIZE);
1420        assert_matches!(result, Err(ref message) if message.to_string().contains("too large file size 17179869185 for AppendVec"));
1421    }
1422
1423    #[test]
1424    fn test_append_vec_sanitize_len_and_size_full_and_same_as_current_len() {
1425        const LEN: usize = 1024 * 1024;
1426        const SIZE: usize = 1024 * 1024;
1427        let result = AppendVec::sanitize_len_and_size(LEN, SIZE);
1428        assert_matches!(result, Ok(_));
1429    }
1430
1431    #[test]
1432    fn test_append_vec_sanitize_len_and_size_larger_current_len() {
1433        const LEN: usize = 1024 * 1024 + 1;
1434        const SIZE: usize = 1024 * 1024;
1435        let result = AppendVec::sanitize_len_and_size(LEN, SIZE);
1436        assert_matches!(result, Err(ref message) if message.to_string().contains("is larger than file size (1048576)"));
1437    }
1438
1439    #[test]
1440    fn test_append_vec_one() {
1441        let path = get_append_vec_path("test_append");
1442        let av = AppendVec::new(&path.path, true, 1024 * 1024);
1443        let account = create_test_account(0);
1444        let index = av.append_account_test(&account).unwrap();
1445        assert_eq!(av.get_account_test(index).unwrap(), account);
1446        truncate_and_test(av, index);
1447    }
1448
1449    /// truncate `av` and make sure that we fail to get an account. This verifies that the eof
1450    /// code is working correctly.
1451    fn truncate_and_test(av: AppendVec, index: usize) {
1452        // truncate the hash, 1 byte at a time
1453        let hash = std::mem::size_of::<AccountHash>();
1454        for _ in 0..hash {
1455            av.current_len.fetch_sub(1, Ordering::Relaxed);
1456            assert_eq!(av.get_account_test(index), None);
1457        }
1458        // truncate 1 byte into the AccountMeta
1459        av.current_len.fetch_sub(1, Ordering::Relaxed);
1460        assert_eq!(av.get_account_test(index), None);
1461    }
1462
1463    #[test]
1464    fn test_append_vec_one_with_data() {
1465        let path = get_append_vec_path("test_append");
1466        let av = AppendVec::new(&path.path, true, 1024 * 1024);
1467        let data_len = 1;
1468        let account = create_test_account(data_len);
1469        let index = av.append_account_test(&account).unwrap();
1470        // make the append vec 1 byte too short. we should get `None` since the append vec was truncated
1471        assert_eq!(
1472            STORE_META_OVERHEAD + data_len,
1473            av.current_len.load(Ordering::Relaxed)
1474        );
1475        assert_eq!(av.get_account_test(index).unwrap(), account);
1476        truncate_and_test(av, index);
1477    }
1478
1479    #[test]
1480    fn test_remaining_bytes() {
1481        let path = get_append_vec_path("test_append");
1482        let sz = 1024 * 1024;
1483        let sz64 = sz as u64;
1484        let av = AppendVec::new(&path.path, true, sz);
1485        assert_eq!(av.capacity(), sz64);
1486        assert_eq!(av.remaining_bytes(), sz64);
1487
1488        // append first account, an u64 aligned account (136 bytes)
1489        let mut av_len = 0;
1490        let account = create_test_account(0);
1491        av.append_account_test(&account).unwrap();
1492        av_len += STORE_META_OVERHEAD;
1493        assert_eq!(av.capacity(), sz64);
1494        assert_eq!(av.remaining_bytes(), sz64 - (STORE_META_OVERHEAD as u64));
1495        assert_eq!(av.len(), av_len);
1496
1497        // append second account, a *not* u64 aligned account (137 bytes)
1498        let account = create_test_account(1);
1499        let account_storage_len = STORE_META_OVERHEAD + 1;
1500        av_len += account_storage_len;
1501        av.append_account_test(&account).unwrap();
1502        assert_eq!(av.capacity(), sz64);
1503        assert_eq!(av.len(), av_len);
1504        let alignment_bytes = u64_align!(av_len) - av_len; // bytes used for alignment (7 bytes)
1505        assert_eq!(alignment_bytes, 7);
1506        assert_eq!(av.remaining_bytes(), sz64 - u64_align!(av_len) as u64);
1507
1508        // append third account, a *not* u64 aligned account (137 bytes)
1509        let account = create_test_account(1);
1510        av.append_account_test(&account).unwrap();
1511        let account_storage_len = STORE_META_OVERHEAD + 1;
1512        av_len += alignment_bytes; // bytes used for alignment at the end of previous account
1513        av_len += account_storage_len;
1514        assert_eq!(av.capacity(), sz64);
1515        assert_eq!(av.len(), av_len);
1516        assert_eq!(av.remaining_bytes(), sz64 - u64_align!(av_len) as u64);
1517    }
1518
1519    #[test]
1520    fn test_append_vec_data() {
1521        let path = get_append_vec_path("test_append_data");
1522        let av = AppendVec::new(&path.path, true, 1024 * 1024);
1523        let account = create_test_account(5);
1524        let index = av.append_account_test(&account).unwrap();
1525        assert_eq!(av.get_account_test(index).unwrap(), account);
1526        let account1 = create_test_account(6);
1527        let index1 = av.append_account_test(&account1).unwrap();
1528        assert_eq!(av.get_account_test(index).unwrap(), account);
1529        assert_eq!(av.get_account_test(index1).unwrap(), account1);
1530    }
1531
1532    #[test]
1533    fn test_account_matches_owners() {
1534        let path = get_append_vec_path("test_append_data");
1535        let av = AppendVec::new(&path.path, true, 1024 * 1024);
1536        let owners: Vec<Pubkey> = (0..2).map(|_| Pubkey::new_unique()).collect();
1537
1538        let mut account = create_test_account(5);
1539        account.1.set_owner(owners[0]);
1540        let index = av.append_account_test(&account).unwrap();
1541        assert_eq!(av.account_matches_owners(index, &owners), Ok(0));
1542
1543        let mut account1 = create_test_account(6);
1544        account1.1.set_owner(owners[1]);
1545        let index1 = av.append_account_test(&account1).unwrap();
1546        assert_eq!(av.account_matches_owners(index1, &owners), Ok(1));
1547        assert_eq!(av.account_matches_owners(index, &owners), Ok(0));
1548
1549        let mut account2 = create_test_account(6);
1550        account2.1.set_owner(Pubkey::new_unique());
1551        let index2 = av.append_account_test(&account2).unwrap();
1552        assert_eq!(
1553            av.account_matches_owners(index2, &owners),
1554            Err(MatchAccountOwnerError::NoMatch)
1555        );
1556
1557        // tests for overflow
1558        assert_eq!(
1559            av.account_matches_owners(usize::MAX - mem::size_of::<StoredMeta>(), &owners),
1560            Err(MatchAccountOwnerError::UnableToLoad)
1561        );
1562
1563        assert_eq!(
1564            av.account_matches_owners(
1565                usize::MAX - mem::size_of::<StoredMeta>() - mem::size_of::<AccountMeta>() + 1,
1566                &owners
1567            ),
1568            Err(MatchAccountOwnerError::UnableToLoad)
1569        );
1570    }
1571
1572    impl AppendVec {
1573        /// return how many accounts in the storage
1574        fn accounts_count(&self) -> usize {
1575            let mut count = 0;
1576            self.scan_accounts(|_| {
1577                count += 1;
1578            });
1579            count
1580        }
1581    }
1582
1583    #[test]
1584    fn test_append_vec_append_many() {
1585        let path = get_append_vec_path("test_append_many");
1586        let av = AppendVec::new(&path.path, true, 1024 * 1024);
1587        let size = 1000;
1588        let mut indexes = vec![];
1589        let now = Instant::now();
1590        let mut sizes = vec![];
1591        for sample in 0..size {
1592            // sample + 1 is so sample = 0 won't be used.
1593            // sample = 0 produces default account with default pubkey
1594            let account = create_test_account(sample + 1);
1595            sizes.push(aligned_stored_size(account.1.data().len()));
1596            let pos = av.append_account_test(&account).unwrap();
1597            assert_eq!(av.get_account_test(pos).unwrap(), account);
1598            indexes.push(pos);
1599            assert_eq!(sizes, av.get_account_sizes(&indexes));
1600        }
1601        trace!("append time: {} ms", now.elapsed().as_millis());
1602
1603        let now = Instant::now();
1604        for _ in 0..size {
1605            let sample = thread_rng().gen_range(0..indexes.len());
1606            let account = create_test_account(sample + 1);
1607            assert_eq!(av.get_account_test(indexes[sample]).unwrap(), account);
1608        }
1609        trace!("random read time: {} ms", now.elapsed().as_millis());
1610
1611        let now = Instant::now();
1612        assert_eq!(indexes.len(), size);
1613        assert_eq!(indexes[0], 0);
1614        let mut sample = 0;
1615        assert_eq!(av.accounts_count(), size);
1616        av.scan_accounts(|v| {
1617            let account = create_test_account(sample + 1);
1618            let recovered = v.to_account_shared_data();
1619            assert_eq!(recovered, account.1);
1620            sample += 1;
1621        });
1622        trace!("sequential read time: {} ms", now.elapsed().as_millis());
1623    }
1624
1625    #[test_case(StorageAccess::Mmap)]
1626    #[test_case(StorageAccess::File)]
1627    fn test_new_from_file_crafted_zero_lamport_account(storage_access: StorageAccess) {
1628        // This test verifies that when we sanitize on load, that we fail sanitizing if we load an account with zero lamports that does not have all default value fields.
1629        // This test writes an account with zero lamports, but with 3 bytes of data. On load, it asserts that load fails.
1630        // It used to be possible to use the append vec api to write an account to an append vec with zero lamports, but with non-default values for other account fields.
1631        // This will no longer be possible. Thus, to implement the write portion of this test would require additional test-only parameters to public apis or otherwise duplicating code paths.
1632        // So, the sanitizing on load behavior can be tested by capturing [u8] that would be created if such a write was possible (as it used to be).
1633        // The contents of [u8] written by an append vec cannot easily or reasonably change frequently since it has released a long time.
1634        /*
1635            solana_logger::setup();
1636            // uncomment this code to generate the invalid append vec that will fail on load
1637            let file = get_append_vec_path("test_append");
1638            let path = &file.path;
1639            let mut av = AppendVec::new(path, true, 256);
1640            av.set_no_remove_on_drop();
1641
1642            let pubkey = solana_pubkey::new_rand();
1643            let owner = Pubkey::default();
1644            let data_len = 3_u64;
1645            let mut account = AccountSharedData::new(0, data_len as usize, &owner);
1646            account.set_data(b"abc".to_vec());
1647            let stored_meta = StoredMeta {
1648                write_version: 0,
1649                pubkey,
1650                data_len,
1651            };
1652            let account_with_meta = (stored_meta, account);
1653            let index = av.append_account_test(&account_with_meta).unwrap();
1654            assert_eq!(av.get_account_test(index).unwrap(), account_with_meta);
1655
1656            av.flush().unwrap();
1657            let accounts_len = av.len();
1658            drop(av);
1659            // read file and log out as [u8]
1660            use std::fs::File;
1661            use std::io::BufReader;
1662            use std::io::Read;
1663            let f = File::open(path).unwrap();
1664            let mut reader = BufReader::new(f);
1665            let mut buffer = Vec::new();
1666            reader.read_to_end(&mut buffer).unwrap();
1667            error!("{:?}", buffer);
1668        */
1669
1670        // create an invalid append vec file using known bytes
1671        let file = get_append_vec_path("test_append_bytes");
1672        let path = &file.path;
1673
1674        let accounts_len = 139;
1675        {
1676            let append_vec_data = [
1677                0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 192, 118, 150, 1, 185, 209, 118,
1678                82, 154, 222, 172, 202, 110, 26, 218, 140, 143, 96, 61, 43, 212, 73, 203, 7, 190,
1679                88, 80, 222, 110, 114, 67, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1680                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1681                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1682                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 97, 98, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1683                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1684                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1685                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1686                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1687            ];
1688
1689            let f = std::fs::File::create(path).unwrap();
1690            let mut writer = std::io::BufWriter::new(f);
1691            writer.write_all(append_vec_data.as_slice()).unwrap();
1692        }
1693
1694        let result = AppendVec::new_from_file(path, accounts_len, storage_access);
1695        assert_matches!(result, Err(ref message) if message.to_string().contains("incorrect layout/length/data"));
1696    }
1697
1698    #[test_case(StorageAccess::Mmap)]
1699    #[test_case(StorageAccess::File)]
1700    fn test_new_from_file_crafted_data_len(storage_access: StorageAccess) {
1701        let file = get_append_vec_path("test_new_from_file_crafted_data_len");
1702        let path = &file.path;
1703        let accounts_len = {
1704            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1705            let av = ManuallyDrop::new(AppendVec::new(path, true, 1024 * 1024));
1706
1707            let crafted_data_len = 1;
1708
1709            av.append_account_test(&create_test_account(10)).unwrap();
1710
1711            av.get_stored_account_meta_callback(0, |account| {
1712                let StoredAccountMeta::AppendVec(account) = account else {
1713                    panic!("StoredAccountMeta can only be AppendVec in this test.");
1714                };
1715                account.set_data_len_unsafe(crafted_data_len);
1716                assert_eq!(account.data_len(), crafted_data_len);
1717
1718                // Reload accounts and observe crafted_data_len
1719                av.get_stored_account_meta_callback(0, |account| {
1720                    assert_eq!(account.data_len() as u64, crafted_data_len);
1721                });
1722            });
1723
1724            av.flush().unwrap();
1725            av.len()
1726        };
1727        let result = AppendVec::new_from_file(path, accounts_len, storage_access);
1728        assert_matches!(result, Err(ref message) if message.to_string().contains("incorrect layout/length/data"));
1729    }
1730
1731    #[test]
1732    fn test_append_vec_reset() {
1733        let file = get_append_vec_path("test_append_vec_reset");
1734        let path = &file.path;
1735        let av = AppendVec::new(path, true, 1024 * 1024);
1736        av.append_account_test(&create_test_account(10)).unwrap();
1737
1738        assert!(!av.is_empty());
1739        av.reset();
1740        assert_eq!(av.len(), 0);
1741    }
1742
1743    #[test_case(StorageAccess::Mmap)]
1744    #[test_case(StorageAccess::File)]
1745    fn test_append_vec_flush(storage_access: StorageAccess) {
1746        let file = get_append_vec_path("test_append_vec_flush");
1747        let path = &file.path;
1748        let accounts_len = {
1749            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1750            let av = ManuallyDrop::new(AppendVec::new(path, true, 1024 * 1024));
1751            av.append_account_test(&create_test_account(10)).unwrap();
1752            av.len()
1753        };
1754
1755        let (av, num_account) =
1756            AppendVec::new_from_file(path, accounts_len, storage_access).unwrap();
1757        av.flush().unwrap();
1758        assert_eq!(num_account, 1);
1759    }
1760
1761    #[test_case(StorageAccess::Mmap)]
1762    #[test_case(StorageAccess::File)]
1763    fn test_append_vec_reopen_as_readonly(storage_access: StorageAccess) {
1764        let file = get_append_vec_path("test_append_vec_flush");
1765        let path = &file.path;
1766        let accounts_len = {
1767            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1768            let av = ManuallyDrop::new(AppendVec::new(path, true, 1024 * 1024));
1769            av.append_account_test(&create_test_account(10)).unwrap();
1770            av.len()
1771        };
1772        let (av, _) = AppendVec::new_from_file(path, accounts_len, storage_access).unwrap();
1773        let reopen = av.reopen_as_readonly();
1774        if storage_access == StorageAccess::File {
1775            assert!(reopen.is_none());
1776        } else {
1777            assert!(reopen.is_some());
1778        }
1779    }
1780
1781    #[test_case(StorageAccess::Mmap)]
1782    #[test_case(StorageAccess::File)]
1783    fn test_new_from_file_too_large_data_len(storage_access: StorageAccess) {
1784        let file = get_append_vec_path("test_new_from_file_too_large_data_len");
1785        let path = &file.path;
1786        let accounts_len = {
1787            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1788            let av = ManuallyDrop::new(AppendVec::new(path, true, 1024 * 1024));
1789
1790            let too_large_data_len = u64::MAX;
1791            av.append_account_test(&create_test_account(10)).unwrap();
1792
1793            av.get_stored_account_meta_callback(0, |account| {
1794                let StoredAccountMeta::AppendVec(account) = account else {
1795                    panic!("StoredAccountMeta can only be AppendVec in this test.");
1796                };
1797                account.set_data_len_unsafe(too_large_data_len);
1798                assert_eq!(account.data_len(), too_large_data_len);
1799            })
1800            .unwrap();
1801
1802            // Reload accounts and observe no account with bad offset
1803            assert!(av
1804                .get_stored_account_meta_callback(0, |_| {
1805                    panic!("unexpected");
1806                })
1807                .is_none());
1808            av.flush().unwrap();
1809            av.len()
1810        };
1811        let result = AppendVec::new_from_file(path, accounts_len, storage_access);
1812        assert_matches!(result, Err(ref message) if message.to_string().contains("incorrect layout/length/data"));
1813    }
1814
1815    #[test_case(StorageAccess::Mmap)]
1816    #[test_case(StorageAccess::File)]
1817    fn test_new_from_file_crafted_executable(storage_access: StorageAccess) {
1818        let file = get_append_vec_path("test_new_from_crafted_executable");
1819        let path = &file.path;
1820        let accounts_len = {
1821            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1822            let av = ManuallyDrop::new(AppendVec::new(path, true, 1024 * 1024));
1823            av.append_account_test(&create_test_account(10)).unwrap();
1824            let offset_1 = {
1825                let mut executable_account = create_test_account(10);
1826                executable_account.1.set_executable(true);
1827                av.append_account_test(&executable_account).unwrap()
1828            };
1829
1830            let crafted_executable = u8::MAX - 1;
1831
1832            // reload accounts
1833            // ensure false is 0u8 and true is 1u8 actually
1834            av.get_stored_account_meta_callback(0, |account| {
1835                assert_eq!(*account.ref_executable_byte(), 0);
1836                let StoredAccountMeta::AppendVec(account) = account else {
1837                    panic!("StoredAccountMeta can only be AppendVec in this test.");
1838                };
1839                account.set_executable_as_byte(crafted_executable);
1840            })
1841            .unwrap();
1842            av.get_stored_account_meta_callback(offset_1, |account| {
1843                assert_eq!(*account.ref_executable_byte(), 1);
1844            })
1845            .unwrap();
1846
1847            // reload crafted accounts
1848            av.get_stored_account_meta_callback(0, |account| {
1849                let StoredAccountMeta::AppendVec(account) = account else {
1850                    panic!("StoredAccountMeta can only be AppendVec in this test.");
1851                };
1852
1853                // upper 7-bits are not 0, so sanitization should fail
1854                assert!(!account.sanitize_executable());
1855
1856                // we can observe crafted value by ref
1857                {
1858                    let executable_bool: &bool = &account.account_meta.executable;
1859                    // Depending on use, *executable_bool can be truthy or falsy due to direct memory manipulation
1860                    // assert_eq! thinks *executable_bool is equal to false but the if condition thinks it's not, contradictorily.
1861                    assert!(!*executable_bool);
1862                    assert_eq!(*account.ref_executable_byte(), crafted_executable);
1863                }
1864
1865                // we can NOT observe crafted value by value
1866                {
1867                    let executable_bool: bool = account.executable();
1868                    assert!(!executable_bool);
1869                    assert_eq!(account.get_executable_byte(), 0); // Wow, not crafted_executable!
1870                }
1871            })
1872            .unwrap();
1873
1874            av.flush().unwrap();
1875            av.len()
1876        };
1877        let result = AppendVec::new_from_file(path, accounts_len, storage_access);
1878        assert_matches!(result, Err(ref message) if message.to_string().contains("incorrect layout/length/data"));
1879    }
1880
1881    #[test]
1882    fn test_type_layout() {
1883        assert_eq!(offset_of!(StoredMeta, write_version_obsolete), 0x00);
1884        assert_eq!(offset_of!(StoredMeta, data_len), 0x08);
1885        assert_eq!(offset_of!(StoredMeta, pubkey), 0x10);
1886        assert_eq!(mem::size_of::<StoredMeta>(), 0x30);
1887
1888        assert_eq!(offset_of!(AccountMeta, lamports), 0x00);
1889        assert_eq!(offset_of!(AccountMeta, rent_epoch), 0x08);
1890        assert_eq!(offset_of!(AccountMeta, owner), 0x10);
1891        assert_eq!(offset_of!(AccountMeta, executable), 0x30);
1892        assert_eq!(mem::size_of::<AccountMeta>(), 0x38);
1893    }
1894
1895    #[test_case(StorageAccess::Mmap)]
1896    #[test_case(StorageAccess::File)]
1897    fn test_get_account_shared_data_from_truncated_file(storage_access: StorageAccess) {
1898        let file = get_append_vec_path("test_get_account_shared_data_from_truncated_file");
1899        let path = &file.path;
1900
1901        {
1902            // Set up a test account with data_len larger than PAGE_SIZE (i.e.
1903            // AppendVec internal buffer size is PAGESIZE).
1904            let data_len: usize = 2 * PAGE_SIZE as usize;
1905            let account = create_test_account_with(data_len);
1906            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1907            let av = ManuallyDrop::new(AppendVec::new(path, true, aligned_stored_size(data_len)));
1908            av.append_account_test(&account).unwrap();
1909            av.flush().unwrap();
1910        }
1911
1912        // Truncate the AppendVec to PAGESIZE. This will cause get_account* to fail to load the account.
1913        let truncated_accounts_len: usize = PAGE_SIZE as usize;
1914        let av = AppendVec::new_from_file_unchecked(path, truncated_accounts_len, storage_access)
1915            .unwrap();
1916        let account = av.get_account_shared_data(0);
1917        assert!(account.is_none()); // Expect None to be returned.
1918
1919        let result = av.get_stored_account_meta_callback(0, |_| true);
1920        assert!(result.is_none()); // Expect None to be returned.
1921    }
1922
1923    #[test_case(StorageAccess::Mmap)]
1924    #[test_case(StorageAccess::File)]
1925    fn test_get_account_sizes(storage_access: StorageAccess) {
1926        const NUM_ACCOUNTS: usize = 37;
1927        let pubkeys: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
1928            .take(NUM_ACCOUNTS)
1929            .collect();
1930
1931        let mut rng = thread_rng();
1932        let mut accounts = Vec::with_capacity(pubkeys.len());
1933        let mut stored_sizes = Vec::with_capacity(pubkeys.len());
1934        for _ in &pubkeys {
1935            let lamports = rng.gen();
1936            let data_len = rng.gen_range(0..MAX_PERMITTED_DATA_LENGTH) as usize;
1937            let account = AccountSharedData::new(lamports, data_len, &Pubkey::default());
1938            accounts.push(account);
1939            stored_sizes.push(aligned_stored_size(data_len));
1940        }
1941        let accounts = accounts;
1942        let stored_sizes = stored_sizes;
1943        let total_stored_size = stored_sizes.iter().sum();
1944
1945        let temp_file = get_append_vec_path("test_get_account_sizes");
1946        let account_offsets = {
1947            let append_vec = AppendVec::new(&temp_file.path, true, total_stored_size);
1948            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1949            let append_vec = ManuallyDrop::new(append_vec);
1950            let slot = 77; // the specific slot does not matter
1951            let storable_accounts: Vec<_> = std::iter::zip(&pubkeys, &accounts).collect();
1952            let stored_accounts_info = append_vec
1953                .append_accounts(&(slot, storable_accounts.as_slice()), 0)
1954                .unwrap();
1955            append_vec.flush().unwrap();
1956            stored_accounts_info.offsets
1957        };
1958
1959        // now open the append vec with the given storage access method
1960        // then get the account sizes to ensure they are correct
1961        let (append_vec, _) =
1962            AppendVec::new_from_file(&temp_file.path, total_stored_size, storage_access).unwrap();
1963
1964        let account_sizes = append_vec.get_account_sizes(account_offsets.as_slice());
1965        assert_eq!(account_sizes, stored_sizes);
1966    }
1967
1968    /// A helper function for testing different scenario for scan_*.
1969    ///
1970    /// `modify_fn` is used to (optionally) modify the append vec before checks are performed.
1971    /// `check_fn` performs the check for the scan.
1972    fn test_scan_helper(
1973        storage_access: StorageAccess,
1974        modify_fn: impl Fn(&PathBuf, usize) -> usize,
1975        check_fn: impl Fn(&AppendVec, &[Pubkey], &[usize], &[AccountSharedData]),
1976    ) {
1977        const NUM_ACCOUNTS: usize = 37;
1978        let pubkeys: Vec<_> = std::iter::repeat_with(solana_pubkey::new_rand)
1979            .take(NUM_ACCOUNTS)
1980            .collect();
1981
1982        let mut rng = thread_rng();
1983        let mut accounts = Vec::with_capacity(pubkeys.len());
1984        let mut total_stored_size = 0;
1985        for _ in &pubkeys {
1986            let lamports = rng.gen();
1987            let data_len = rng.gen_range(0..MAX_PERMITTED_DATA_LENGTH) as usize;
1988            let account = AccountSharedData::new(lamports, data_len, &Pubkey::default());
1989            accounts.push(account);
1990            total_stored_size += aligned_stored_size(data_len);
1991        }
1992        let accounts = accounts;
1993        let total_stored_size = total_stored_size;
1994
1995        let temp_file = get_append_vec_path("test_scan");
1996        let account_offsets = {
1997            // wrap AppendVec in ManuallyDrop to ensure we do not remove the backing file when dropped
1998            let append_vec =
1999                ManuallyDrop::new(AppendVec::new(&temp_file.path, true, total_stored_size));
2000            let slot = 42; // the specific slot does not matter
2001            let storable_accounts: Vec<_> = std::iter::zip(&pubkeys, &accounts).collect();
2002            let stored_accounts_info = append_vec
2003                .append_accounts(&(slot, storable_accounts.as_slice()), 0)
2004                .unwrap();
2005            append_vec.flush().unwrap();
2006            stored_accounts_info.offsets
2007        };
2008
2009        let total_stored_size = modify_fn(&temp_file.path, total_stored_size);
2010        // now open the append vec with the given storage access method
2011        // then perform the scan and check it is correct
2012        let append_vec = ManuallyDrop::new(
2013            AppendVec::new_from_file_unchecked(&temp_file.path, total_stored_size, storage_access)
2014                .unwrap(),
2015        );
2016
2017        check_fn(&append_vec, &pubkeys, &account_offsets, &accounts);
2018    }
2019
2020    /// A helper fn to test `scan_pubkeys`.
2021    fn test_scan_pubkeys_helper(
2022        storage_access: StorageAccess,
2023        modify_fn: impl Fn(&PathBuf, usize) -> usize,
2024    ) {
2025        test_scan_helper(
2026            storage_access,
2027            modify_fn,
2028            |append_vec, pubkeys, _account_offsets, _accounts| {
2029                let mut i = 0;
2030                append_vec.scan_pubkeys(|pubkey| {
2031                    assert_eq!(pubkey, pubkeys.get(i).unwrap());
2032                    i += 1;
2033                });
2034                assert_eq!(i, pubkeys.len());
2035            },
2036        )
2037    }
2038
2039    /// Test `scan_pubkey` for a valid account storage.
2040    #[test_case(StorageAccess::Mmap)]
2041    #[test_case(StorageAccess::File)]
2042    fn test_scan_pubkeys(storage_access: StorageAccess) {
2043        test_scan_pubkeys_helper(storage_access, |_, size| size);
2044    }
2045
2046    /// Test `scan_pubkey` for storage with incomplete account meta data.
2047    #[test_case(StorageAccess::Mmap)]
2048    #[test_case(StorageAccess::File)]
2049    fn test_scan_pubkeys_incomplete_data(storage_access: StorageAccess) {
2050        test_scan_pubkeys_helper(storage_access, |path, size| {
2051            // Append 1 byte of data at the end of the storage file to simulate
2052            // incomplete account's meta data.
2053            let mut f = OpenOptions::new()
2054                .read(true)
2055                .append(true)
2056                .open(path)
2057                .unwrap();
2058            f.write_all(&[0xFF]).unwrap();
2059            size + 1
2060        });
2061    }
2062
2063    /// Test `scan_pubkey` for storage which is missing the last account data
2064    #[test_case(StorageAccess::Mmap)]
2065    #[test_case(StorageAccess::File)]
2066    fn test_scan_pubkeys_missing_account_data(storage_access: StorageAccess) {
2067        test_scan_pubkeys_helper(storage_access, |path, size| {
2068            let fake_stored_meta = StoredMeta {
2069                write_version_obsolete: 0,
2070                data_len: 100,
2071                pubkey: solana_pubkey::new_rand(),
2072            };
2073            let fake_account_meta = AccountMeta {
2074                lamports: 100,
2075                rent_epoch: 10,
2076                owner: solana_pubkey::new_rand(),
2077                executable: false,
2078            };
2079
2080            let stored_meta_slice: &[u8] = unsafe {
2081                std::slice::from_raw_parts(
2082                    (&fake_stored_meta as *const StoredMeta) as *const u8,
2083                    mem::size_of::<StoredMeta>(),
2084                )
2085            };
2086            let account_meta_slice: &[u8] = unsafe {
2087                std::slice::from_raw_parts(
2088                    (&fake_account_meta as *const AccountMeta) as *const u8,
2089                    mem::size_of::<AccountMeta>(),
2090                )
2091            };
2092
2093            let mut f = OpenOptions::new()
2094                .read(true)
2095                .append(true)
2096                .open(path)
2097                .unwrap();
2098
2099            f.write_all(stored_meta_slice).unwrap();
2100            f.write_all(account_meta_slice).unwrap();
2101
2102            size + mem::size_of::<StoredMeta>() + mem::size_of::<AccountMeta>()
2103        });
2104    }
2105
2106    /// A helper fn to test scan_index
2107    fn test_scan_index_helper(
2108        storage_access: StorageAccess,
2109        modify_fn: impl Fn(&PathBuf, usize) -> usize,
2110    ) {
2111        test_scan_helper(
2112            storage_access,
2113            modify_fn,
2114            |append_vec, pubkeys, account_offsets, accounts| {
2115                let mut i = 0;
2116                append_vec.scan_index(|index_info| {
2117                    let pubkey = pubkeys.get(i).unwrap();
2118                    let account = accounts.get(i).unwrap();
2119                    let offset = account_offsets.get(i).unwrap();
2120
2121                    assert_eq!(
2122                        index_info.stored_size_aligned,
2123                        aligned_stored_size(account.data().len()),
2124                    );
2125                    assert_eq!(index_info.index_info.offset, *offset);
2126                    assert_eq!(index_info.index_info.pubkey, *pubkey);
2127                    assert_eq!(index_info.index_info.lamports, account.lamports());
2128                    assert_eq!(index_info.index_info.rent_epoch, account.rent_epoch());
2129                    assert_eq!(index_info.index_info.executable, account.executable());
2130                    assert_eq!(index_info.index_info.data_len, account.data().len() as u64);
2131
2132                    i += 1;
2133                });
2134                assert_eq!(i, accounts.len());
2135            },
2136        )
2137    }
2138
2139    #[test_case(StorageAccess::Mmap)]
2140    #[test_case(StorageAccess::File)]
2141    fn test_scan_index(storage_access: StorageAccess) {
2142        test_scan_index_helper(storage_access, |_, size| size);
2143    }
2144
2145    /// Test `scan_index` for storage with incomplete account meta data.
2146    #[test_case(StorageAccess::Mmap)]
2147    #[test_case(StorageAccess::File)]
2148    fn test_scan_index_incomplete_data(storage_access: StorageAccess) {
2149        test_scan_index_helper(storage_access, |path, size| {
2150            // Append 1 byte of data at the end of the storage file to simulate
2151            // incomplete account's meta data.
2152            let mut f = OpenOptions::new()
2153                .read(true)
2154                .append(true)
2155                .open(path)
2156                .unwrap();
2157            f.write_all(&[0xFF]).unwrap();
2158            size + 1
2159        });
2160    }
2161
2162    /// Test `scan_index` for storage which is missing the last account data
2163    #[test_case(StorageAccess::Mmap)]
2164    #[test_case(StorageAccess::File)]
2165    fn test_scan_index_missing_account_data(storage_access: StorageAccess) {
2166        test_scan_index_helper(storage_access, |path, size| {
2167            let fake_stored_meta = StoredMeta {
2168                write_version_obsolete: 0,
2169                data_len: 100,
2170                pubkey: solana_pubkey::new_rand(),
2171            };
2172            let fake_account_meta = AccountMeta {
2173                lamports: 100,
2174                rent_epoch: 10,
2175                owner: solana_pubkey::new_rand(),
2176                executable: false,
2177            };
2178
2179            let stored_meta_slice: &[u8] = unsafe {
2180                std::slice::from_raw_parts(
2181                    (&fake_stored_meta as *const StoredMeta) as *const u8,
2182                    mem::size_of::<StoredMeta>(),
2183                )
2184            };
2185            let account_meta_slice: &[u8] = unsafe {
2186                std::slice::from_raw_parts(
2187                    (&fake_account_meta as *const AccountMeta) as *const u8,
2188                    mem::size_of::<AccountMeta>(),
2189                )
2190            };
2191
2192            let mut f = OpenOptions::new()
2193                .read(true)
2194                .append(true)
2195                .open(path)
2196                .unwrap();
2197
2198            f.write_all(stored_meta_slice).unwrap();
2199            f.write_all(account_meta_slice).unwrap();
2200
2201            size + mem::size_of::<StoredMeta>() + mem::size_of::<AccountMeta>()
2202        });
2203    }
2204}