solana_accounts_db/
account_info.rs

1//! AccountInfo represents a reference to AccountSharedData in either an AppendVec or the write cache.
2//! AccountInfo is not persisted anywhere between program runs.
3//! AccountInfo is purely runtime state.
4//! Note that AccountInfo is saved to disk buckets during runtime, but disk buckets are recreated at startup.
5use {
6    crate::{
7        accounts_db::AccountsFileId,
8        accounts_file::ALIGN_BOUNDARY_OFFSET,
9        accounts_index::{IsCached, ZeroLamport},
10    },
11    modular_bitfield::prelude::*,
12};
13
14/// offset within an append vec to account data
15pub type Offset = usize;
16
17/// bytes used to store this account in append vec
18/// Note this max needs to be big enough to handle max data len of 10MB, which is a const
19pub type StoredSize = u32;
20
21/// specify where account data is located
22#[derive(Debug, PartialEq, Eq)]
23pub enum StorageLocation {
24    AppendVec(AccountsFileId, Offset),
25    Cached,
26}
27
28impl StorageLocation {
29    pub fn is_offset_equal(&self, other: &StorageLocation) -> bool {
30        match self {
31            StorageLocation::Cached => {
32                matches!(other, StorageLocation::Cached) // technically, 2 cached entries match in offset
33            }
34            StorageLocation::AppendVec(_, offset) => {
35                match other {
36                    StorageLocation::Cached => {
37                        false // 1 cached, 1 not
38                    }
39                    StorageLocation::AppendVec(_, other_offset) => other_offset == offset,
40                }
41            }
42        }
43    }
44    pub fn is_store_id_equal(&self, other: &StorageLocation) -> bool {
45        match self {
46            StorageLocation::Cached => {
47                matches!(other, StorageLocation::Cached) // 2 cached entries are same store id
48            }
49            StorageLocation::AppendVec(store_id, _) => {
50                match other {
51                    StorageLocation::Cached => {
52                        false // 1 cached, 1 not
53                    }
54                    StorageLocation::AppendVec(other_store_id, _) => other_store_id == store_id,
55                }
56            }
57        }
58    }
59}
60
61/// how large the offset we store in AccountInfo is
62/// Note this is a smaller datatype than 'Offset'
63/// AppendVecs store accounts aligned to u64, so offset is always a multiple of 8 (sizeof(u64))
64pub type OffsetReduced = u32;
65
66/// This is an illegal value for 'offset'.
67/// Account size on disk would have to be pointing to the very last 8 byte value in the max sized append vec.
68/// That would mean there was a maximum size of 8 bytes for the last entry in the append vec.
69/// A pubkey alone is 32 bytes, so there is no way for a valid offset to be this high of a value.
70/// Realistically, a max offset is (1<<31 - 156) bytes or so for an account with zero data length. Of course, this
71/// depends on the layout on disk, compression, etc. But, 8 bytes per account will never be possible.
72/// So, we use this last value as a sentinel to say that the account info refers to an entry in the write cache.
73const CACHED_OFFSET: OffsetReduced = (1 << (OffsetReduced::BITS - 1)) - 1;
74
75#[bitfield(bits = 32)]
76#[repr(C)]
77#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
78pub struct PackedOffsetAndFlags {
79    /// this provides 2^31 bits, which when multiplied by 8 (sizeof(u64)) = 16G, which is the maximum size of an append vec
80    offset_reduced: B31,
81    /// use 1 bit to specify that the entry is zero lamport
82    is_zero_lamport: bool,
83}
84
85#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
86pub struct AccountInfo {
87    /// index identifying the append storage
88    store_id: AccountsFileId,
89
90    account_offset_and_flags: AccountOffsetAndFlags,
91}
92
93#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
94pub struct AccountOffsetAndFlags {
95    /// offset = 'packed_offset_and_flags.offset_reduced()' * ALIGN_BOUNDARY_OFFSET into the storage
96    /// Note this is a smaller type than 'Offset'
97    packed_offset_and_flags: PackedOffsetAndFlags,
98}
99
100impl ZeroLamport for AccountInfo {
101    fn is_zero_lamport(&self) -> bool {
102        self.account_offset_and_flags
103            .packed_offset_and_flags
104            .is_zero_lamport()
105    }
106}
107
108impl IsCached for AccountInfo {
109    fn is_cached(&self) -> bool {
110        self.account_offset_and_flags
111            .packed_offset_and_flags
112            .offset_reduced()
113            == CACHED_OFFSET
114    }
115}
116
117impl IsCached for StorageLocation {
118    fn is_cached(&self) -> bool {
119        matches!(self, StorageLocation::Cached)
120    }
121}
122
123/// We have to have SOME value for store_id when we are cached
124const CACHE_VIRTUAL_STORAGE_ID: AccountsFileId = AccountsFileId::MAX;
125
126impl AccountInfo {
127    pub fn new(storage_location: StorageLocation, lamports: u64) -> Self {
128        let mut packed_offset_and_flags = PackedOffsetAndFlags::default();
129        let store_id = match storage_location {
130            StorageLocation::AppendVec(store_id, offset) => {
131                let reduced_offset = Self::get_reduced_offset(offset);
132                assert_ne!(
133                    CACHED_OFFSET, reduced_offset,
134                    "illegal offset for non-cached item"
135                );
136                packed_offset_and_flags.set_offset_reduced(Self::get_reduced_offset(offset));
137                assert_eq!(
138                    Self::reduced_offset_to_offset(packed_offset_and_flags.offset_reduced()),
139                    offset,
140                    "illegal offset"
141                );
142                store_id
143            }
144            StorageLocation::Cached => {
145                packed_offset_and_flags.set_offset_reduced(CACHED_OFFSET);
146                CACHE_VIRTUAL_STORAGE_ID
147            }
148        };
149        packed_offset_and_flags.set_is_zero_lamport(lamports == 0);
150        let account_offset_and_flags = AccountOffsetAndFlags {
151            packed_offset_and_flags,
152        };
153        Self {
154            store_id,
155            account_offset_and_flags,
156        }
157    }
158
159    pub fn get_reduced_offset(offset: usize) -> OffsetReduced {
160        (offset / ALIGN_BOUNDARY_OFFSET) as OffsetReduced
161    }
162
163    pub fn store_id(&self) -> AccountsFileId {
164        // if the account is in a cached store, the store_id is meaningless
165        assert!(!self.is_cached());
166        self.store_id
167    }
168
169    pub fn offset(&self) -> Offset {
170        Self::reduced_offset_to_offset(
171            self.account_offset_and_flags
172                .packed_offset_and_flags
173                .offset_reduced(),
174        )
175    }
176
177    pub fn reduced_offset_to_offset(reduced_offset: OffsetReduced) -> Offset {
178        (reduced_offset as Offset) * ALIGN_BOUNDARY_OFFSET
179    }
180
181    pub fn storage_location(&self) -> StorageLocation {
182        if self.is_cached() {
183            StorageLocation::Cached
184        } else {
185            StorageLocation::AppendVec(self.store_id, self.offset())
186        }
187    }
188}
189#[cfg(test)]
190mod test {
191    use {super::*, crate::append_vec::MAXIMUM_APPEND_VEC_FILE_SIZE};
192
193    #[test]
194    fn test_limits() {
195        for offset in [
196            // MAXIMUM_APPEND_VEC_FILE_SIZE is too big. That would be an offset at the first invalid byte in the max file size.
197            // MAXIMUM_APPEND_VEC_FILE_SIZE - 8 bytes would reference the very last 8 bytes in the file size. It makes no sense to reference that since element sizes are always more than 8.
198            // MAXIMUM_APPEND_VEC_FILE_SIZE - 16 bytes would reference the second to last 8 bytes in the max file size. This is still likely meaningless, but it is 'valid' as far as the index
199            // is concerned.
200            (MAXIMUM_APPEND_VEC_FILE_SIZE - 2 * (ALIGN_BOUNDARY_OFFSET as u64)) as Offset,
201            0,
202            ALIGN_BOUNDARY_OFFSET,
203            4 * ALIGN_BOUNDARY_OFFSET,
204        ] {
205            let info = AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
206            assert!(info.offset() == offset);
207        }
208    }
209
210    #[test]
211    #[should_panic(expected = "illegal offset")]
212    fn test_illegal_offset() {
213        let offset = (MAXIMUM_APPEND_VEC_FILE_SIZE - (ALIGN_BOUNDARY_OFFSET as u64)) as Offset;
214        AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
215    }
216
217    #[test]
218    #[should_panic(expected = "illegal offset")]
219    fn test_alignment() {
220        let offset = 1; // not aligned
221        AccountInfo::new(StorageLocation::AppendVec(0, offset), 0);
222    }
223}