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