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 12 : #define FD_STAKE_DELEGATIONS_MAGIC (0xF17EDA2CE757A3E0) /* FIREDANCER STAKE V0 */
11 :
12 : /* fd_stake_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_stake_delegations_t is split into two:
20 : 1. The entire set of stake delegations are stored in the root as a
21 : map/pool pair. This root state is setup at boot (on snapshot
22 : load) and is not directly modified after that point.
23 : 2. As banks/forks execute, they will maintain a delta-based
24 : representation of the stake delegations. Each fork will hold its
25 : own set of deltas. These are then applied to the root set when
26 : the fork is finalized. This is implemented as each bank having
27 : its own dlist of deltas which are allocated from a pool which is
28 : shared across all stake delegation forks. The caller is expected
29 : to create a new fork index for each bank and add deltas to it.
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_stake_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 concurrency model is limited: most operations are not allowed to
62 : be concurrent with each other with the exception of operations that
63 : operate on the stake delegations's delta pool:
64 : fd_stake_delegations_fork_update()
65 : fd_stake_delegations_fork_remove()
66 : fd_stake_delegations_evict_fork()
67 : These operations are internally synchronized with a read-write lock
68 : because multiple executor tiles may be trying to call
69 : stake_delegations_fork_update() at the same time, and the replay tile
70 : can simulatenously be calling fd_stake_delegations_evict_fork()
71 : */
72 :
73 671 : #define FD_STAKE_DELEGATIONS_ALIGN (128UL)
74 :
75 : #define FD_STAKE_DELEGATIONS_FORK_MAX (4096UL)
76 :
77 : /* The warmup cooldown rate can only be one of two values: 0.25 or 0.09.
78 : The reason that the double is mapped to an enum is to save space in
79 : the stake delegations struct. */
80 331 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 (0)
81 0 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009 (1)
82 30 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 (0.25)
83 0 : #define FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009 (0.09)
84 :
85 : struct fd_stake_delegation {
86 : fd_pubkey_t stake_account;
87 : fd_pubkey_t vote_account;
88 : ulong stake;
89 : ulong credits_observed;
90 : uint next_; /* Internal pool/map/dlist usage */
91 :
92 : union {
93 : uint prev_; /* Internal dlist usage for delta */
94 : uint delta_idx; /* Tracking for stake delegation iteration */
95 : };
96 : ushort activation_epoch;
97 : ushort deactivation_epoch;
98 : union {
99 : uchar is_tombstone; /* Internal dlist/delta usage */
100 : uchar dne_in_root; /* Tracking for stake delegation iteration */
101 : };
102 : uchar warmup_cooldown_rate; /* enum representing 0.25 or 0.09 */
103 : };
104 : typedef struct fd_stake_delegation fd_stake_delegation_t;
105 :
106 : struct fd_stake_delegations {
107 : ulong magic;
108 : ulong expected_stake_accounts_;
109 : ulong max_stake_accounts_;
110 :
111 : /* Root map + pool */
112 : ulong map_offset_;
113 : ulong pool_offset_;
114 :
115 : /* Delta pool + fork */
116 : ulong delta_pool_offset_;
117 : ulong fork_pool_offset_;
118 : ulong dlist_offsets_[ FD_STAKE_DELEGATIONS_FORK_MAX ];
119 : fd_rwlock_t delta_lock;
120 : };
121 : typedef struct fd_stake_delegations fd_stake_delegations_t;
122 :
123 : /* Forward declare map iterator API generated by fd_map_chain.c */
124 : typedef struct root_map_private root_map_t;
125 : typedef struct fd_map_chain_iter fd_stake_delegation_map_iter_t;
126 : struct fd_stake_delegations_iter {
127 : root_map_t * root_map;
128 : fd_stake_delegation_t * root_pool;
129 : fd_stake_delegation_t * delta_pool;
130 : fd_stake_delegation_map_iter_t iter;
131 : };
132 : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t;
133 :
134 : FD_PROTOTYPES_BEGIN
135 :
136 : static inline double
137 30 : fd_stake_delegations_warmup_cooldown_rate_to_double( uchar warmup_cooldown_rate ) {
138 30 : return warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025 ? FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 : FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009;
139 30 : }
140 :
141 : static inline uchar
142 301 : fd_stake_delegations_warmup_cooldown_rate_enum( double warmup_cooldown_rate ) {
143 : /* TODO: Replace with fd_double_eq */
144 301 : if( FD_LIKELY( warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_025 ) ) {
145 301 : return FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_025;
146 301 : } else if( FD_LIKELY( warmup_cooldown_rate==FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_009 ) ) {
147 0 : return FD_STAKE_DELEGATIONS_WARMUP_COOLDOWN_RATE_ENUM_009;
148 0 : }
149 0 : FD_LOG_CRIT(( "Invalid warmup cooldown rate %f", warmup_cooldown_rate ));
150 0 : }
151 :
152 : /* fd_stake_delegations_align returns the alignment of the stake
153 : delegations struct. */
154 :
155 : ulong
156 : fd_stake_delegations_align( void );
157 :
158 : /* fd_stake_delegations_footprint returns the footprint of the stake
159 : delegations struct for a given amount of max stake accounts,
160 : expected stake accounts, and max live slots. */
161 :
162 : ulong
163 : fd_stake_delegations_footprint( ulong max_stake_accounts,
164 : ulong expected_stake_accounts,
165 : ulong max_live_slots );
166 :
167 : /* fd_stake_delegations_new creates a new stake delegations struct
168 : with a given amount of max and expected stake accounts and max live
169 : slots. It formats a memory region which is sized based off the pool
170 : capacity, expected map occupancy, and per-fork delta structures. */
171 :
172 : void *
173 : fd_stake_delegations_new( void * mem,
174 : ulong seed,
175 : ulong max_stake_accounts,
176 : ulong expected_stake_accounts,
177 : ulong max_live_slots );
178 :
179 : /* fd_stake_delegations_join joins a stake delegations struct from a
180 : memory region. There can be multiple valid joins for a given memory
181 : region but the caller is responsible for accessing memory in a
182 : thread-safe manner. */
183 :
184 : fd_stake_delegations_t *
185 : fd_stake_delegations_join( void * mem );
186 :
187 : /* fd_stake_delegations_init resets the state of a valid join of a
188 : stake delegations struct. Specifically, it only resets the root
189 : state, leaving the deltas intact. */
190 :
191 : void
192 : fd_stake_delegations_init( fd_stake_delegations_t * stake_delegations );
193 :
194 : /* fd_stake_delegations_root_update will either insert a new stake
195 : delegation if the pubkey doesn't exist yet, or it will update the
196 : stake delegation for the pubkey if already in the map, overriding any
197 : previous data. fd_stake_delegations_t must be a valid local join. */
198 :
199 : void
200 : fd_stake_delegations_root_update( fd_stake_delegations_t * stake_delegations,
201 : fd_pubkey_t const * stake_account,
202 : fd_pubkey_t const * vote_account,
203 : ulong stake,
204 : ulong activation_epoch,
205 : ulong deactivation_epoch,
206 : ulong credits_observed,
207 : double warmup_cooldown_rate );
208 :
209 : /* fd_stake_delegations_refresh is used to refresh the stake
210 : delegations stored in fd_stake_delegations_t which is owned by
211 : the bank. For a given database handle, read in the state of all
212 : stake accounts, decode their state, and update each stake delegation.
213 : This is meant to be called before any slots are executed, but after
214 : the snapshot has finished loading.
215 :
216 : Before this function is called, there are some important assumptions
217 : made about the state of the stake delegations:
218 : 1. fd_stake_delegations_t is not missing any valid entries
219 : 2. fd_stake_delegations_t may have some invalid entries that should
220 : be removed
221 :
222 : fd_stake_delegations_refresh will remove all of the invalid entries
223 : that are detected. An entry is considered invalid if the stake
224 : account does not exist (e.g. zero balance or no record) or if it
225 : has invalid state (e.g. not a stake account or invalid bincode data).
226 : No new entries are added to the struct at this point. */
227 :
228 : void
229 : fd_stake_delegations_refresh( fd_stake_delegations_t * stake_delegations,
230 : fd_accdb_user_t * accdb,
231 : fd_funk_txn_xid_t const * xid );
232 :
233 : /* fd_stake_delegations_cnt returns the number of stake delegations
234 : in the base of stake delegations struct. */
235 :
236 : ulong
237 : fd_stake_delegations_cnt( fd_stake_delegations_t const * stake_delegations );
238 :
239 : /* fd_stake_delegations_new_fork allocates a new fork index for the
240 : stake delegations. The fork index is returned to the caller. */
241 :
242 : ushort
243 : fd_stake_delegations_new_fork( fd_stake_delegations_t * stake_delegations );
244 :
245 : /* fd_stake_delegations_fork_update will insert a new stake delegation
246 : delta for the fork. If an entry already exists in the fork, a new
247 : one will be inserted without removing the old one.
248 :
249 : TODO: Add a per fork map so multiple entries aren't needed for the
250 : same stake account. */
251 :
252 : void
253 : fd_stake_delegations_fork_update( fd_stake_delegations_t * stake_delegations,
254 : ushort fork_idx,
255 : fd_pubkey_t const * stake_account,
256 : fd_pubkey_t const * vote_account,
257 : ulong stake,
258 : ulong activation_epoch,
259 : ulong deactivation_epoch,
260 : ulong credits_observed,
261 : double warmup_cooldown_rate );
262 :
263 : /* fd_stake_delegations_fork_remove inserts a tombstone stake delegation
264 : entry for the given fork. The function will not actually remove or
265 : free any resources corresponding to the stake account. The reason a
266 : tombstone is stored is because each fork corresponds to a set of
267 : stake delegation deltas for a given slot. This function may insert a
268 : 'duplicate' entry for the same stake account but it will be resolved
269 : by the time the delta is applied to a base stake delegations
270 : object. */
271 :
272 : void
273 : fd_stake_delegations_fork_remove( fd_stake_delegations_t * stake_delegations,
274 : ushort fork_idx,
275 : fd_pubkey_t const * stake_account );
276 :
277 : /* fd_stake_delegations_evict_fork removes/frees all stake delegation
278 : entries for a given fork. After this function is called it is no
279 : longer safe to have any references to the fork index (until it is
280 : reused via a call to fd_stake_delegations_new_fork). The caller is
281 : responsible for making sure references to this fork index are not
282 : being held. */
283 :
284 : void
285 : fd_stake_delegations_evict_fork( fd_stake_delegations_t * stake_delegations,
286 : ushort fork_idx );
287 :
288 : /* fd_stake_delegations_apply_fork_delta merges all stake delegation
289 : entries for fork_idx into the root map: non-tombstone entries are
290 : applied via fd_stake_delegations_root_update; tombstone entries remove
291 : the corresponding stake account from the root map. Caller must
292 : ensure no concurrent iteration on stake_delegations for this fork. */
293 :
294 : void
295 : fd_stake_delegations_apply_fork_delta( fd_stake_delegations_t * stake_delegations,
296 : ushort fork_idx );
297 :
298 : /* fd_stake_delegations_{mark,unmark}_delta are used to temporarily
299 : tag delta elements from a given fork in the base/root stake
300 : delegation map/pool. This allows the caller to then iterator over
301 : the stake delegations for a given bank using just the deltas and the
302 : root without creating a copy. Each delta that is marked, must be
303 : unmarked after the caller is done iterating over the stake
304 : delegations.
305 :
306 : Under the hood, it reuses internal pointers for elements in the root
307 : map to point to the corresponding delta element. If the element is
308 : removed by a delta another field will be reused to ignore it during
309 : iteration. If an element is inserted by a delta, it will be
310 : temporarily added to the root, but will be removed with a call to
311 : unmark_delta. */
312 :
313 : void
314 : fd_stake_delegations_mark_delta( fd_stake_delegations_t * stake_delegations,
315 : ushort fork_idx );
316 :
317 : void
318 : fd_stake_delegations_unmark_delta( fd_stake_delegations_t * stake_delegations,
319 : ushort fork_idx );
320 :
321 : /* Iterator API for stake delegations. The iterator is initialized with
322 : a call to fd_stake_delegations_iter_init. The caller is responsible
323 : for managing the memory for the iterator. It is safe to call
324 : fd_stake_delegations_iter_next if the result of
325 : fd_stake_delegations_iter_done()==0. It is safe to call
326 : fd_stake_delegations_iter_ele() to get the current stake delegation
327 : or fd_stake_delegations_iter_idx() to get the index of the current
328 : stake delegation. It is not safe to modify the stake delegation
329 : while iterating through it.
330 :
331 : Under the hood, the iterator is just a wrapper over the iterator in
332 : fd_map_chain.c.
333 :
334 : Example use:
335 :
336 : fd_stake_delegations_iter_t iter_[1];
337 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
338 : !fd_stake_delegations_iter_done( iter );
339 : fd_stake_delegations_iter_next( iter ) ) {
340 : fd_stake_delegation_t * stake_delegation = fd_stake_delegations_iter_ele( iter );
341 : }
342 : */
343 :
344 : fd_stake_delegation_t const *
345 : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t * iter );
346 :
347 : ulong
348 : fd_stake_delegations_iter_idx( fd_stake_delegations_iter_t * iter );
349 :
350 : fd_stake_delegations_iter_t *
351 : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t * iter,
352 : fd_stake_delegations_t const * stake_delegations );
353 :
354 : void
355 : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter );
356 :
357 : int
358 : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter );
359 :
360 : FD_PROTOTYPES_END
361 :
362 : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */
|