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