Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h 2 : #define HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h 3 : 4 : #include "../rewards/fd_rewards_base.h" 5 : #include "../runtime/fd_cost_tracker.h" 6 : #include "../../util/tmpl/fd_map.h" 7 : 8 30 : #define FD_STAKE_DELEGATIONS_MAGIC (0xF17EDA2CE757A3E0) /* FIREDANCER STAKE V0 */ 9 : 10 : /* fd_stakes_delegations_t is a cache of stake accounts mapping the 11 : pubkey of the stake account to various information including 12 : stake, activation/deactivation epoch, corresponding vote_account, 13 : credits observed, and warmup cooldown rate. This is used to quickly 14 : iterate through all of the stake delegations in the system during 15 : epoch boundary reward calculations. 16 : 17 : The implementation of fd_stakes_delegations_t is a hash map which 18 : is backed by a memory pool. Callers are allowed to insert, replace, 19 : and remove entries from the map. 20 : 21 : fd_stakes_delegations_t can also exist in two modes: with and without 22 : tombstones. The mode is determined by the leave_tombstones flag 23 : passed to fd_stake_delegations_new. If tombstones are enabled, then 24 : calling fd_stake_delegations_remove will not remove the entry from 25 : the map, but rather set the is_tombstone flag to true. This is 26 : useful for delta updates where we want to keep the entry in the map 27 : for future reference. In practice, this struct is used in both modes 28 : by the bank. The stake delegations corresponding to each slot are 29 : stored in a delta struct which is used to update the main cache. 30 : 31 : There are some important invariants wrt fd_stake_delegations_t: 32 : 1. After execution has started, there will be no invalid stake 33 : accounts in the stake delegations struct. 34 : 2. The stake delegations struct can have valid delegations for vote 35 : accounts which no longer exist. 36 : 3. There are no stake accounts which are valid delegations which 37 : exist in the accounts database but not in fd_stake_delegations_t. 38 : 39 : In practice, fd_stakes_delegations_t are updated in 3 cases: 40 : 1. During bootup when the snapshot manifest is loaded in. The cache 41 : is also refreshed during the bootup process to ensure that the 42 : states are valid and up-to-date. 43 : 44 : The reason we can't populate the stake accounts from the cache 45 : is because the cache in the manifest is partially incomplete: 46 : all of the expected keys are there, but the values are not. 47 : Notably, the credits_observed field is not available until all of 48 : the accounts are loaded into the database. 49 : 50 : https://github.com/anza-xyz/agave/blob/v2.3.6/runtime/src/bank.rs#L1780-L1806 51 : 52 : 2. After transaction execution. If an update is made to a stake 53 : account, the updated state is reflected in the cache (or the entry 54 : is evicted). 55 : 3. During rewards distribution. Stake accounts are partitioned over 56 : several hundred slots where their rewards are distributed. In this 57 : case, the cache is updated to reflect each stake account post 58 : reward distribution. 59 : The stake accounts are read-only during the epoch boundary. */ 60 : 61 : /* The max number of stake accounts that can be modified in a current 62 : slot from transactions can be bounded based on CU consumption. The 63 : best strategy to maximize the max number of stake accounts modified 64 : in a single transaction. A stake program instruction can either 65 : modify one or two stake accounts. Stake program instructions that 66 : modify two stake accounts (merge/split) are assumed to be at least 2x 67 : as expensive as a stake program instruction that modifies one stake 68 : account. So we will assume that the most efficient strategy is to 69 : modify one stake account per instruction and have as many instruction 70 : as posssible in this transaction. We can have 63 stake program 71 : instructions in this transaction because one account will be the fee 72 : payer/signature and the other 63 are free to be writable accounts. 73 : 74 : Given the above: 75 : 100000000 - CUs per slot 76 : 64 - Max number of writable accounts per transaction. 77 : 63 - Max number of writable stake accounts per transaction. 78 : 720 - Cost of a signature 79 : 300 - Cost of a writable account lock. 80 : 6000 - Cost of a stake program instruction. 81 : 82 : We can have (100000000 / (720 + 300 * 64 + 6000 * 63)) = 251 83 : optimal stake program transactions per slot. With 63 stake accounts 84 : per transaction, we can have 251 * 63 = 15813 stake accounts modified 85 : in a slot. */ 86 : 87 15 : #define MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN (FD_MAX_BLOCK_UNITS_SIMD_0286/(FD_WRITE_LOCK_UNITS * FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION + FD_RUNTIME_MIN_STAKE_INSN_CUS * (FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION - 1UL) + FD_PACK_COST_PER_SIGNATURE)) 88 : FD_STATIC_ASSERT(MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN==251, "Incorrect MAX_STAKE_ACCOUNTS_POSSIBLE_IN_TXN"); 89 15 : #define MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS (MAX_OPTIMAL_STAKE_ACCOUNTS_POSSIBLE_IN_TXN * (FD_RUNTIME_MAX_WRITABLE_ACCOUNTS_PER_TRANSACTION - 1UL)) 90 : FD_STATIC_ASSERT(MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS==15813, "Incorrect MAX_STAKE_ACCOUNTS_PER_SLOT"); 91 : 92 : /* The static footprint of fd_stake_delegations_t when it is a delta 93 : is determined by the max total number of stake accounts that can get 94 : changed in a single slot. Stake accounts can get modified in two ways: 95 : 1. Through transactions. This bound is calculated using CU 96 : consumption as described above. 97 : 2. Through epoch rewards. This is a protocol-level bound is defined 98 : in fd_rewards_base.h and is the max number of stake accounts that 99 : can reside in a single reward partition. */ 100 : 101 15 : #define FD_STAKE_DELEGATIONS_MAX_PER_SLOT (MAX_STAKE_ACCOUNTS_POSSIBLE_IN_SLOT_FROM_TXNS + STAKE_ACCOUNT_STORES_PER_BLOCK) 102 : 103 : /* The static footprint of the vote states assumes that there are 104 : FD_RUNTIME_MAX_STAKE_ACCOUNTS. It also assumes worst case alignment 105 : for each struct. fd_stake_delegations_t is laid out as first the 106 : fd_stake_delegations_t struct, followed by a pool of 107 : fd_stake_delegation_t structs, followed by a map of 108 : fd_stake_delegation_map_ele_t structs. The pool has 109 : FD_RUNTIME_MAX_STAKE_ACCOUNTS elements, and the map has a chain count 110 : determined by a call to fd_stake_delegations_chain_cnt_est. 111 : NOTE: the footprint is validated to be at least as large as the 112 : actual runtime-determined footprint (see test_stake_delegations.c) */ 113 : 114 24 : #define FD_STAKE_DELEGATIONS_CHAIN_CNT_EST (2097152UL) 115 : #define FD_STAKE_DELEGATIONS_FOOTPRINT \ 116 : /* First, layout the struct with alignment */ \ 117 24 : sizeof(fd_stake_delegations_t) + alignof(fd_stake_delegations_t) + \ 118 24 : /* Now layout the pool's data footprint */ \ 119 24 : FD_STAKE_DELEGATIONS_ALIGN + sizeof(fd_stake_delegation_t) * FD_RUNTIME_MAX_STAKE_ACCOUNTS + \ 120 24 : /* Now layout the pool's meta footprint */ \ 121 24 : FD_STAKE_DELEGATIONS_ALIGN + 128UL /* POOL_ALIGN */ + \ 122 24 : /* Now layout the map. We must make assumptions about the chain */ \ 123 24 : /* count to be equivalent to chain_cnt_est. */ \ 124 24 : FD_STAKE_DELEGATIONS_ALIGN + 128UL /* MAP_ALIGN */ + (FD_STAKE_DELEGATIONS_CHAIN_CNT_EST * sizeof(ulong)) 125 : 126 : /* We need a footprint for the max amount of stake delegations that 127 : can be added in a single slot. We know that there can be up to 128 : 8192 writable accounts in a slot (bound determined from the cost 129 : tracker). Using the same calculation as above, we get 120 bytes per 130 : stake delegation with up to ~19K delegations we have a total 131 : footprint of ~2.5MB. */ 132 : 133 : #define FD_STAKE_DELEGATIONS_DELTA_CHAIN_CNT_EST (16384UL) 134 : #define FD_STAKE_DELEGATIONS_DELTA_FOOTPRINT \ 135 : /* First, layout the struct with alignment */ \ 136 : sizeof(fd_stake_delegations_t) + alignof(fd_stake_delegations_t) + \ 137 : /* Now layout the pool's data footprint */ \ 138 : FD_STAKE_DELEGATIONS_ALIGN + sizeof(fd_stake_delegation_t) * FD_STAKE_DELEGATIONS_MAX_PER_SLOT + \ 139 : /* Now layout the pool's meta footprint */ \ 140 : FD_STAKE_DELEGATIONS_ALIGN + 128UL /* POOL_ALIGN */ + \ 141 : /* Now layout the map. We must make assumptions about the chain */ \ 142 : /* count to be equivalent to chain_cnt_est. */ \ 143 : FD_STAKE_DELEGATIONS_ALIGN + 128UL /* MAP_ALIGN */ + (FD_STAKE_DELEGATIONS_DELTA_CHAIN_CNT_EST * sizeof(ulong)) 144 : 145 72 : #define FD_STAKE_DELEGATIONS_ALIGN (128UL) 146 : 147 : struct fd_stake_delegation { 148 : fd_pubkey_t stake_account; 149 : fd_pubkey_t vote_account; 150 : ulong next_; /* Only for internal pool/map usage */ 151 : ulong stake; 152 : ulong activation_epoch; 153 : ulong deactivation_epoch; 154 : ulong credits_observed; 155 : double warmup_cooldown_rate; 156 : int is_tombstone; 157 : }; 158 : typedef struct fd_stake_delegation fd_stake_delegation_t; 159 : 160 : struct fd_stake_delegations { 161 : ulong magic; 162 : ulong map_offset_; 163 : ulong pool_offset_; 164 : ulong max_stake_accounts_; 165 : int leave_tombstones_; 166 : }; 167 : typedef struct fd_stake_delegations fd_stake_delegations_t; 168 : 169 : /* Forward declare map iterator API generated by fd_map_chain.c */ 170 : typedef struct fd_stake_delegation_map_private fd_stake_delegation_map_t; 171 : typedef struct fd_map_chain_iter fd_stake_delegation_map_iter_t; 172 : struct fd_stake_delegations_iter { 173 : fd_stake_delegation_map_t * map; 174 : fd_stake_delegation_t * pool; 175 : fd_stake_delegation_map_iter_t iter; 176 : }; 177 : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t; 178 : 179 : struct fd_funk_private; 180 : typedef struct fd_funk_private fd_funk_t; 181 : struct fd_funk_txn_private; 182 : typedef struct fd_funk_txn_private fd_funk_txn_t; 183 : 184 : FD_PROTOTYPES_BEGIN 185 : 186 : /* fd_stake_delegations_align returns the alignment of the stake 187 : delegations struct. */ 188 : 189 : ulong 190 : fd_stake_delegations_align( void ); 191 : 192 : /* fd_stake_delegations_footprint returns the footprint of the stake 193 : delegations struct for a given amount of max stake accounts. */ 194 : 195 : ulong 196 : fd_stake_delegations_footprint( ulong max_stake_accounts ); 197 : 198 : /* fd_stake_delegations_new creates a new stake delegations struct 199 : with a given amount of max stake accounts. It formats a memory region 200 : which is sized based off of the number of stake accounts. The struct 201 : can optionally be configured to leave tombstones in the map. This is 202 : useful if fd_stake_delegations is being used as a delta. */ 203 : 204 : void * 205 : fd_stake_delegations_new( void * mem, 206 : ulong max_stake_accounts, 207 : int leave_tombstones ); 208 : 209 : /* fd_stake_delegations_join joins a stake delegations struct from a 210 : memory region. There can be multiple valid joins for a given memory 211 : region but the caller is responsible for accessing memory in a 212 : thread-safe manner. */ 213 : 214 : fd_stake_delegations_t * 215 : fd_stake_delegations_join( void * mem ); 216 : 217 : /* fd_stake_delegations_leave returns the stake delegations struct 218 : from a memory region. */ 219 : 220 : void * 221 : fd_stake_delegations_leave( fd_stake_delegations_t * self ); 222 : 223 : /* fd_stake_delegations_delete unformats a memory region that was 224 : formatted by fd_stake_delegations_new. */ 225 : 226 : void * 227 : fd_stake_delegations_delete( void * mem ); 228 : 229 : /* fd_stake_delegations_update will either insert a new stake delegation 230 : if the pubkey doesn't exist yet, or it will update the stake 231 : delegation for the pubkey if already in the map, overriding any 232 : previous data. fd_stake_delegations_t must be a valid local join. 233 : 234 : NOTE: This function CAN be called while iterating over the map, but 235 : ONLY for keys which already exist in the map. */ 236 : 237 : void 238 : fd_stake_delegations_update( fd_stake_delegations_t * stake_delegations, 239 : fd_pubkey_t const * stake_account, 240 : fd_pubkey_t const * vote_account, 241 : ulong stake, 242 : ulong activation_epoch, 243 : ulong deactivation_epoch, 244 : ulong credits_observed, 245 : double warmup_cooldown_rate ); 246 : 247 : /* fd_stake_delegations_remove removes a stake delegation corresponding 248 : to a stake account's pubkey if one exists. Nothing happens if the 249 : key doesn't exist in the stake delegations. fd_stake_delegations_t 250 : must be a valid local join. 251 : 252 : NOTE: If the leave_tombstones flag is set, then the entry is not 253 : removed from the map, but rather set to a tombstone. If the 254 : delegation does not exist in the map, then a tombstone is actually 255 : inserted into the struct. */ 256 : 257 : void 258 : fd_stake_delegations_remove( fd_stake_delegations_t * stake_delegations, 259 : fd_pubkey_t const * stake_account ); 260 : 261 : 262 : /* fd_stake_delegations_query returns the stake delegation for a 263 : stake account's pubkey if one exists. If one does not exist, returns 264 : NULL. fd_stake_delegations_t must be a valid local join. */ 265 : 266 : fd_stake_delegation_t const * 267 : fd_stake_delegations_query( fd_stake_delegations_t const * stake_delegations, 268 : fd_pubkey_t const * stake_account ); 269 : 270 : /* fd_stake_delegations_refresh is used to refresh the stake 271 : delegations stored in fd_stake_delegations_t which is owned by 272 : the bank. For a given database handle, read in the state of all 273 : stake accounts, decode their state, and update each stake delegation. 274 : This is meant to be called before any slots are executed, but after 275 : the snapshot has finished loading. 276 : 277 : Before this function is called, there are some important assumptions 278 : made about the state of the stake delegations that are enforced by 279 : the Agave client: 280 : 1. fd_stake_delegations_t is not missing any valid entries 281 : 2. fd_stake_delegations_t may have some invalid entries that should 282 : be removed 283 : 284 : fd_stake_delegations_refresh will remove all of the invalid entries 285 : that are detected. An entry is considered invalid if the stake 286 : account does not exist (e.g. zero balance or no record) or if it 287 : has invalid state (e.g. not a stake account or invalid bincode data). 288 : No new entries are added to the struct at this point. */ 289 : 290 : void 291 : fd_stake_delegations_refresh( fd_stake_delegations_t * stake_delegations, 292 : fd_funk_t * funk, 293 : fd_funk_txn_t * funk_txn ); 294 : 295 : /* fd_stake_delegations_cnt returns the number of stake delegations 296 : in the stake delegations struct. fd_stake_delegations_t must be a 297 : valid local join. 298 : 299 : NOTE: The cnt will return the number of stake delegations that are 300 : in the underlying map. This number includes tombstones if the 301 : leave_tombstones flag is set. */ 302 : 303 : ulong 304 : fd_stake_delegations_cnt( fd_stake_delegations_t const * stake_delegations ); 305 : 306 : static inline ulong 307 3 : fd_stake_delegations_max( fd_stake_delegations_t const * stake_delegations ) { 308 3 : if( FD_UNLIKELY( !stake_delegations ) ) { 309 0 : FD_LOG_CRIT(( "NULL stake_delegations" )); 310 0 : } 311 : 312 3 : return stake_delegations->max_stake_accounts_; 313 3 : } 314 : 315 : /* Iterator API for stake delegations. The iterator is initialized with 316 : a call to fd_stake_delegations_iter_init. The caller is responsible 317 : for managing the memory for the iterator. It is safe to call 318 : fd_stake_delegations_iter_next if the result of 319 : fd_stake_delegations_iter_done() ==0. It is safe to call 320 : fd_stake_delegations_iter_ele() to get the current stake delegation. 321 : As a note, it is safe to modify the stake delegation acquired from 322 : fd_stake_delegations_iter_ele() as long as the next_ field is not 323 : modified (which the caller should never do). It is unsafe to insert 324 : or remove fd_stake_delegation_t from the stake delegations struct 325 : while iterating. 326 : 327 : Under the hood, the iterator is just a wrapper over the iterator in 328 : fd_map_chain.c. 329 : 330 : Example use: 331 : 332 : fd_stake_delegations_iter_t iter_[1]; 333 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations ); !fd_stake_delegations_iter_done( iter ); fd_stake_delegations_iter_next( iter ) ) { 334 : fd_stake_delegation_t * stake_delegation = fd_stake_delegations_iter_ele( iter ); 335 : // Do something with the stake delegation ... 336 : } 337 : */ 338 : 339 : fd_stake_delegation_t * 340 : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t * iter ); 341 : 342 : fd_stake_delegations_iter_t * 343 : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t * iter, 344 : fd_stake_delegations_t const * stake_delegations ); 345 : 346 : void 347 : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter ); 348 : 349 : int 350 : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter ); 351 : 352 : FD_PROTOTYPES_END 353 : 354 : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */