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 "../runtime/sysvar/fd_sysvar_base.h"
5 : #include "../accdb/fd_accdb.h"
6 :
7 228 : #define FD_STAKE_DELEGATIONS_MAGIC (0xF17EDA2CE757A3E1) /* FIREDANCER STAKE V1 */
8 :
9 : /* fd_stake_delegations_t is a cache of stake accounts mapping the
10 : pubkey of the stake account to various information including
11 : stake, activation/deactivation epoch, corresponding vote_account,
12 : and credits observed. This is used to quickly iterate through all
13 : of the stake delegations in the system during epoch boundary
14 : reward calculations.
15 :
16 : The implementation of fd_stake_delegations_t is split into two:
17 : 1. The entire set of stake delegations are stored in the in-memory
18 : root map/pool and, when needed, the disk root store. This root
19 : state is setup at boot (on snapshot load) and is not directly
20 : modified after that point.
21 : 2. As banks/forks execute, they will maintain a delta-based
22 : representation of the stake delegations. Each fork will hold its
23 : own set of deltas. These are then applied to the root set when
24 : the fork is finalized. Deltas share one map keyed by stake
25 : account and fork index, and one pool across all forks. Each
26 : fork maintains a list of its deltas. The caller is expected to
27 : create a new fork index for each bank and add deltas to it.
28 :
29 : Root and delta entries that exceed their respective in-memory pools
30 : spill to disk as full fd_stake_delegation_t records. Root records
31 : are keyed by stake pubkey. Delta records are keyed by fork index
32 : and stake pubkey, so sibling forks retain independent versions.
33 :
34 : There are some important invariants wrt fd_stake_delegations_t:
35 : 1. After execution has started, there will be no invalid stake
36 : accounts in the stake delegations struct.
37 : 2. The stake delegations struct can have valid delegations for vote
38 : accounts which no longer exist.
39 : 3. There are no stake accounts which are valid delegations which
40 : exist in the accounts database but not in fd_stake_delegations_t.
41 :
42 : In practice, fd_stake_delegations_t are updated in 3 cases:
43 : 1. During snapshot boot, snapin populates the root cache directly
44 : from the account stream. The cache is refreshed after all
45 : accounts are loaded to resolve duplicate account versions, remove
46 : stale entries, and calculate activation state.
47 :
48 : https://github.com/anza-xyz/agave/blob/v2.3.6/runtime/src/bank.rs#L1780-L1806
49 :
50 : 2. After transaction execution. If an update is made to a stake
51 : account, the updated state is reflected in the cache (or the entry
52 : is evicted).
53 : 3. During rewards distribution. Stake accounts are partitioned over
54 : several hundred slots where their rewards are distributed. In this
55 : case, the cache is updated to reflect each stake account post
56 : reward distribution.
57 : The stake accounts are read-only during the epoch boundary.
58 :
59 : The concurrency model is: every mutating operation takes the struct's
60 : write lock for its whole duration, so mutators are safe to call
61 : concurrently from any tile. fd_stake_delegations_view_{begin,end}
62 : and the iterator are the exception: view_begin takes the write lock
63 : and view_end releases it, so it is held across the whole
64 : view_begin/iterate/view_end bracket.
65 :
66 : max_disk_records bounds the number of delta records that can spill
67 : to disk. The disk root capacity is max_stake_accounts plus twice
68 : max_disk_records. */
69 :
70 7470 : #define FD_STAKE_DELEGATIONS_ALIGN (128UL)
71 872313 : #define FD_STAKE_DELEGATIONS_FORK_MAX (65534UL)
72 :
73 : /* 123454 is vote stakes, 123455 is cost tracker, 123456 is epoch
74 : credits, 123458/123459 are store, 123460/123461 are accdb, and
75 : 123462+ are reserved by XDP. */
76 :
77 285 : #define FD_STAKE_DELEGATIONS_FD (123457)
78 :
79 : /* delta_idx uses the high bit to distinguish a dense disk-delta index
80 : from an in-memory delta-pool index. */
81 :
82 20928 : #define FD_STAKE_DELEGATIONS_DELTA_DISK_TAG (1U<<31)
83 13344 : #define FD_STAKE_DELEGATIONS_DELTA_IDX_MASK (FD_STAKE_DELEGATIONS_DELTA_DISK_TAG-1U)
84 :
85 : /* Most stake delegations are stable. So intuitively, there should be a
86 : way to return their effective stake in O(1). Essentially, at a given
87 : target_epoch, if we know that the delegation is in a stable state for
88 : the purposes of effective stake evaluation, then we can simply return
89 : the fully activated stake for WARMED, or 0 for COOLED, without
90 : running any warmup/cooldown simulation. The vast majority of
91 : delegations are in fact stable and can take the fast path for
92 : effective stake evaluation.
93 :
94 : We trust a tag's prescription of stable state (WARMED/COOLED) if
95 :
96 : - The delegation record (stake,activation_epoch,deactivation_epoch)
97 : hasn't changed since the delegation was most recently evaluated and
98 : tagged at tag_epoch
99 : - tag_epoch<=target_epoch
100 :
101 : Condition #1 is maintained by how delegations are tagged. Only root
102 : records can take on non-UNKNOWN tags. Delta records are
103 : unconditionally UNKNOWN. All delegation-updating operations funnel
104 : the delegation through a delta, so the delegation effectively gets
105 : invalidated for stable state query purposes.
106 :
107 : Condition #2 ultimately has to be maintained by the user of the tag
108 : who provides target_epoch. A key invariant here is that stable state
109 : tags are awarded when the delta list gets folded into the root state,
110 : aka when a block roots. Currently, the only use cases of the tag are
111 : at the boundary.
112 :
113 : - For the refresh_vote_accounts() use case, the target_epoch is the
114 : upcoming epoch, which is naturally the largest epoch in the
115 : cluster. Since tag_epoch was sometime in the past when a block
116 : rooted, tag_epoch<=target_epoch holds trivially.
117 : - For the reward stake accumulation in refresh_vote_accounts(),
118 : recall that rewarded_epoch is the just-ended epoch. Since we are
119 : at the boundary of rewarded_epoch=>rewarded_epoch+1, we know that
120 : tag_epoch<=rewarded_epoch, because no slot has rooted for
121 : rewarded_epoch+1 yet. So if we constrain the target_epoch to be
122 : exactly rewarded_epoch, we get tag_epoch<=target_epoch.
123 :
124 : WARMED and COOLED are stable states and are the only tags that the
125 : fast paths act on. The unstable state tags (WARMING/COOLING) are
126 : defined for clarity and do not enable any fast path. As a side note,
127 : WARMING tags get a chance to be promoted to WARMED if the delegation
128 : gets any inflation rewards or is otherwise written. At rewards
129 : distribution time, the delegation will re-enter a delta tier and
130 : shortly afterwards get a chance to be re-classified when the
131 : distribution block roots. Fresh dust delegations that don't get any
132 : rewards will be sticky WARMING until the next boot or a write. */
133 24 : #define FD_STAKE_DELEGATION_STATE_UNKNOWN ((uchar)0)
134 75 : #define FD_STAKE_DELEGATION_STATE_WARMING ((uchar)1) /* activating */
135 429 : #define FD_STAKE_DELEGATION_STATE_WARMED ((uchar)2) /* effective=delegated */
136 3 : #define FD_STAKE_DELEGATION_STATE_COOLING ((uchar)3) /* deactivating */
137 708 : #define FD_STAKE_DELEGATION_STATE_COOLED ((uchar)4) /* effective=0 */
138 :
139 : struct fd_stake_delegation_key {
140 : fd_pubkey_t stake_account;
141 : ushort fork_idx;
142 : };
143 : typedef struct fd_stake_delegation_key fd_stake_delegation_key_t;
144 :
145 : struct fd_stake_delegation {
146 : /* The delta map uses key; root lookups use stake_account directly. */
147 : union {
148 : fd_stake_delegation_key_t key;
149 : struct {
150 : fd_pubkey_t stake_account;
151 : ushort fork_idx; /* In-memory delta's fork index */
152 : };
153 : };
154 : ushort activation_epoch;
155 : ushort deactivation_epoch;
156 : union {
157 : /* No storage conflict because one is for delta records and the
158 : other for root records. */
159 : uchar is_tombstone; /* Internal delta usage */
160 : uchar dne_in_root; /* Tracking for stake delegation iteration */
161 : };
162 : uchar in_use : 1; /* For the in-memory root pool only. Not meaningful in the
163 : delta pool. Set to 1 if this element holds a live delegation
164 : present in the root map, 0 if the element has been reclaimed. */
165 : uchar state : 3; /* Can only be non-UNKNOWN in a root record. */
166 : fd_pubkey_t vote_account;
167 : ulong stake;
168 : ulong lamports;
169 : ulong credits_observed;
170 : uint acc_dlen;
171 : uint next_; /* Internal pool/map usage */
172 : union {
173 : uint delta_idx; /* Root's in-memory/disk delta reference for iteration */
174 : uint fork_next; /* Next in-memory delta in this fork */
175 : };
176 : uint slot; /* Snapshot loading only: slot of the account version this
177 : record came from, newest wins. Runtime writes store 0. */
178 : };
179 : typedef struct fd_stake_delegation fd_stake_delegation_t;
180 :
181 : FD_STATIC_ASSERT( sizeof(fd_stake_delegation_t)==112UL, fd_stake_delegation );
182 :
183 : typedef struct fd_stake_delegations fd_stake_delegations_t;
184 :
185 5286 : #define FD_STAKE_DELEGATIONS_ITER_BATCH_CNT (128UL)
186 :
187 : struct fd_stake_delegations_iter {
188 : fd_stake_delegation_t * root_pool;
189 : fd_stake_delegation_t * delta_pool;
190 : fd_stake_delegation_t * ele;
191 : fd_stake_delegations_t const * stake_delegations;
192 : ulong idx; /* externally visible index */
193 : ulong wmk; /* in-memory root watermark */
194 : ulong root_max; /* in-memory root capacity */
195 : ulong disk_idx; /* dense disk-root cursor */
196 : fd_stake_delegation_t disk_ele;
197 : fd_stake_delegation_t disk_batch[ FD_STAKE_DELEGATIONS_ITER_BATCH_CNT ];
198 : };
199 : typedef struct fd_stake_delegations_iter fd_stake_delegations_iter_t;
200 :
201 : #include "fd_stake_delegations_private.h"
202 :
203 : FD_PROTOTYPES_BEGIN
204 :
205 : /* Classify stake given the activation status evaluated at the provided
206 : epoch. The provided epoch is expected to be >= activation epoch. */
207 : static inline uchar
208 : fd_stake_delegation_classify( fd_stake_delegation_t const * delegation,
209 : fd_stake_history_entry_t activation_status,
210 537 : ulong epoch ) {
211 : /* Activation epoch in Agave's stake program is either clock.epoch, or
212 : inherited from an existing activation epoch, so activation epoch <=
213 : current epoch always holds for delegations created by the stake
214 : program. Synthetic inputs do not conform to this, so we mark them
215 : UNKNOWN to force the slow path. */
216 537 : if( FD_UNLIKELY( delegation->activation_epoch!=(ushort)USHORT_MAX && epoch<delegation->activation_epoch ) ) {
217 18 : return FD_STAKE_DELEGATION_STATE_UNKNOWN;
218 18 : }
219 :
220 519 : if( activation_status.activating>0UL ) return FD_STAKE_DELEGATION_STATE_WARMING;
221 444 : if( activation_status.deactivating>0UL ) return FD_STAKE_DELEGATION_STATE_COOLING;
222 441 : if( activation_status.effective==delegation->stake && delegation->deactivation_epoch==(ushort)USHORT_MAX ) return FD_STAKE_DELEGATION_STATE_WARMED;
223 :
224 : /* When evaluated at >= activation_epoch, (0,0,0) implies a fully
225 : cooled delegation. One might think that we could simply
226 :
227 : if( activation_status.effective==0UL ) return FD_STAKE_DELEGATION_STATE_COOLED;
228 :
229 : and life would be great. In an unfortunate turn, Agave has a
230 : special branch that will assume stake has been fully activated if
231 : the activation epoch is not found in the stake history sysvar,
232 : regardless of whether the delegation fully warmed up or not when
233 : the simulation ran faithfully from the activation epoch.
234 :
235 : https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L969
236 :
237 : This means that a COOLED tag doesn't necessarily survive against
238 : future stake history sysvars. As the stake history sysvar window
239 : advances and evicts older epochs, a delegation's activation epoch
240 : will eventually be evicted. At that point an effective stake
241 : simulation would cooldown from the full delegated amount, which
242 : might be more than the effective stake simulated at tag time, if
243 : the delegation only partially warmed up at deactivation epoch.
244 : This can in theory lead to a nonzero effective stake at the target
245 : epoch, once the activation epoch is evicted, contradicting the
246 : COOLED tag. Note that this is exceedingly hard to pull off as it
247 : requires that (1) the delegation only partially warmed up at
248 : deactivation, and (2) the delegation failed to fully cooldown from
249 : the full delegated amount over the potentially hundreds of epochs
250 : between deactivation epoch and target epoch. AKA either warmup or
251 : cooldown congestion in the cluster over an extended period of time.
252 : The upshot is that we will only award the COOLED tag when the
253 : delegation is always COOLED independent of the history sysvar.
254 : This safe verdict loses by a few millis at the boundary, mostly in
255 : the refresh_vote_accounts() phase, compared to the naive but unsafe
256 : classify.
257 :
258 : https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L877
259 : https://github.com/solana-program/stake/blob/interface%40v4.3.1/interface/src/state.rs#L896
260 :
261 : Note that the same misfortune doesn't apply to the WARMED tag. The
262 : sysvar query miss's "assume fully effective" bias means that a
263 : WARMED delegation stays warmed forever until it's instructed to
264 : deactivate. */
265 12 : if( epoch>(delegation->deactivation_epoch+FD_SYSVAR_STAKE_HISTORY_CAP) || delegation->activation_epoch==delegation->deactivation_epoch || delegation->stake==0UL ) return FD_STAKE_DELEGATION_STATE_COOLED;
266 6 : return FD_STAKE_DELEGATION_STATE_UNKNOWN;
267 12 : }
268 :
269 :
270 : /* fd_stake_delegations_align returns the alignment of the stake
271 : delegations struct. */
272 :
273 : ulong
274 : fd_stake_delegations_align( void );
275 :
276 : /* fd_stake_delegations_footprint returns the footprint of the stake
277 : delegations struct for the given root pool capacity and max live
278 : slots. The in-memory delta pool has the same capacity as the root
279 : pool (max_stake_accounts); further deltas spill to disk. */
280 :
281 : ulong
282 : fd_stake_delegations_footprint( ulong max_stake_accounts,
283 : ulong max_live_slots );
284 :
285 : /* fd_stake_delegations_new creates a new stake delegations struct with
286 : the given backing descriptor, in-memory pool capacity, disk spill
287 : capacity, and max live slots. Every process joining the object must
288 : have the same backing file open at disk_fd. It formats a memory
289 : region sized from the pool capacity and per-fork delta structures. */
290 :
291 : void *
292 : fd_stake_delegations_new( void * mem,
293 : int disk_fd,
294 : ulong seed,
295 : ulong max_stake_accounts,
296 : ulong max_disk_records,
297 : ulong max_live_slots );
298 :
299 : /* fd_stake_delegations_join joins a stake delegations struct from a
300 : memory region. disk_fd must match the descriptor number provided to
301 : fd_stake_delegations_new. There can be multiple valid joins for a
302 : given memory region but the caller is responsible for accessing
303 : memory in a thread-safe manner. */
304 :
305 : fd_stake_delegations_t *
306 : fd_stake_delegations_join( void * mem,
307 : int disk_fd );
308 :
309 : /* fd_stake_delegations_reset resets delegations to the post-new state. */
310 :
311 : void
312 : fd_stake_delegations_reset( fd_stake_delegations_t * stake_delegations );
313 :
314 : /* Read the current aggregate stake totals. During a frontier query,
315 : these include the marked deltas. The returned epoch is zero; callers
316 : supply the epoch when constructing a stake-history entry. The caller
317 : must exclude concurrent mutations or hold the frontier-query lock. */
318 :
319 : fd_stake_history_entry_t
320 : fd_stake_delegations_totals( fd_stake_delegations_t const * stake_delegations );
321 :
322 : /* fd_stake_delegations_metrics_t holds the in-memory occupancy and disk
323 : spill metrics. *_spilled_cnt and disk_* only grow; disk_ticks is in
324 : fd_tickcount() units. */
325 :
326 : struct fd_stake_delegations_metrics {
327 : ulong root_used;
328 : ulong root_capacity;
329 : ulong root_disk_cnt;
330 : ulong delta_used;
331 : ulong delta_capacity;
332 : ulong delta_disk_cnt;
333 : ulong root_spilled_cnt;
334 : ulong delta_spilled_cnt;
335 : ulong disk_read_cnt;
336 : ulong disk_written_cnt;
337 : ulong disk_ticks;
338 : };
339 :
340 : typedef struct fd_stake_delegations_metrics fd_stake_delegations_metrics_t;
341 :
342 : /* fd_stake_delegations_metrics_snap copies the metrics of
343 : stake_delegations into out without taking the lock, so values from
344 : different fields may be slightly out of step. */
345 :
346 : void
347 : fd_stake_delegations_metrics_snap( fd_stake_delegations_t const * stake_delegations,
348 : fd_stake_delegations_metrics_t * out );
349 :
350 : /* Replace aggregate totals when the root crosses an epoch boundary.
351 : Acquires the store write lock; do not call inside a frontier query. */
352 :
353 : void
354 : fd_stake_delegations_set_totals( fd_stake_delegations_t * stake_delegations,
355 : ulong effective,
356 : ulong activating,
357 : ulong deactivating );
358 :
359 : /* fd_stake_delegations_root_update upserts a stake delegation into the
360 : in-memory or disk root, overriding any previous data. */
361 :
362 : void
363 : fd_stake_delegations_root_update( fd_stake_delegations_t * stake_delegations,
364 : fd_pubkey_t const * stake_account,
365 : fd_pubkey_t const * vote_account,
366 : ulong stake,
367 : ulong activation_epoch,
368 : ulong deactivation_epoch,
369 : ulong credits_observed,
370 : ulong lamports,
371 : uint acc_dlen );
372 :
373 : /* fd_stake_delegations_prune_inactive_root removes root delegations
374 : that are inactive in both epoch and epoch-1. This function removes
375 : all inactive delegations from the in-memory and disk roots. It is a
376 : parallel to Agave removing inactive stake accounts directly at the
377 : epoch boundary. Returns the number of delegations removed. When
378 : emit_bank is non-NULL, a runtime_stake_delegation remove event is
379 : emitted for each removal (NULL to skip). */
380 :
381 : ulong
382 : fd_stake_delegations_prune_inactive_root( fd_stake_delegations_t * stake_delegations,
383 : ulong epoch,
384 : fd_stake_history_t const * stake_history,
385 : ulong * warmup_cooldown_rate_epoch,
386 : fd_bank_t const * emit_bank );
387 :
388 : /* fd_stake_delegations_refresh finalizes the root after a snapshot or
389 : genesis load, before any slot executes. Pure in-memory pass: drops
390 : tombstones, drops delegations inactive in both epoch and epoch-1
391 : when remove_inactive_stakes is set, sets the warmup rate and state
392 : tag, accumulates the stake totals and refills the pool from disk
393 : roots. stake_history may be NULL. */
394 :
395 : void
396 : fd_stake_delegations_refresh( fd_stake_delegations_t * stake_delegations,
397 : ulong epoch,
398 : fd_stake_history_t const * stake_history,
399 : ulong * warmup_cooldown_rate_epoch,
400 : int remove_inactive_stakes );
401 :
402 : /* fd_stake_delegations_vote_account_status sums the root activation
403 : status of every delegation to vote_account at epoch. Acquires the
404 : store write lock; do not call inside a frontier query. stake_history
405 : may be NULL. */
406 :
407 : fd_stake_history_entry_t
408 : fd_stake_delegations_vote_account_status( fd_stake_delegations_t * stake_delegations,
409 : fd_pubkey_t const * vote_account,
410 : ulong epoch,
411 : fd_stake_history_t const * stake_history,
412 : ulong * warmup_cooldown_rate_epoch );
413 :
414 : /* fd_stake_delegations_new_fork allocates a child of parent_fork_idx.
415 : USHORT_MAX indicates that the parent is the root. */
416 :
417 : ushort
418 : fd_stake_delegations_new_fork( fd_stake_delegations_t * stake_delegations,
419 : ushort parent_fork_idx );
420 :
421 : /* fd_stake_delegations_fork_update upserts a stake delegation into the
422 : fork's deltas, or into the root when fork_idx is USHORT_MAX. slot is
423 : the slot of the account version being written: a write is dropped if
424 : the entry already holds a newer slot, so snapin tiles can apply
425 : versions in any order. Runtime callers pass 0. */
426 :
427 : void
428 : fd_stake_delegations_fork_update( fd_stake_delegations_t * stake_delegations,
429 : ushort fork_idx,
430 : ulong slot,
431 : fd_pubkey_t const * stake_account,
432 : fd_pubkey_t const * vote_account,
433 : ulong stake,
434 : ulong activation_epoch,
435 : ulong deactivation_epoch,
436 : ulong credits_observed,
437 : ulong lamports,
438 : uint acc_dlen );
439 :
440 : /* fd_stake_delegations_fork_remove inserts a tombstone stake delegation
441 : entry for the given fork, or for the root when fork_idx is
442 : USHORT_MAX. The function will not actually remove or free any
443 : resources corresponding to the stake account. The reason a
444 : tombstone is stored is because each fork corresponds to a set of
445 : stake delegation deltas for a given slot. If an entry already
446 : exists for the stake account in this fork, it is overwritten in
447 : place. slot is as in fd_stake_delegations_fork_update.
448 : replacing_full_entry means the replaced version was loaded by the
449 : full snapshot and this one by the incremental, so an account not in
450 : the root is left alone. */
451 :
452 : void
453 : fd_stake_delegations_fork_remove( fd_stake_delegations_t * stake_delegations,
454 : ushort fork_idx,
455 : ulong slot,
456 : fd_pubkey_t const * stake_account,
457 : int replacing_full_entry );
458 :
459 : /* fd_stake_delegations_evict_fork frees a fork's deltas and its ID.
460 : The caller must no longer need this fork or query its descendants.
461 : Root application detaches surviving children before this is called. */
462 :
463 : void
464 : fd_stake_delegations_evict_fork( fd_stake_delegations_t * stake_delegations,
465 : ushort fork_idx );
466 :
467 : /* upserts and removes accumulate across applications. root_cnt is
468 : overwritten with the current in-memory and disk root count. */
469 :
470 : struct fd_stake_delegations_delta_stats {
471 : ulong upserts;
472 : ulong removes;
473 : ulong root_cnt;
474 : };
475 : typedef struct fd_stake_delegations_delta_stats fd_stake_delegations_delta_stats_t;
476 :
477 : /* fd_stake_delegations_advance_root applies the target and its
478 : ancestors in root-to-leaf order. Its direct children then inherit
479 : from rooted state. The caller frees the applied fork descriptors
480 : with evict_fork. USHORT_MAX applies no deltas.
481 :
482 : skip_stake_math applies the deltas without updating totals or state,
483 : for the snapshot loader; fd_stake_delegations_refresh recomputes them
484 : afterwards and the stake history arguments may be NULL.
485 :
486 : If stake_delegations_delta_stats is non-NULL, upserts and removes
487 : accumulate into it (caller zeroes). */
488 :
489 : void
490 : fd_stake_delegations_advance_root( ulong epoch,
491 : fd_stake_history_t const * stake_history,
492 : ulong * warmup_cooldown_rate_epoch,
493 : int skip_stake_math,
494 : fd_stake_delegations_t * stake_delegations,
495 : ushort fork_idx,
496 : fd_stake_delegations_delta_stats_t * stake_delegations_delta_stats );
497 :
498 : /* fd_stake_delegations_view_{begin,end} temporarily overlay
499 : delta elements from the target fork's ancestry onto the base/root
500 : stake delegation stores. This allows the caller to iterate over the
501 : delegations for a bank using the root and its deltas without creating
502 : a copy.
503 :
504 : Under the hood, each in-memory or disk root record points to the
505 : corresponding in-memory or disk delta. If an element is inserted by
506 : a delta, a temporary root record is added and then removed by
507 : view_end. These functions also temporarily update and
508 : unwind the stake totals for the current root. */
509 :
510 : void
511 : fd_stake_delegations_view_begin( fd_stake_delegations_t * stake_delegations,
512 : ulong epoch,
513 : fd_stake_history_t const * stake_history,
514 : ulong * warmup_cooldown_rate_epoch,
515 : ushort fork_idx );
516 :
517 : void
518 : fd_stake_delegations_view_end( fd_stake_delegations_t * stake_delegations,
519 : fd_stake_history_t const * stake_history,
520 : ulong * warmup_cooldown_rate_epoch );
521 :
522 : /* Iterator API for stake delegations. The iterator is initialized with
523 : a call to fd_stake_delegations_iter_init. The caller is responsible
524 : for managing the memory for the iterator. It is safe to call
525 : fd_stake_delegations_iter_next if the result of
526 : fd_stake_delegations_iter_done()==0. It is safe to call
527 : fd_stake_delegations_iter_ele() to get the current stake delegation
528 : or fd_stake_delegations_iter_idx() to get the index of the current
529 : stake delegation. It is not safe to modify the stake delegation
530 : while iterating through it.
531 :
532 : Under the hood, the iterator walks in-memory roots followed by dense
533 : disk roots, redirecting through tagged in-memory/disk delta references
534 : for entries a marked fork has changed. Disk roots are read in batches
535 : of up to 128 records (14 KiB per iterator). Disk records are returned
536 : through iterator-owned storage, so that pointer remains valid only
537 : until the next call to fd_stake_delegations_iter_next. Reinitialize
538 : the iterator after modifying the store or changing the marked forks.
539 :
540 : Example use:
541 :
542 : fd_stake_delegations_iter_t iter_[1];
543 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
544 : !fd_stake_delegations_iter_done( iter );
545 : fd_stake_delegations_iter_next( iter ) ) {
546 : fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
547 : }
548 : */
549 :
550 : fd_stake_delegations_iter_t *
551 : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t * iter,
552 : fd_stake_delegations_t const * stake_delegations );
553 :
554 : static inline fd_stake_delegation_t const *
555 53148 : fd_stake_delegations_iter_ele( fd_stake_delegations_iter_t const * iter ) {
556 53148 : return iter->ele;
557 53148 : }
558 :
559 : static inline ulong
560 5472 : fd_stake_delegations_iter_idx( fd_stake_delegations_iter_t * iter ) {
561 5472 : return iter->idx;
562 5472 : }
563 :
564 : static inline void
565 41256 : fd_stake_delegations_iter_next( fd_stake_delegations_iter_t * iter ) {
566 41256 : if( FD_LIKELY( iter->idx<iter->root_max ) ) {
567 37110 : iter->idx++;
568 37110 : fd_stake_delegations_iter_advance_private( iter );
569 37110 : } else {
570 4146 : iter->disk_idx++;
571 4146 : fd_stake_delegations_iter_advance_disk_root_private( iter );
572 4146 : }
573 41256 : }
574 :
575 : static inline int
576 73224 : fd_stake_delegations_iter_done( fd_stake_delegations_iter_t * iter ) {
577 73224 : return !iter->ele;
578 73224 : }
579 :
580 : FD_PROTOTYPES_END
581 :
582 : #endif /* HEADER_fd_src_flamenco_stakes_fd_stake_delegations_h */
|