solana_accounts_db/tiered_storage/
meta.rs

1//! The account meta and related structs for the tiered storage.
2
3use {
4    crate::tiered_storage::owners::OwnerOffset,
5    bytemuck_derive::{Pod, Zeroable},
6    modular_bitfield::prelude::*,
7    solana_sdk::{pubkey::Pubkey, stake_history::Epoch},
8};
9
10/// The struct that handles the account meta flags.
11#[bitfield(bits = 32)]
12#[repr(C)]
13#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Pod, Zeroable)]
14pub struct AccountMetaFlags {
15    /// whether the account meta has rent epoch
16    pub has_rent_epoch: bool,
17    /// whether the account is executable
18    pub executable: bool,
19    /// the reserved bits.
20    reserved: B30,
21}
22
23// Ensure there are no implicit padding bytes
24const _: () = assert!(std::mem::size_of::<AccountMetaFlags>() == 4);
25
26/// A trait that allows different implementations of the account meta that
27/// support different tiers of the accounts storage.
28pub trait TieredAccountMeta: Sized {
29    /// Constructs a TieredAcountMeta instance.
30    fn new() -> Self;
31
32    /// A builder function that initializes lamports.
33    fn with_lamports(self, lamports: u64) -> Self;
34
35    /// A builder function that initializes the number of padding bytes
36    /// for the account data associated with the current meta.
37    fn with_account_data_padding(self, padding: u8) -> Self;
38
39    /// A builder function that initializes the owner offset.
40    fn with_owner_offset(self, owner_offset: OwnerOffset) -> Self;
41
42    /// A builder function that initializes the account data size.
43    /// The size here represents the logical data size without compression.
44    fn with_account_data_size(self, account_data_size: u64) -> Self;
45
46    /// A builder function that initializes the AccountMetaFlags of the current
47    /// meta.
48    fn with_flags(self, flags: &AccountMetaFlags) -> Self;
49
50    /// Returns the balance of the lamports associated with the account.
51    fn lamports(&self) -> u64;
52
53    /// Returns the number of padding bytes for the associated account data
54    fn account_data_padding(&self) -> u8;
55
56    /// Returns the offset to the accounts' owner in the current AccountsFile.
57    fn owner_offset(&self) -> OwnerOffset;
58
59    /// Returns the AccountMetaFlags of the current meta.
60    fn flags(&self) -> &AccountMetaFlags;
61
62    /// Returns true if the TieredAccountMeta implementation supports multiple
63    /// accounts sharing one account block.
64    fn supports_shared_account_block() -> bool;
65
66    /// Returns the epoch that this account will next owe rent by parsing
67    /// the specified account block.  None will be returned if this account
68    /// does not persist this optional field.
69    fn rent_epoch(&self, _account_block: &[u8]) -> Option<Epoch>;
70
71    /// Returns the epoch that this account will next owe rent by parsing
72    /// the specified account block.  RENT_EXEMPT_RENT_EPOCH will be returned
73    /// if the account is rent-exempt.
74    ///
75    /// For a zero-lamport account, Epoch::default() will be returned to
76    /// default states of an AccountSharedData.
77    fn final_rent_epoch(&self, account_block: &[u8]) -> Epoch;
78
79    /// Returns the offset of the optional fields based on the specified account
80    /// block.
81    fn optional_fields_offset(&self, _account_block: &[u8]) -> usize;
82
83    /// Returns the length of the data associated to this account based on the
84    /// specified account block.
85    fn account_data_size(&self, _account_block: &[u8]) -> usize;
86
87    /// Returns the data associated to this account based on the specified
88    /// account block.
89    fn account_data<'a>(&self, _account_block: &'a [u8]) -> &'a [u8];
90}
91
92impl AccountMetaFlags {
93    pub fn new_from(optional_fields: &AccountMetaOptionalFields) -> Self {
94        let mut flags = AccountMetaFlags::default();
95        flags.set_has_rent_epoch(optional_fields.rent_epoch.is_some());
96        flags.set_executable(false);
97        flags
98    }
99}
100
101/// The in-memory struct for the optional fields for tiered account meta.
102///
103/// Note that the storage representation of the optional fields might be
104/// different from its in-memory representation.
105#[derive(Debug, PartialEq, Eq, Clone)]
106pub struct AccountMetaOptionalFields {
107    /// the epoch at which its associated account will next owe rent
108    pub rent_epoch: Option<Epoch>,
109}
110
111impl AccountMetaOptionalFields {
112    /// The size of the optional fields in bytes (excluding the boolean flags).
113    pub fn size(&self) -> usize {
114        self.rent_epoch.map_or(0, |_| std::mem::size_of::<Epoch>())
115    }
116
117    /// Given the specified AccountMetaFlags, returns the size of its
118    /// associated AccountMetaOptionalFields.
119    pub fn size_from_flags(flags: &AccountMetaFlags) -> usize {
120        let mut fields_size = 0;
121        if flags.has_rent_epoch() {
122            fields_size += std::mem::size_of::<Epoch>();
123        }
124
125        fields_size
126    }
127
128    /// Given the specified AccountMetaFlags, returns the relative offset
129    /// of its rent_epoch field to the offset of its optional fields entry.
130    pub fn rent_epoch_offset(_flags: &AccountMetaFlags) -> usize {
131        0
132    }
133}
134
135const MIN_ACCOUNT_ADDRESS: Pubkey = Pubkey::new_from_array([0x00u8; 32]);
136const MAX_ACCOUNT_ADDRESS: Pubkey = Pubkey::new_from_array([0xFFu8; 32]);
137
138#[derive(Debug)]
139/// A struct that maintains an address-range using its min and max fields.
140pub struct AccountAddressRange {
141    /// The minimum address observed via update()
142    pub min: Pubkey,
143    /// The maximum address observed via update()
144    pub max: Pubkey,
145}
146
147impl Default for AccountAddressRange {
148    fn default() -> Self {
149        Self {
150            min: MAX_ACCOUNT_ADDRESS,
151            max: MIN_ACCOUNT_ADDRESS,
152        }
153    }
154}
155
156impl AccountAddressRange {
157    pub fn update(&mut self, address: &Pubkey) {
158        if self.min > *address {
159            self.min = *address;
160        }
161        if self.max < *address {
162            self.max = *address;
163        }
164    }
165}
166
167#[cfg(test)]
168pub mod tests {
169    use super::*;
170
171    #[test]
172    fn test_account_meta_flags_new() {
173        let flags = AccountMetaFlags::new();
174
175        assert!(!flags.has_rent_epoch());
176        assert_eq!(flags.reserved(), 0u32);
177
178        assert_eq!(
179            std::mem::size_of::<AccountMetaFlags>(),
180            std::mem::size_of::<u32>()
181        );
182    }
183
184    fn verify_flags_serialization(flags: &AccountMetaFlags) {
185        assert_eq!(AccountMetaFlags::from_bytes(flags.into_bytes()), *flags);
186    }
187
188    #[test]
189    fn test_account_meta_flags_set() {
190        let mut flags = AccountMetaFlags::new();
191
192        flags.set_has_rent_epoch(true);
193
194        assert!(flags.has_rent_epoch());
195        assert!(!flags.executable());
196        verify_flags_serialization(&flags);
197
198        flags.set_executable(true);
199        assert!(flags.has_rent_epoch());
200        assert!(flags.executable());
201        verify_flags_serialization(&flags);
202
203        // make sure the reserved bits are untouched.
204        assert_eq!(flags.reserved(), 0u32);
205    }
206
207    fn update_and_verify_flags(opt_fields: &AccountMetaOptionalFields) {
208        let flags: AccountMetaFlags = AccountMetaFlags::new_from(opt_fields);
209        assert_eq!(flags.has_rent_epoch(), opt_fields.rent_epoch.is_some());
210        assert_eq!(flags.reserved(), 0u32);
211    }
212
213    #[test]
214    fn test_optional_fields_update_flags() {
215        let test_epoch = 5432312;
216
217        for rent_epoch in [None, Some(test_epoch)] {
218            update_and_verify_flags(&AccountMetaOptionalFields { rent_epoch });
219        }
220    }
221
222    #[test]
223    fn test_optional_fields_size() {
224        let test_epoch = 5432312;
225
226        for rent_epoch in [None, Some(test_epoch)] {
227            let opt_fields = AccountMetaOptionalFields { rent_epoch };
228            assert_eq!(
229                opt_fields.size(),
230                rent_epoch.map_or(0, |_| std::mem::size_of::<Epoch>()),
231            );
232            assert_eq!(
233                opt_fields.size(),
234                AccountMetaOptionalFields::size_from_flags(&AccountMetaFlags::new_from(
235                    &opt_fields
236                ))
237            );
238        }
239    }
240
241    #[test]
242    fn test_optional_fields_offset() {
243        let test_epoch = 5432312;
244
245        for rent_epoch in [None, Some(test_epoch)] {
246            let rent_epoch_offset = 0;
247            let derived_size = if rent_epoch.is_some() {
248                std::mem::size_of::<Epoch>()
249            } else {
250                0
251            };
252            let opt_fields = AccountMetaOptionalFields { rent_epoch };
253            let flags = AccountMetaFlags::new_from(&opt_fields);
254            assert_eq!(
255                AccountMetaOptionalFields::rent_epoch_offset(&flags),
256                rent_epoch_offset
257            );
258            assert_eq!(
259                AccountMetaOptionalFields::size_from_flags(&flags),
260                derived_size
261            );
262        }
263    }
264
265    #[test]
266    fn test_pubkey_range_update_single() {
267        let address = solana_pubkey::new_rand();
268        let mut address_range = AccountAddressRange::default();
269
270        address_range.update(&address);
271        // For a single update, the min and max should equal to the address
272        assert_eq!(address_range.min, address);
273        assert_eq!(address_range.max, address);
274    }
275
276    #[test]
277    fn test_pubkey_range_update_multiple() {
278        const NUM_PUBKEYS: usize = 20;
279
280        let mut address_range = AccountAddressRange::default();
281        let mut addresses = Vec::with_capacity(NUM_PUBKEYS);
282
283        let mut min_index = 0;
284        let mut max_index = 0;
285
286        // Generate random addresses and track expected min and max indices
287        for i in 0..NUM_PUBKEYS {
288            let address = solana_pubkey::new_rand();
289            addresses.push(address);
290
291            // Update expected min and max indices
292            if address < addresses[min_index] {
293                min_index = i;
294            }
295            if address > addresses[max_index] {
296                max_index = i;
297            }
298        }
299
300        addresses
301            .iter()
302            .for_each(|address| address_range.update(address));
303
304        assert_eq!(address_range.min, addresses[min_index]);
305        assert_eq!(address_range.max, addresses[max_index]);
306    }
307}