solana_runtime/
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 crate::{
6    accounts_db::{AppendVecId, CACHE_VIRTUAL_OFFSET},
7    accounts_index::{IsCached, ZeroLamport},
8    append_vec::ALIGN_BOUNDARY_OFFSET,
9};
10
11/// offset within an append vec to account data
12pub type Offset = usize;
13
14/// bytes used to store this account in append vec
15/// Note this max needs to be big enough to handle max data len of 10MB, which is a const
16pub type StoredSize = u32;
17
18/// specify where account data is located
19#[derive(Debug)]
20pub enum StorageLocation {
21    AppendVec(AppendVecId, 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#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
64pub struct AccountInfo {
65    /// index identifying the append storage
66    store_id: AppendVecId,
67
68    /// offset = 'reduced_offset' * ALIGN_BOUNDARY_OFFSET into the storage
69    /// Note this is a smaller type than 'Offset'
70    reduced_offset: OffsetReduced,
71
72    /// needed to track shrink candidacy in bytes. Used to update the number
73    /// of alive bytes in an AppendVec as newer slots purge outdated entries
74    /// Note that highest bits are used by ALL_FLAGS
75    /// So, 'stored_size' is 'stored_size_mask' with ALL_FLAGS masked out.
76    stored_size_mask: StoredSize,
77}
78
79/// These flags can be present in stored_size_mask to indicate additional info about the AccountInfo
80
81/// presence of this flag in stored_size_mask indicates this account info references an account with zero lamports
82const IS_ZERO_LAMPORT_FLAG: StoredSize = 1 << (StoredSize::BITS - 1);
83/// presence of this flag in stored_size_mask indicates this account info references an account stored in the cache
84const IS_CACHED_STORE_ID_FLAG: StoredSize = 1 << (StoredSize::BITS - 2);
85const ALL_FLAGS: StoredSize = IS_ZERO_LAMPORT_FLAG | IS_CACHED_STORE_ID_FLAG;
86
87impl ZeroLamport for AccountInfo {
88    fn is_zero_lamport(&self) -> bool {
89        self.stored_size_mask & IS_ZERO_LAMPORT_FLAG == IS_ZERO_LAMPORT_FLAG
90    }
91}
92
93impl IsCached for AccountInfo {
94    fn is_cached(&self) -> bool {
95        self.stored_size_mask & IS_CACHED_STORE_ID_FLAG == IS_CACHED_STORE_ID_FLAG
96    }
97}
98
99impl IsCached for StorageLocation {
100    fn is_cached(&self) -> bool {
101        matches!(self, StorageLocation::Cached)
102    }
103}
104
105/// We have to have SOME value for store_id when we are cached
106const CACHE_VIRTUAL_STORAGE_ID: AppendVecId = AppendVecId::MAX;
107
108impl AccountInfo {
109    pub fn new(storage_location: StorageLocation, stored_size: StoredSize, lamports: u64) -> Self {
110        assert_eq!(stored_size & ALL_FLAGS, 0);
111        let mut stored_size_mask = stored_size;
112        let (store_id, raw_offset) = match storage_location {
113            StorageLocation::AppendVec(store_id, offset) => (store_id, offset),
114            StorageLocation::Cached => {
115                stored_size_mask |= IS_CACHED_STORE_ID_FLAG;
116                (CACHE_VIRTUAL_STORAGE_ID, CACHE_VIRTUAL_OFFSET)
117            }
118        };
119        if lamports == 0 {
120            stored_size_mask |= IS_ZERO_LAMPORT_FLAG;
121        }
122        let reduced_offset: OffsetReduced = (raw_offset / ALIGN_BOUNDARY_OFFSET) as OffsetReduced;
123        let result = Self {
124            store_id,
125            reduced_offset,
126            stored_size_mask,
127        };
128        assert_eq!(result.offset(), raw_offset, "illegal offset");
129        result
130    }
131
132    pub fn store_id(&self) -> AppendVecId {
133        // if the account is in a cached store, the store_id is meaningless
134        assert!(!self.is_cached());
135        self.store_id
136    }
137
138    pub fn offset(&self) -> Offset {
139        (self.reduced_offset as Offset) * ALIGN_BOUNDARY_OFFSET
140    }
141
142    pub fn stored_size(&self) -> StoredSize {
143        // elminate the special bit that indicates the info references an account with zero lamports
144        self.stored_size_mask & !ALL_FLAGS
145    }
146
147    /// true iff store_id and offset match self AND self is not cached
148    /// If self is cached, then store_id and offset are meaningless.
149    pub fn matches_storage_location(&self, store_id: AppendVecId, offset: Offset) -> bool {
150        // order is set for best short circuit
151        self.store_id == store_id && self.offset() == offset && !self.is_cached()
152    }
153
154    pub fn storage_location(&self) -> StorageLocation {
155        if self.is_cached() {
156            StorageLocation::Cached
157        } else {
158            StorageLocation::AppendVec(self.store_id, self.offset())
159        }
160    }
161}
162#[cfg(test)]
163mod test {
164    use {super::*, crate::append_vec::MAXIMUM_APPEND_VEC_FILE_SIZE};
165
166    #[test]
167    fn test_limits() {
168        for offset in [
169            MAXIMUM_APPEND_VEC_FILE_SIZE as Offset,
170            0,
171            ALIGN_BOUNDARY_OFFSET,
172            4 * ALIGN_BOUNDARY_OFFSET,
173        ] {
174            let info = AccountInfo::new(StorageLocation::AppendVec(0, offset), 0, 0);
175            assert!(info.offset() == offset);
176        }
177    }
178
179    #[test]
180    #[should_panic(expected = "illegal offset")]
181    fn test_alignment() {
182        let offset = 1; // not aligned
183        AccountInfo::new(StorageLocation::AppendVec(0, offset), 0, 0);
184    }
185
186    #[test]
187    fn test_matches_storage_location() {
188        let offset = 0;
189        let id = 0;
190        let info = AccountInfo::new(StorageLocation::AppendVec(id, offset), 0, 0);
191        assert!(info.matches_storage_location(id, offset));
192
193        // wrong offset
194        let offset = ALIGN_BOUNDARY_OFFSET;
195        assert!(!info.matches_storage_location(id, offset));
196
197        // wrong id
198        let offset = 0;
199        let id = 1;
200        assert!(!info.matches_storage_location(id, offset));
201
202        // is cached
203        let id = CACHE_VIRTUAL_STORAGE_ID;
204        let info = AccountInfo::new(StorageLocation::Cached, 0, 0);
205        assert!(!info.matches_storage_location(id, offset));
206    }
207}