solana_accounts_db/
accounts_file.rs

1use {
2    crate::{
3        account_info::AccountInfo,
4        account_storage::meta::StoredAccountMeta,
5        accounts_db::AccountsFileId,
6        append_vec::{AppendVec, AppendVecError, IndexInfo},
7        storable_accounts::StorableAccounts,
8        tiered_storage::{
9            error::TieredStorageError, hot::HOT_FORMAT, index::IndexOffset, TieredStorage,
10        },
11    },
12    solana_pubkey::Pubkey,
13    solana_sdk::{account::AccountSharedData, clock::Slot},
14    std::{
15        mem,
16        path::{Path, PathBuf},
17    },
18    thiserror::Error,
19};
20
21// Data placement should be aligned at the next boundary. Without alignment accessing the memory may
22// crash on some architectures.
23pub const ALIGN_BOUNDARY_OFFSET: usize = mem::size_of::<u64>();
24#[macro_export]
25macro_rules! u64_align {
26    ($addr: expr) => {
27        ($addr + (ALIGN_BOUNDARY_OFFSET - 1)) & !(ALIGN_BOUNDARY_OFFSET - 1)
28    };
29}
30
31#[derive(Error, Debug)]
32/// An enum for AccountsFile related errors.
33pub enum AccountsFileError {
34    #[error("I/O error: {0}")]
35    Io(#[from] std::io::Error),
36
37    #[error("AppendVecError: {0}")]
38    AppendVecError(#[from] AppendVecError),
39
40    #[error("TieredStorageError: {0}")]
41    TieredStorageError(#[from] TieredStorageError),
42}
43
44#[derive(Error, Debug, PartialEq, Eq)]
45pub enum MatchAccountOwnerError {
46    #[error("The account owner does not match with the provided list")]
47    NoMatch,
48    #[error("Unable to load the account")]
49    UnableToLoad,
50}
51
52#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
53pub enum StorageAccess {
54    /// storages should be accessed by Mmap
55    Mmap,
56    /// storages should be accessed by File I/O
57    /// ancient storages are created by 1-shot write to pack multiple accounts together more efficiently with new formats
58    #[default]
59    File,
60}
61
62pub type Result<T> = std::result::Result<T, AccountsFileError>;
63
64#[derive(Debug)]
65/// An enum for accessing an accounts file which can be implemented
66/// under different formats.
67pub enum AccountsFile {
68    AppendVec(AppendVec),
69    TieredStorage(TieredStorage),
70}
71
72impl AccountsFile {
73    /// Create an AccountsFile instance from the specified path.
74    ///
75    /// The second element of the returned tuple is the number of accounts in the
76    /// accounts file.
77    pub fn new_from_file(
78        path: impl Into<PathBuf>,
79        current_len: usize,
80        storage_access: StorageAccess,
81    ) -> Result<(Self, usize)> {
82        let (av, num_accounts) = AppendVec::new_from_file(path, current_len, storage_access)?;
83        Ok((Self::AppendVec(av), num_accounts))
84    }
85
86    /// true if this storage can possibly be appended to (independent of capacity check)
87    pub(crate) fn can_append(&self) -> bool {
88        match self {
89            Self::AppendVec(av) => av.can_append(),
90            // once created, tiered storages cannot be appended to
91            Self::TieredStorage(_) => false,
92        }
93    }
94
95    /// if storage is not readonly, reopen another instance that is read only
96    pub(crate) fn reopen_as_readonly(&self) -> Option<Self> {
97        match self {
98            Self::AppendVec(av) => av.reopen_as_readonly().map(Self::AppendVec),
99            Self::TieredStorage(_) => None,
100        }
101    }
102
103    /// Return the total number of bytes of the zero lamport single ref accounts in the storage.
104    /// Those bytes are "dead" and can be shrunk away.
105    pub(crate) fn dead_bytes_due_to_zero_lamport_single_ref(&self, count: usize) -> usize {
106        match self {
107            Self::AppendVec(av) => av.dead_bytes_due_to_zero_lamport_single_ref(count),
108            Self::TieredStorage(ts) => ts.dead_bytes_due_to_zero_lamport_single_ref(count),
109        }
110    }
111
112    pub fn flush(&self) -> Result<()> {
113        match self {
114            Self::AppendVec(av) => av.flush(),
115            Self::TieredStorage(_) => Ok(()),
116        }
117    }
118
119    pub fn reset(&self) {
120        match self {
121            Self::AppendVec(av) => av.reset(),
122            Self::TieredStorage(_) => {}
123        }
124    }
125
126    pub fn remaining_bytes(&self) -> u64 {
127        match self {
128            Self::AppendVec(av) => av.remaining_bytes(),
129            Self::TieredStorage(ts) => ts.capacity().saturating_sub(ts.len() as u64),
130        }
131    }
132
133    pub fn len(&self) -> usize {
134        match self {
135            Self::AppendVec(av) => av.len(),
136            Self::TieredStorage(ts) => ts.len(),
137        }
138    }
139
140    pub fn is_empty(&self) -> bool {
141        match self {
142            Self::AppendVec(av) => av.is_empty(),
143            Self::TieredStorage(ts) => ts.is_empty(),
144        }
145    }
146
147    pub fn capacity(&self) -> u64 {
148        match self {
149            Self::AppendVec(av) => av.capacity(),
150            Self::TieredStorage(ts) => ts.capacity(),
151        }
152    }
153
154    pub fn file_name(slot: Slot, id: AccountsFileId) -> String {
155        format!("{slot}.{id}")
156    }
157
158    /// calls `callback` with the account located at the specified index offset.
159    pub fn get_stored_account_meta_callback<Ret>(
160        &self,
161        offset: usize,
162        callback: impl for<'local> FnMut(StoredAccountMeta<'local>) -> Ret,
163    ) -> Option<Ret> {
164        match self {
165            Self::AppendVec(av) => av.get_stored_account_meta_callback(offset, callback),
166            // Note: The conversion here is needed as the AccountsDB currently
167            // assumes all offsets are multiple of 8 while TieredStorage uses
168            // IndexOffset that is equivalent to AccountInfo::reduced_offset.
169            Self::TieredStorage(ts) => ts
170                .reader()?
171                .get_stored_account_meta_callback(
172                    IndexOffset(AccountInfo::get_reduced_offset(offset)),
173                    callback,
174                )
175                .ok()?,
176        }
177    }
178
179    /// return an `AccountSharedData` for an account at `offset`, if any.  Otherwise return None.
180    pub(crate) fn get_account_shared_data(&self, offset: usize) -> Option<AccountSharedData> {
181        match self {
182            Self::AppendVec(av) => av.get_account_shared_data(offset),
183            Self::TieredStorage(ts) => {
184                // Note: The conversion here is needed as the AccountsDB currently
185                // assumes all offsets are multiple of 8 while TieredStorage uses
186                // IndexOffset that is equivalent to AccountInfo::reduced_offset.
187                let index_offset = IndexOffset(AccountInfo::get_reduced_offset(offset));
188                ts.reader()?.get_account_shared_data(index_offset).ok()?
189            }
190        }
191    }
192
193    pub fn account_matches_owners(
194        &self,
195        offset: usize,
196        owners: &[Pubkey],
197    ) -> std::result::Result<usize, MatchAccountOwnerError> {
198        match self {
199            Self::AppendVec(av) => av.account_matches_owners(offset, owners),
200            // Note: The conversion here is needed as the AccountsDB currently
201            // assumes all offsets are multiple of 8 while TieredStorage uses
202            // IndexOffset that is equivalent to AccountInfo::reduced_offset.
203            Self::TieredStorage(ts) => {
204                let Some(reader) = ts.reader() else {
205                    return Err(MatchAccountOwnerError::UnableToLoad);
206                };
207                reader.account_matches_owners(
208                    IndexOffset(AccountInfo::get_reduced_offset(offset)),
209                    owners,
210                )
211            }
212        }
213    }
214
215    /// Return the path of the underlying account file.
216    pub fn path(&self) -> &Path {
217        match self {
218            Self::AppendVec(av) => av.path(),
219            Self::TieredStorage(ts) => ts.path(),
220        }
221    }
222
223    /// Iterate over all accounts and call `callback` with each account.
224    pub fn scan_accounts(&self, callback: impl for<'local> FnMut(StoredAccountMeta<'local>)) {
225        match self {
226            Self::AppendVec(av) => av.scan_accounts(callback),
227            Self::TieredStorage(ts) => {
228                if let Some(reader) = ts.reader() {
229                    _ = reader.scan_accounts(callback);
230                }
231            }
232        }
233    }
234
235    /// for each offset in `sorted_offsets`, return the account size
236    pub(crate) fn get_account_sizes(&self, sorted_offsets: &[usize]) -> Vec<usize> {
237        match self {
238            Self::AppendVec(av) => av.get_account_sizes(sorted_offsets),
239            Self::TieredStorage(ts) => ts
240                .reader()
241                .and_then(|reader| reader.get_account_sizes(sorted_offsets).ok())
242                .unwrap_or_default(),
243        }
244    }
245
246    /// iterate over all entries to put in index
247    pub(crate) fn scan_index(&self, callback: impl FnMut(IndexInfo)) {
248        match self {
249            Self::AppendVec(av) => av.scan_index(callback),
250            Self::TieredStorage(ts) => {
251                if let Some(reader) = ts.reader() {
252                    _ = reader.scan_index(callback);
253                }
254            }
255        }
256    }
257
258    /// iterate over all pubkeys
259    pub fn scan_pubkeys(&self, callback: impl FnMut(&Pubkey)) {
260        match self {
261            Self::AppendVec(av) => av.scan_pubkeys(callback),
262            Self::TieredStorage(ts) => {
263                if let Some(reader) = ts.reader() {
264                    _ = reader.scan_pubkeys(callback);
265                }
266            }
267        }
268    }
269
270    /// Copy each account metadata, account and hash to the internal buffer.
271    /// If there is no room to write the first entry, None is returned.
272    /// Otherwise, returns the starting offset of each account metadata.
273    /// Plus, the final return value is the offset where the next entry would be appended.
274    /// So, return.len() is 1 + (number of accounts written)
275    /// After each account is appended, the internal `current_len` is updated
276    /// and will be available to other threads.
277    pub fn append_accounts<'a>(
278        &self,
279        accounts: &impl StorableAccounts<'a>,
280        skip: usize,
281    ) -> Option<StoredAccountsInfo> {
282        match self {
283            Self::AppendVec(av) => av.append_accounts(accounts, skip),
284            // Note: The conversion here is needed as the AccountsDB currently
285            // assumes all offsets are multiple of 8 while TieredStorage uses
286            // IndexOffset that is equivalent to AccountInfo::reduced_offset.
287            Self::TieredStorage(ts) => ts
288                .write_accounts(accounts, skip, &HOT_FORMAT)
289                .map(|mut stored_accounts_info| {
290                    stored_accounts_info.offsets.iter_mut().for_each(|offset| {
291                        *offset = AccountInfo::reduced_offset_to_offset(*offset as u32);
292                    });
293                    stored_accounts_info
294                })
295                .ok(),
296        }
297    }
298
299    /// Returns the way to access this accounts file when archiving
300    pub fn internals_for_archive(&self) -> InternalsForArchive {
301        match self {
302            Self::AppendVec(av) => av.internals_for_archive(),
303            Self::TieredStorage(ts) => InternalsForArchive::Mmap(
304                ts.reader()
305                    .expect("must be a reader when archiving")
306                    .data_for_archive(),
307            ),
308        }
309    }
310}
311
312/// An enum that creates AccountsFile instance with the specified format.
313#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
314pub enum AccountsFileProvider {
315    #[default]
316    AppendVec,
317    HotStorage,
318}
319
320impl AccountsFileProvider {
321    pub fn new_writable(&self, path: impl Into<PathBuf>, file_size: u64) -> AccountsFile {
322        match self {
323            Self::AppendVec => {
324                AccountsFile::AppendVec(AppendVec::new(path, true, file_size as usize))
325            }
326            Self::HotStorage => AccountsFile::TieredStorage(TieredStorage::new_writable(path)),
327        }
328    }
329}
330
331/// The access method to use when archiving an AccountsFile
332#[derive(Debug)]
333pub enum InternalsForArchive<'a> {
334    /// Accessing the internals is done via Mmap
335    Mmap(&'a [u8]),
336    /// Accessing the internals is done via File I/O
337    FileIo(&'a Path),
338}
339
340/// Information after storing accounts
341#[derive(Debug)]
342pub struct StoredAccountsInfo {
343    /// offset in the storage where each account was stored
344    pub offsets: Vec<usize>,
345    /// total size of all the stored accounts
346    pub size: usize,
347}
348
349#[cfg(test)]
350pub mod tests {
351    use crate::accounts_file::AccountsFile;
352    impl AccountsFile {
353        pub(crate) fn set_current_len_for_tests(&self, len: usize) {
354            match self {
355                Self::AppendVec(av) => av.set_current_len_for_tests(len),
356                Self::TieredStorage(_) => {}
357            }
358        }
359    }
360}