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