Line data Source code
1 : #include "fd_stake_delegations.h"
2 : #include "../fd_rwlock.h"
3 : #include "fd_stakes.h"
4 : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
5 : #include "../events/fd_event_runtime.h"
6 : #include "../../util/fd_hash32.h"
7 :
8 : #include <errno.h>
9 : #include <unistd.h>
10 :
11 : struct fd_stake_delegations {
12 : ulong magic;
13 : ulong seed_;
14 : ulong max_stake_accounts_;
15 :
16 : /* Root map + pool */
17 : ulong map_offset_;
18 : ulong pool_offset_;
19 : ulong pool_idx_wmk_; /* One past the highest root pool index ever acquired. Every index in
20 : [0, wmk) has been acquired at least once, so its in_use byte is
21 : well defined. */
22 :
23 : /* Delta map + pool and fork pool */
24 : ulong delta_pool_offset_;
25 : ulong fork_pool_offset_;
26 : ulong delta_map_offset_;
27 :
28 : /* Guards every mutating operation on the struct. */
29 : fd_rwlock_t lock;
30 :
31 : /* File descriptor number for this instance's backing file.
32 : Every process joining this object must map the same file at this
33 : descriptor number for the object's lifetime. */
34 : int disk_fd_;
35 :
36 : /* Management for stake delegations that spill to disk. */
37 : ulong max_disk_records_; /* disk delta capacity; disk root capacity is derived */
38 : ulong disk_root_cnt_; /* number of disk-backed root records */
39 : ulong disk_delta_cnt_;
40 : ulong disk_bucket_cnt_; /* pow2 buckets in each disk hash index */
41 : ulong disk_root_tombstone_cnt_;
42 : ulong disk_delta_tombstone_cnt_;
43 : uint disk_root_gen_; /* buckets with an older generation are empty */
44 : uint disk_delta_gen_;
45 :
46 : /* Spill metrics. Counts only grow; disk_ticks_ is in fd_tickcount()
47 : units. */
48 : ulong root_spilled_cnt_;
49 : ulong delta_spilled_cnt_;
50 : ulong disk_read_cnt_;
51 : ulong disk_written_cnt_;
52 : ulong disk_ticks_;
53 :
54 : /* Stake totals for the current root. */
55 : ulong effective_stake;
56 : ulong activating_stake;
57 : ulong deactivating_stake;
58 :
59 : /* Epoch and target fork of the active frontier query. */
60 : ulong frontier_query_epoch;
61 : ushort frontier_query_fork;
62 : };
63 :
64 : /* map/pool pair to represent in-memory state */
65 : #define POOL_NAME root_pool
66 24 : #define POOL_T fd_stake_delegation_t
67 0 : #define POOL_NEXT next_
68 : #define POOL_IDX_T uint
69 : #define POOL_LAZY 1
70 : #include "../../util/tmpl/fd_pool.c"
71 :
72 : #define MAP_NAME root_map
73 : #define MAP_KEY_T fd_pubkey_t
74 : #define MAP_ELE_T fd_stake_delegation_t
75 1017 : #define MAP_KEY stake_account
76 273 : #define MAP_KEY_EQ(k0,k1) (fd_pubkey_eq( k0, k1 ))
77 2305 : #define MAP_KEY_HASH(key,seed) (fd_hash32( key->uc, seed ))
78 1018 : #define MAP_NEXT next_
79 4218 : #define MAP_IDX_T uint
80 : #include "../../util/tmpl/fd_map_chain.c"
81 :
82 : /* pool/map pair to represent in-memory delta state */
83 : #define POOL_NAME delta_pool
84 24 : #define POOL_T fd_stake_delegation_t
85 136 : #define POOL_NEXT next_
86 : #define POOL_IDX_T uint
87 : #define POOL_LAZY 1
88 : #include "../../util/tmpl/fd_pool.c"
89 :
90 : #define MAP_NAME delta_map
91 : #define MAP_KEY_T fd_stake_delegation_key_t
92 : #define MAP_ELE_T fd_stake_delegation_t
93 136 : #define MAP_KEY key
94 136 : #define MAP_KEY_EQ(k0,k1) ((k0)->fork_idx==(k1)->fork_idx && fd_pubkey_eq( &(k0)->stake_account, &(k1)->stake_account ))
95 408 : #define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->stake_account.uc, (seed) ^ (ulong)(key)->fork_idx ))
96 272 : #define MAP_NEXT next_
97 1167 : #define MAP_IDX_T uint
98 : #include "../../util/tmpl/fd_map_chain.c"
99 :
100 : /* Pool/map pair for fork ids. Just holds metadata. */
101 : struct fork_pool_ele {
102 : ushort next;
103 : ushort parent;
104 : uint delta_head;
105 : uint disk_delta_head;
106 : };
107 : typedef struct fork_pool_ele fork_pool_ele_t;
108 :
109 : FD_STATIC_ASSERT( sizeof(fork_pool_ele_t)==12UL, fork_pool_ele_size );
110 :
111 : /* A live parent is a fork index or USHORT_MAX for the rooted state. */
112 1869 : #define FORK_PARENT_FREE ((ushort)FD_STAKE_DELEGATIONS_FORK_MAX)
113 : FD_STATIC_ASSERT( FD_STAKE_DELEGATIONS_FORK_MAX<USHORT_MAX, fork_idx_width );
114 :
115 : #define POOL_NAME fork_pool
116 24 : #define POOL_T fork_pool_ele_t
117 : #define POOL_IDX_T ushort
118 : #include "../../util/tmpl/fd_pool.c"
119 :
120 : /* Internal getters for base map + pool */
121 :
122 : static inline fd_stake_delegation_t *
123 4333 : get_root_pool( fd_stake_delegations_t const * stake_delegations ) {
124 4333 : return fd_type_pun( (uchar *)stake_delegations + stake_delegations->pool_offset_ );
125 4333 : }
126 :
127 : static inline root_map_t *
128 2915 : get_root_map( fd_stake_delegations_t const * stake_delegations ) {
129 2915 : return fd_type_pun( (uchar *)stake_delegations + stake_delegations->map_offset_ );
130 2915 : }
131 :
132 : /* Internal getters for delta pool + fork structures */
133 :
134 : static inline fd_stake_delegation_t *
135 3660 : get_delta_pool( fd_stake_delegations_t const * stake_delegations ) {
136 3660 : return fd_type_pun( (uchar *)stake_delegations + stake_delegations->delta_pool_offset_ );
137 3660 : }
138 :
139 : static inline fork_pool_ele_t *
140 4701 : get_fork_pool( fd_stake_delegations_t const * stake_delegations ) {
141 4701 : return fd_type_pun( (uchar *)stake_delegations + stake_delegations->fork_pool_offset_ );
142 4701 : }
143 :
144 : static inline delta_map_t *
145 1494 : get_delta_map( fd_stake_delegations_t const * stake_delegations ) {
146 1494 : return fd_type_pun( (uchar *)stake_delegations + stake_delegations->delta_map_offset_ );
147 1494 : }
148 :
149 : /* Collect leaf to root; callers process the result in reverse order. */
150 : static ulong
151 : fork_ancestry( fd_stake_delegations_t const * stake_delegations,
152 : ushort fork_idx,
153 1360 : ushort * fork_ids ) {
154 1360 : fork_pool_ele_t const * fork_pool = get_fork_pool( stake_delegations );
155 1360 : ulong max_forks = fork_pool_max( fork_pool );
156 1360 : ulong cnt = 0UL;
157 2720 : for( ; fork_idx!=USHORT_MAX; fork_idx=fork_pool[ fork_idx ].parent ) {
158 1360 : FD_CHECK_CRIT( cnt<max_forks && (ulong)fork_idx<max_forks && fork_pool[ fork_idx ].parent!=FORK_PARENT_FREE,
159 1360 : "invalid stake delegation ancestry" );
160 1360 : fork_ids[ cnt++ ] = fork_idx;
161 1360 : }
162 1360 : return cnt;
163 1360 : }
164 :
165 : /* The spill file contains two compact open-addressed indexes followed
166 : by dense root and delta record arrays. Index generations make reset
167 : O(1). Root and delta indexes are separate so root mutation while
168 : applying a fork cannot disturb traversal of that fork's deltas. */
169 :
170 0 : #define DISK_BUCKET_TOMBSTONE (UINT_MAX-1U)
171 :
172 : struct disk_bucket {
173 : uint idx;
174 : uint gen;
175 : };
176 : typedef struct disk_bucket disk_bucket_t;
177 :
178 : struct disk_delta {
179 : fd_stake_delegation_t delegation;
180 : uint next;
181 : uint prev;
182 : ushort fork_idx;
183 : };
184 : typedef struct disk_delta disk_delta_t;
185 :
186 : FD_STATIC_ASSERT( sizeof(disk_bucket_t)==8UL, disk_bucket );
187 : FD_STATIC_ASSERT( sizeof(disk_delta_t)==128UL, disk_delta );
188 : FD_STATIC_ASSERT( offsetof( disk_delta_t, delegation )== 0UL, disk_delta );
189 : FD_STATIC_ASSERT( offsetof( disk_delta_t, next )==112UL, disk_delta );
190 : FD_STATIC_ASSERT( offsetof( disk_delta_t, prev )==116UL, disk_delta );
191 : FD_STATIC_ASSERT( offsetof( disk_delta_t, fork_idx )==120UL, disk_delta );
192 :
193 : static inline ulong
194 12 : disk_root_record_cap( fd_stake_delegations_t const * stake_delegations ) {
195 12 : return 2UL*stake_delegations->max_disk_records_ + stake_delegations->max_stake_accounts_;
196 12 : }
197 :
198 : static inline ulong
199 0 : disk_delta_bucket_off( fd_stake_delegations_t const * stake_delegations ) {
200 0 : return stake_delegations->disk_bucket_cnt_*sizeof(disk_bucket_t);
201 0 : }
202 :
203 : static inline ulong
204 0 : disk_root_record_off( fd_stake_delegations_t const * stake_delegations ) {
205 0 : return fd_ulong_align_up( 2UL*stake_delegations->disk_bucket_cnt_*sizeof(disk_bucket_t), alignof(fd_stake_delegation_t) );
206 0 : }
207 :
208 : static inline ulong
209 0 : disk_delta_record_off( fd_stake_delegations_t const * stake_delegations ) {
210 0 : return fd_ulong_align_up( disk_root_record_off( stake_delegations ) +
211 0 : disk_root_record_cap( stake_delegations )*sizeof(fd_stake_delegation_t),
212 0 : alignof(disk_delta_t) );
213 0 : }
214 :
215 : /* Iteration reads the spill file through a const join. */
216 :
217 : static inline void
218 : disk_metrics_add( fd_stake_delegations_t const * stake_delegations,
219 : int write,
220 0 : long t0 ) {
221 0 : fd_stake_delegations_t * metrics = (fd_stake_delegations_t *)stake_delegations;
222 0 : if( write ) metrics->disk_written_cnt_++;
223 0 : else metrics->disk_read_cnt_++;
224 0 : metrics->disk_ticks_ += (ulong)(fd_tickcount()-t0);
225 0 : }
226 :
227 : /* Reads short at EOF zero-fill. The spill file is sparse and is not
228 : required to be pre-sized. */
229 :
230 : static void
231 : disk_read( fd_stake_delegations_t const * stake_delegations,
232 : void * dst,
233 : ulong off,
234 0 : ulong sz ) {
235 0 : long t0 = fd_tickcount();
236 0 : ulong got = 0UL;
237 0 : while( got<sz ) {
238 0 : long n = pread( stake_delegations->disk_fd_, (uchar *)dst+got, sz-got, (long)(off+got) );
239 0 : if( FD_UNLIKELY( n<0L ) ) {
240 0 : if( FD_LIKELY( errno==EINTR ) ) continue;
241 0 : FD_LOG_CRIT(( "pread(stake delegation spill file, fd=%d) failed (%i-%s)", stake_delegations->disk_fd_, errno, fd_io_strerror( errno ) ));
242 0 : }
243 0 : if( FD_UNLIKELY( !n ) ) break;
244 0 : got += (ulong)n;
245 0 : }
246 0 : if( FD_UNLIKELY( got<sz ) ) fd_memset( (uchar *)dst+got, 0, sz-got );
247 0 : disk_metrics_add( stake_delegations, 0, t0 );
248 0 : }
249 :
250 : static void
251 : disk_write( fd_stake_delegations_t const * stake_delegations,
252 : void const * src,
253 : ulong off,
254 0 : ulong sz ) {
255 0 : long t0 = fd_tickcount();
256 0 : ulong put = 0UL;
257 0 : while( put<sz ) {
258 0 : long n = pwrite( stake_delegations->disk_fd_, (uchar const *)src+put, sz-put, (long)(off+put) );
259 0 : if( FD_LIKELY( n>0L ) ) { put += (ulong)n; continue; }
260 0 : if( FD_UNLIKELY( n<0L && errno==EINTR ) ) continue;
261 0 : if( FD_UNLIKELY( !n ) ) errno = EIO;
262 0 : FD_LOG_CRIT(( "pwrite(stake delegation spill file, fd=%d) failed (%i-%s)", stake_delegations->disk_fd_, errno, fd_io_strerror( errno ) ));
263 0 : }
264 0 : disk_metrics_add( stake_delegations, 1, t0 );
265 0 : }
266 :
267 : static inline int
268 : disk_bucket_is_empty( disk_bucket_t const * bucket,
269 0 : uint gen ) {
270 0 : return bucket->gen!=gen;
271 0 : }
272 :
273 : static inline int
274 : disk_bucket_is_tombstone( disk_bucket_t const * bucket,
275 0 : uint gen ) {
276 0 : return bucket->gen==gen && bucket->idx==DISK_BUCKET_TOMBSTONE;
277 0 : }
278 :
279 : static inline ulong
280 : disk_bucket_off( ulong base,
281 0 : ulong bucket_idx ) {
282 0 : return base + bucket_idx*sizeof(disk_bucket_t);
283 0 : }
284 :
285 : static void
286 : disk_bucket_store( fd_stake_delegations_t const * stake_delegations,
287 : ulong base,
288 : ulong bucket_idx,
289 : uint gen,
290 0 : uint idx ) {
291 0 : disk_bucket_t bucket = {
292 0 : .idx = idx,
293 0 : .gen = gen,
294 0 : };
295 0 : disk_write( stake_delegations, &bucket, disk_bucket_off( base, bucket_idx ), sizeof(disk_bucket_t) );
296 0 : }
297 :
298 : static void
299 : disk_root_read( fd_stake_delegations_t const * stake_delegations,
300 : uint idx,
301 0 : fd_stake_delegation_t * delegation ) {
302 0 : disk_read( stake_delegations, delegation,
303 0 : disk_root_record_off( stake_delegations ) + (ulong)idx*sizeof(fd_stake_delegation_t),
304 0 : sizeof(fd_stake_delegation_t) );
305 0 : }
306 :
307 : static void
308 : disk_root_write( fd_stake_delegations_t const * stake_delegations,
309 : uint idx,
310 0 : fd_stake_delegation_t const * delegation ) {
311 0 : disk_write( stake_delegations, delegation,
312 0 : disk_root_record_off( stake_delegations ) + (ulong)idx*sizeof(fd_stake_delegation_t),
313 0 : sizeof(fd_stake_delegation_t) );
314 0 : }
315 :
316 : static void
317 : disk_delta_read( fd_stake_delegations_t const * stake_delegations,
318 : uint idx,
319 0 : disk_delta_t * delta ) {
320 0 : disk_read( stake_delegations, delta,
321 0 : disk_delta_record_off( stake_delegations ) + (ulong)idx*sizeof(disk_delta_t),
322 0 : sizeof(disk_delta_t) );
323 0 : }
324 :
325 : static void
326 : disk_delta_write( fd_stake_delegations_t const * stake_delegations,
327 : uint idx,
328 0 : disk_delta_t const * delta ) {
329 0 : disk_write( stake_delegations, delta,
330 0 : disk_delta_record_off( stake_delegations ) + (ulong)idx*sizeof(disk_delta_t),
331 0 : sizeof(disk_delta_t) );
332 0 : }
333 :
334 : static void
335 : disk_root_find( fd_stake_delegations_t const * stake_delegations,
336 : fd_pubkey_t const * stake_account,
337 : fd_stake_delegation_t * delegation,
338 : uint * idx,
339 : ulong * found_bucket_idx,
340 0 : ulong * free_bucket_idx ) {
341 : /* Default to not found and no reusable bucket. */
342 0 : *idx = UINT_MAX;
343 0 : *found_bucket_idx = ULONG_MAX;
344 0 : *free_bucket_idx = ULONG_MAX;
345 0 : ulong bucket_cnt = stake_delegations->disk_bucket_cnt_;
346 :
347 : /* Hash the stake account to its initial bucket. */
348 0 : ulong bucket_idx = (ulong)fd_hash32( stake_account->uc, stake_delegations->seed_ ) & (bucket_cnt-1UL);
349 0 : ulong base = 0UL;
350 0 : for( ulong probe=0UL; probe<bucket_cnt; probe++ ) {
351 0 : disk_bucket_t bucket;
352 0 : disk_read( stake_delegations, &bucket, disk_bucket_off( base, bucket_idx ), sizeof(disk_bucket_t) );
353 :
354 : /* An empty bucket ends the search and can be reused for insertion. */
355 0 : if( FD_UNLIKELY( disk_bucket_is_empty( &bucket, stake_delegations->disk_root_gen_ ) ) ) {
356 0 : if( *free_bucket_idx==ULONG_MAX ) *free_bucket_idx = bucket_idx;
357 0 : return;
358 0 : }
359 :
360 : /* Remember the first tombstone, but keep searching past it. If the
361 : record doesn't exist already, the tombstone will be used. */
362 0 : if( FD_UNLIKELY( bucket.idx==DISK_BUCKET_TOMBSTONE ) ) {
363 0 : if( *free_bucket_idx==ULONG_MAX ) *free_bucket_idx = bucket_idx;
364 0 : } else {
365 : /* A live bucket points to a record in the dense disk array. */
366 0 : FD_CHECK_CRIT( (ulong)bucket.idx<stake_delegations->disk_root_cnt_, "corrupt stake delegation disk root index" );
367 0 : fd_stake_delegation_t candidate;
368 0 : disk_root_read( stake_delegations, bucket.idx, &candidate );
369 0 : if( FD_UNLIKELY( fd_pubkey_eq( &candidate.stake_account, stake_account ) ) ) {
370 : /* Return the record and both of its disk locations. */
371 0 : if( delegation ) *delegation = candidate;
372 0 : *idx = bucket.idx;
373 0 : *found_bucket_idx = bucket_idx;
374 0 : return;
375 0 : }
376 0 : }
377 :
378 : /* Resolve a collision by advancing, wrapping at the table end. */
379 0 : bucket_idx = (bucket_idx+1UL) & (bucket_cnt-1UL);
380 0 : }
381 0 : }
382 :
383 : struct disk_delta_query {
384 : uint record_idx; /* UINT_MAX if absent */
385 : ulong bucket_idx; /* Matching entry or first reusable slot */
386 : int is_tombstone; /* Reusable slot holds a deleted entry */
387 : };
388 : typedef struct disk_delta_query disk_delta_query_t;
389 :
390 : static void
391 : disk_delta_find( fd_stake_delegations_t const * stake_delegations,
392 : ushort fork_idx,
393 : fd_pubkey_t const * stake_account,
394 : disk_delta_t * delta,
395 0 : disk_delta_query_t * query ) {
396 0 : query->record_idx = UINT_MAX;
397 0 : query->bucket_idx = ULONG_MAX;
398 0 : query->is_tombstone = 0;
399 0 : ulong bucket_cnt = stake_delegations->disk_bucket_cnt_;
400 0 : ulong seed = stake_delegations->seed_ ^ (ulong)fork_idx;
401 0 : ulong bucket_idx = (ulong)fd_hash32( stake_account->uc, seed ) & (bucket_cnt-1UL);
402 0 : ulong base = disk_delta_bucket_off( stake_delegations );
403 0 : for( ulong probe=0UL; probe<bucket_cnt; probe++ ) {
404 0 : disk_bucket_t bucket;
405 0 : disk_read( stake_delegations, &bucket, disk_bucket_off( base, bucket_idx ), sizeof(disk_bucket_t) );
406 0 : if( FD_UNLIKELY( disk_bucket_is_empty( &bucket, stake_delegations->disk_delta_gen_ ) ) ) {
407 0 : if( query->bucket_idx==ULONG_MAX ) query->bucket_idx = bucket_idx;
408 0 : return;
409 0 : }
410 0 : if( FD_UNLIKELY( bucket.idx==DISK_BUCKET_TOMBSTONE ) ) {
411 0 : if( query->bucket_idx==ULONG_MAX ) {
412 0 : query->bucket_idx = bucket_idx;
413 0 : query->is_tombstone = 1;
414 0 : }
415 0 : } else {
416 0 : FD_CHECK_CRIT( (ulong)bucket.idx<stake_delegations->disk_delta_cnt_, "corrupt stake delegation disk delta index" );
417 0 : disk_delta_t candidate;
418 0 : disk_delta_read( stake_delegations, bucket.idx, &candidate );
419 0 : if( FD_UNLIKELY( candidate.fork_idx==fork_idx &&
420 0 : fd_pubkey_eq( &candidate.delegation.stake_account, stake_account ) ) ) {
421 0 : if( delta ) *delta = candidate;
422 0 : query->record_idx = bucket.idx;
423 0 : query->bucket_idx = bucket_idx;
424 0 : query->is_tombstone = 0;
425 0 : return;
426 0 : }
427 0 : }
428 0 : bucket_idx = (bucket_idx+1UL) & (bucket_cnt-1UL);
429 0 : }
430 0 : }
431 :
432 : static inline void
433 0 : disk_delta_record_reserve( fd_stake_delegations_t const * stake_delegations ) {
434 0 : FD_CHECK_CRIT( stake_delegations->disk_delta_cnt_<stake_delegations->max_disk_records_,
435 0 : "stake delegation disk delta spill exhausted" );
436 0 : }
437 :
438 : static inline uint
439 1222 : disk_gen_next( uint gen ) {
440 1222 : FD_CHECK_CRIT( gen<UINT_MAX, "stake delegation disk generation exhausted" );
441 1222 : return gen+1U;
442 1222 : }
443 :
444 : static inline int
445 : disk_index_needs_rebuild( ulong tombstone_cnt,
446 : ulong live_cnt,
447 0 : ulong bucket_cnt ) {
448 0 : return tombstone_cnt>live_cnt || tombstone_cnt>(bucket_cnt>>2);
449 0 : }
450 :
451 : static void
452 0 : disk_root_rebuild( fd_stake_delegations_t * stake_delegations ) {
453 0 : stake_delegations->disk_root_gen_ = disk_gen_next( stake_delegations->disk_root_gen_ );
454 0 : stake_delegations->disk_root_tombstone_cnt_ = 0UL;
455 :
456 0 : for( uint idx=0U; (ulong)idx<stake_delegations->disk_root_cnt_; idx++ ) {
457 0 : fd_stake_delegation_t delegation;
458 0 : disk_root_read( stake_delegations, idx, &delegation );
459 :
460 0 : uint found_idx;
461 0 : ulong found_bucket_idx;
462 0 : ulong free_bucket_idx;
463 0 : disk_root_find( stake_delegations, &delegation.stake_account, NULL, &found_idx, &found_bucket_idx, &free_bucket_idx );
464 0 : FD_CHECK_CRIT( found_idx==UINT_MAX && free_bucket_idx!=ULONG_MAX, "unable to rebuild stake delegation disk root index" );
465 0 : disk_bucket_store( stake_delegations, 0UL, free_bucket_idx,
466 0 : stake_delegations->disk_root_gen_, idx );
467 0 : }
468 0 : }
469 :
470 : static void
471 0 : disk_delta_rebuild( fd_stake_delegations_t * stake_delegations ) {
472 0 : stake_delegations->disk_delta_gen_ = disk_gen_next( stake_delegations->disk_delta_gen_ );
473 0 : stake_delegations->disk_delta_tombstone_cnt_ = 0UL;
474 :
475 0 : for( uint idx=0U; (ulong)idx<stake_delegations->disk_delta_cnt_; idx++ ) {
476 0 : disk_delta_t delta;
477 0 : disk_delta_read( stake_delegations, idx, &delta );
478 :
479 0 : disk_delta_query_t query;
480 0 : disk_delta_find( stake_delegations, delta.fork_idx, &delta.delegation.stake_account, NULL, &query );
481 0 : FD_CHECK_CRIT( query.record_idx==UINT_MAX && query.bucket_idx!=ULONG_MAX, "unable to rebuild stake delegation disk delta index" );
482 0 : disk_bucket_store( stake_delegations, disk_delta_bucket_off( stake_delegations ), query.bucket_idx,
483 0 : stake_delegations->disk_delta_gen_, idx );
484 0 : }
485 0 : }
486 :
487 : static void
488 680 : disk_root_maintain( fd_stake_delegations_t * stake_delegations ) {
489 680 : if( FD_UNLIKELY( !stake_delegations->disk_root_cnt_ ) ) {
490 680 : if( FD_UNLIKELY( stake_delegations->disk_root_tombstone_cnt_ ) ) {
491 0 : stake_delegations->disk_root_gen_ = disk_gen_next( stake_delegations->disk_root_gen_ );
492 0 : stake_delegations->disk_root_tombstone_cnt_ = 0UL;
493 0 : }
494 680 : return;
495 680 : }
496 0 : if( FD_UNLIKELY( stake_delegations->frontier_query_epoch==ULONG_MAX &&
497 0 : disk_index_needs_rebuild( stake_delegations->disk_root_tombstone_cnt_,
498 0 : stake_delegations->disk_root_cnt_,
499 0 : stake_delegations->disk_bucket_cnt_ ) ) ) {
500 0 : disk_root_rebuild( stake_delegations );
501 0 : }
502 0 : }
503 :
504 : static void
505 0 : disk_delta_maintain( fd_stake_delegations_t * stake_delegations ) {
506 0 : if( FD_UNLIKELY( !stake_delegations->disk_delta_cnt_ ) ) {
507 0 : if( FD_UNLIKELY( stake_delegations->disk_delta_tombstone_cnt_ ) ) {
508 0 : stake_delegations->disk_delta_gen_ = disk_gen_next( stake_delegations->disk_delta_gen_ );
509 0 : stake_delegations->disk_delta_tombstone_cnt_ = 0UL;
510 0 : }
511 0 : return;
512 0 : }
513 0 : if( FD_UNLIKELY( disk_index_needs_rebuild( stake_delegations->disk_delta_tombstone_cnt_,
514 0 : stake_delegations->disk_delta_cnt_,
515 0 : stake_delegations->disk_bucket_cnt_ ) ) ) {
516 0 : disk_delta_rebuild( stake_delegations );
517 0 : }
518 0 : }
519 :
520 : static void
521 : disk_root_insert( fd_stake_delegations_t * stake_delegations,
522 0 : fd_stake_delegation_t const * delegation ) {
523 0 : FD_CHECK_CRIT( stake_delegations->disk_root_cnt_<disk_root_record_cap( stake_delegations ),
524 0 : "stake delegation disk root spill exhausted" );
525 :
526 0 : uint idx;
527 0 : ulong found_bucket_idx;
528 0 : ulong free_bucket_idx;
529 0 : disk_root_find( stake_delegations, &delegation->stake_account, NULL, &idx, &found_bucket_idx, &free_bucket_idx );
530 0 : FD_CHECK_CRIT( idx==UINT_MAX, "duplicate stake delegation disk root" );
531 0 : FD_CHECK_CRIT( free_bucket_idx!=ULONG_MAX, "stake delegation disk root index exhausted" );
532 :
533 0 : idx = (uint)stake_delegations->disk_root_cnt_;
534 0 : disk_root_write( stake_delegations, idx, delegation );
535 0 : disk_bucket_t free_bucket;
536 0 : disk_read( stake_delegations, &free_bucket, disk_bucket_off( 0UL, free_bucket_idx ), sizeof(disk_bucket_t) );
537 0 : if( FD_UNLIKELY( disk_bucket_is_tombstone( &free_bucket, stake_delegations->disk_root_gen_ ) ) ) {
538 0 : FD_CHECK_CRIT( stake_delegations->disk_root_tombstone_cnt_, "corrupt stake delegation disk root tombstone count" );
539 0 : stake_delegations->disk_root_tombstone_cnt_--;
540 0 : }
541 0 : disk_bucket_store( stake_delegations, 0UL, free_bucket_idx,
542 0 : stake_delegations->disk_root_gen_, idx );
543 0 : stake_delegations->disk_root_cnt_++;
544 0 : stake_delegations->root_spilled_cnt_++;
545 0 : }
546 :
547 : static void
548 : disk_root_remove( fd_stake_delegations_t * stake_delegations,
549 : uint idx,
550 0 : ulong bucket_idx ) {
551 0 : FD_CHECK_CRIT( (ulong)idx<stake_delegations->disk_root_cnt_, "invalid stake delegation disk root removal" );
552 0 : disk_bucket_store( stake_delegations, 0UL, bucket_idx,
553 0 : stake_delegations->disk_root_gen_, DISK_BUCKET_TOMBSTONE );
554 :
555 0 : uint last = (uint)(stake_delegations->disk_root_cnt_-1UL);
556 0 : if( FD_UNLIKELY( idx!=last ) ) {
557 0 : fd_stake_delegation_t moved;
558 0 : disk_root_read( stake_delegations, last, &moved );
559 0 : disk_root_write( stake_delegations, idx, &moved );
560 :
561 0 : uint moved_idx;
562 0 : ulong moved_bucket_idx;
563 0 : ulong free_bucket_idx;
564 0 : disk_root_find( stake_delegations, &moved.stake_account, NULL, &moved_idx, &moved_bucket_idx, &free_bucket_idx );
565 0 : FD_CHECK_CRIT( moved_idx==last, "missing moved stake delegation disk root" );
566 0 : disk_bucket_store( stake_delegations, 0UL, moved_bucket_idx,
567 0 : stake_delegations->disk_root_gen_, idx );
568 0 : }
569 0 : stake_delegations->disk_root_cnt_--;
570 0 : stake_delegations->disk_root_tombstone_cnt_++;
571 0 : disk_root_maintain( stake_delegations );
572 0 : }
573 :
574 : static void
575 : disk_delta_insert( fd_stake_delegations_t * stake_delegations,
576 : ushort fork_idx,
577 : fd_stake_delegation_t const * delegation,
578 0 : disk_delta_query_t const * query ) {
579 0 : disk_delta_record_reserve( stake_delegations );
580 :
581 0 : FD_CHECK_CRIT( query->bucket_idx!=ULONG_MAX, "stake delegation disk delta index exhausted" );
582 :
583 0 : uint idx = (uint)stake_delegations->disk_delta_cnt_;
584 0 : fork_pool_ele_t * fork = get_fork_pool( stake_delegations ) + fork_idx;
585 0 : disk_delta_t delta = {
586 0 : .delegation = *delegation,
587 0 : .next = fork->disk_delta_head,
588 0 : .prev = UINT_MAX,
589 0 : .fork_idx = fork_idx,
590 0 : };
591 0 : disk_delta_write( stake_delegations, idx, &delta );
592 0 : if( FD_LIKELY( delta.next!=UINT_MAX ) ) {
593 0 : disk_write( stake_delegations, &idx,
594 0 : disk_delta_record_off( stake_delegations ) + (ulong)delta.next*sizeof(disk_delta_t) + offsetof(disk_delta_t, prev),
595 0 : sizeof(uint) );
596 0 : }
597 0 : if( FD_UNLIKELY( query->is_tombstone ) ) {
598 0 : FD_CHECK_CRIT( stake_delegations->disk_delta_tombstone_cnt_, "corrupt stake delegation disk delta tombstone count" );
599 0 : stake_delegations->disk_delta_tombstone_cnt_--;
600 0 : }
601 0 : disk_bucket_store( stake_delegations, disk_delta_bucket_off( stake_delegations ), query->bucket_idx,
602 0 : stake_delegations->disk_delta_gen_, idx );
603 0 : fork->disk_delta_head = idx;
604 0 : stake_delegations->disk_delta_cnt_++;
605 0 : stake_delegations->delta_spilled_cnt_++;
606 0 : }
607 :
608 : static void
609 : disk_delta_remove( fd_stake_delegations_t * stake_delegations,
610 : uint idx,
611 0 : disk_delta_t const * removed ) {
612 0 : FD_CHECK_CRIT( (ulong)idx<stake_delegations->disk_delta_cnt_, "invalid stake delegation disk delta removal" );
613 :
614 0 : fork_pool_ele_t * fork = get_fork_pool( stake_delegations ) + removed->fork_idx;
615 0 : if( FD_LIKELY( removed->prev!=UINT_MAX ) ) {
616 0 : disk_write( stake_delegations, &removed->next,
617 0 : disk_delta_record_off( stake_delegations ) + (ulong)removed->prev*sizeof(disk_delta_t) + offsetof(disk_delta_t, next),
618 0 : sizeof(uint) );
619 0 : } else {
620 0 : fork->disk_delta_head = removed->next;
621 0 : }
622 0 : if( FD_LIKELY( removed->next!=UINT_MAX ) ) {
623 0 : disk_write( stake_delegations, &removed->prev,
624 0 : disk_delta_record_off( stake_delegations ) + (ulong)removed->next*sizeof(disk_delta_t) + offsetof(disk_delta_t, prev),
625 0 : sizeof(uint) );
626 0 : }
627 :
628 0 : disk_delta_query_t query;
629 0 : disk_delta_find( stake_delegations, removed->fork_idx, &removed->delegation.stake_account, NULL, &query );
630 0 : FD_CHECK_CRIT( query.record_idx==idx, "missing stake delegation disk delta removal" );
631 0 : disk_bucket_store( stake_delegations, disk_delta_bucket_off( stake_delegations ), query.bucket_idx,
632 0 : stake_delegations->disk_delta_gen_, DISK_BUCKET_TOMBSTONE );
633 :
634 0 : uint last = (uint)(stake_delegations->disk_delta_cnt_-1UL);
635 0 : if( FD_UNLIKELY( idx!=last ) ) {
636 0 : disk_delta_t moved;
637 0 : disk_delta_read( stake_delegations, last, &moved );
638 0 : disk_delta_write( stake_delegations, idx, &moved );
639 :
640 0 : disk_delta_find( stake_delegations, moved.fork_idx, &moved.delegation.stake_account, NULL, &query );
641 0 : FD_CHECK_CRIT( query.record_idx==last, "missing moved stake delegation disk delta" );
642 0 : disk_bucket_store( stake_delegations, disk_delta_bucket_off( stake_delegations ), query.bucket_idx,
643 0 : stake_delegations->disk_delta_gen_, idx );
644 :
645 0 : fork_pool_ele_t * moved_fork = get_fork_pool( stake_delegations ) + moved.fork_idx;
646 0 : if( FD_LIKELY( moved.prev!=UINT_MAX ) ) {
647 0 : disk_write( stake_delegations, &idx,
648 0 : disk_delta_record_off( stake_delegations ) + (ulong)moved.prev*sizeof(disk_delta_t) + offsetof(disk_delta_t, next),
649 0 : sizeof(uint) );
650 0 : } else {
651 0 : moved_fork->disk_delta_head = idx;
652 0 : }
653 0 : if( FD_LIKELY( moved.next!=UINT_MAX ) ) {
654 0 : disk_write( stake_delegations, &idx,
655 0 : disk_delta_record_off( stake_delegations ) + (ulong)moved.next*sizeof(disk_delta_t) + offsetof(disk_delta_t, prev),
656 0 : sizeof(uint) );
657 0 : }
658 0 : }
659 0 : stake_delegations->disk_delta_cnt_--;
660 0 : stake_delegations->disk_delta_tombstone_cnt_++;
661 0 : disk_delta_maintain( stake_delegations );
662 0 : }
663 :
664 : ulong
665 384 : fd_stake_delegations_align( void ) {
666 384 : return FD_STAKE_DELEGATIONS_ALIGN;
667 384 : }
668 :
669 : ulong
670 : fd_stake_delegations_footprint( ulong max_stake_accounts,
671 60 : ulong max_live_slots ) {
672 60 : ulong map_chain_cnt = root_map_chain_cnt_est( max_stake_accounts );
673 60 : ulong delta_map_chain_cnt = delta_map_chain_cnt_est( max_stake_accounts );
674 :
675 60 : ulong l = FD_LAYOUT_INIT;
676 60 : l = FD_LAYOUT_APPEND( l, fd_stake_delegations_align(), sizeof(fd_stake_delegations_t) );
677 60 : l = FD_LAYOUT_APPEND( l, root_pool_align(), root_pool_footprint( max_stake_accounts ) );
678 60 : l = FD_LAYOUT_APPEND( l, root_map_align(), root_map_footprint( map_chain_cnt ) );
679 60 : l = FD_LAYOUT_APPEND( l, delta_pool_align(), delta_pool_footprint( max_stake_accounts ) );
680 60 : l = FD_LAYOUT_APPEND( l, fork_pool_align(), fork_pool_footprint( max_live_slots ) );
681 60 : l = FD_LAYOUT_APPEND( l, delta_map_align(), delta_map_footprint( delta_map_chain_cnt ) );
682 :
683 60 : return FD_LAYOUT_FINI( l, fd_stake_delegations_align() );
684 60 : }
685 :
686 : void *
687 : fd_stake_delegations_new( void * mem,
688 : int disk_fd,
689 : ulong seed,
690 : ulong max_stake_accounts,
691 : ulong max_disk_records,
692 12 : ulong max_live_slots ) {
693 12 : if( FD_UNLIKELY( !mem ) ) {
694 0 : FD_LOG_WARNING(( "NULL mem" ));
695 0 : return NULL;
696 0 : }
697 :
698 12 : if( FD_UNLIKELY( disk_fd<0 ) ) {
699 0 : FD_LOG_WARNING(( "invalid disk fd" ));
700 0 : return NULL;
701 0 : }
702 :
703 12 : if( FD_UNLIKELY( !max_stake_accounts ) ) {
704 0 : FD_LOG_WARNING(( "max_stake_accounts is 0" ));
705 0 : return NULL;
706 0 : }
707 :
708 12 : if( FD_UNLIKELY( max_stake_accounts>=(ulong)FD_STAKE_DELEGATIONS_DELTA_DISK_TAG ) ) {
709 0 : FD_LOG_WARNING(( "max_stake_accounts is too large" ));
710 0 : return NULL;
711 0 : }
712 :
713 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_stake_delegations_align() ) ) ) {
714 0 : FD_LOG_WARNING(( "misaligned mem" ));
715 0 : return NULL;
716 0 : }
717 :
718 12 : if( FD_UNLIKELY( max_live_slots>FD_STAKE_DELEGATIONS_FORK_MAX ) ) {
719 0 : FD_LOG_WARNING(( "max_live_slots is too large" ));
720 0 : return NULL;
721 0 : }
722 :
723 12 : if( FD_UNLIKELY( max_disk_records>=(ulong)FD_STAKE_DELEGATIONS_DELTA_DISK_TAG ) ) {
724 0 : FD_LOG_WARNING(( "max_disk_records is too large" ));
725 0 : return NULL;
726 0 : }
727 :
728 12 : if( FD_UNLIKELY( max_stake_accounts>(ulong)FD_STAKE_DELEGATIONS_DELTA_IDX_MASK ||
729 12 : max_disk_records>
730 12 : ((ulong)FD_STAKE_DELEGATIONS_DELTA_IDX_MASK-max_stake_accounts)/2UL ) ) {
731 0 : FD_LOG_WARNING(( "combined stake delegation disk root capacity is too large" ));
732 0 : return NULL;
733 0 : }
734 :
735 12 : ulong map_chain_cnt = root_map_chain_cnt_est( max_stake_accounts );
736 12 : ulong delta_map_chain_cnt = delta_map_chain_cnt_est( max_stake_accounts );
737 :
738 12 : FD_SCRATCH_ALLOC_INIT( l, mem );
739 12 : fd_stake_delegations_t * stake_delegations = FD_SCRATCH_ALLOC_APPEND( l, fd_stake_delegations_align(), sizeof(fd_stake_delegations_t) );
740 12 : void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, root_pool_align(), root_pool_footprint( max_stake_accounts ) );
741 12 : void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, root_map_align(), root_map_footprint( map_chain_cnt ) );
742 12 : void * delta_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, delta_pool_align(), delta_pool_footprint( max_stake_accounts ) );
743 12 : void * fork_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fork_pool_align(), fork_pool_footprint( max_live_slots ) );
744 12 : void * delta_map_mem = FD_SCRATCH_ALLOC_APPEND( l, delta_map_align(), delta_map_footprint( delta_map_chain_cnt ) );
745 :
746 12 : if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_stake_delegations_align() )!=(ulong)mem+fd_stake_delegations_footprint( max_stake_accounts, max_live_slots ) ) ) {
747 0 : FD_LOG_WARNING(( "fd_stake_delegations_new: bad layout" ));
748 0 : return NULL;
749 0 : }
750 :
751 12 : fd_stake_delegation_t * root_pool = root_pool_join( root_pool_new( pool_mem, max_stake_accounts ) );
752 12 : if( FD_UNLIKELY( !root_pool ) ) {
753 0 : FD_LOG_WARNING(( "Failed to create stake delegations pool" ));
754 0 : return NULL;
755 0 : }
756 :
757 12 : root_map_t * root_map = root_map_join( root_map_new( map_mem, map_chain_cnt, seed ) );
758 12 : if( FD_UNLIKELY( !root_map ) ) {
759 0 : FD_LOG_WARNING(( "Failed to create stake delegations map" ));
760 0 : return NULL;
761 0 : }
762 :
763 12 : fd_stake_delegation_t * delta_pool = delta_pool_join( delta_pool_new( delta_pool_mem, max_stake_accounts ) );
764 12 : if( FD_UNLIKELY( !delta_pool ) ) {
765 0 : FD_LOG_WARNING(( "Failed to create stake delegation delta pool" ));
766 0 : return NULL;
767 0 : }
768 :
769 12 : fork_pool_ele_t * fork_pool = fork_pool_join( fork_pool_new( fork_pool_mem, max_live_slots ) );
770 12 : if( FD_UNLIKELY( !fork_pool ) ) {
771 0 : FD_LOG_WARNING(( "Failed to create fork pool" ));
772 0 : return NULL;
773 0 : }
774 :
775 36 : for( ulong i=0UL; i<max_live_slots; i++ ) fork_pool[ i ].parent = FORK_PARENT_FREE;
776 :
777 12 : delta_map_t * delta_map = delta_map_join( delta_map_new( delta_map_mem, delta_map_chain_cnt, seed ) );
778 12 : if( FD_UNLIKELY( !delta_map ) ) {
779 0 : FD_LOG_WARNING(( "Failed to create delta map" ));
780 0 : return NULL;
781 0 : }
782 :
783 12 : stake_delegations->seed_ = seed;
784 12 : stake_delegations->max_stake_accounts_ = max_stake_accounts;
785 12 : stake_delegations->pool_offset_ = (ulong)root_pool - (ulong)mem;
786 12 : stake_delegations->map_offset_ = (ulong)root_map - (ulong)mem;
787 12 : stake_delegations->delta_pool_offset_ = (ulong)delta_pool - (ulong)mem;
788 12 : stake_delegations->fork_pool_offset_ = (ulong)fork_pool - (ulong)mem;
789 12 : stake_delegations->delta_map_offset_ = (ulong)delta_map - (ulong)mem;
790 12 : stake_delegations->disk_fd_ = disk_fd;
791 12 : stake_delegations->max_disk_records_ = max_disk_records;
792 12 : stake_delegations->disk_root_cnt_ = 0UL;
793 12 : stake_delegations->disk_delta_cnt_ = 0UL;
794 12 : ulong disk_root_cap = disk_root_record_cap( stake_delegations );
795 12 : stake_delegations->disk_bucket_cnt_ = fd_ulong_pow2_up( disk_root_cap + (disk_root_cap>>1) + 1UL );
796 12 : stake_delegations->disk_root_tombstone_cnt_ = 0UL;
797 12 : stake_delegations->disk_delta_tombstone_cnt_ = 0UL;
798 12 : stake_delegations->disk_root_gen_ = 1U;
799 12 : stake_delegations->disk_delta_gen_ = 1U;
800 12 : stake_delegations->root_spilled_cnt_ = 0UL;
801 12 : stake_delegations->delta_spilled_cnt_ = 0UL;
802 12 : stake_delegations->disk_read_cnt_ = 0UL;
803 12 : stake_delegations->disk_written_cnt_ = 0UL;
804 12 : stake_delegations->disk_ticks_ = 0UL;
805 :
806 12 : stake_delegations->effective_stake = 0UL;
807 12 : stake_delegations->activating_stake = 0UL;
808 12 : stake_delegations->deactivating_stake = 0UL;
809 12 : stake_delegations->frontier_query_epoch = ULONG_MAX;
810 12 : stake_delegations->frontier_query_fork = USHORT_MAX;
811 12 : stake_delegations->pool_idx_wmk_ = 0UL;
812 :
813 12 : fd_rwlock_new( &stake_delegations->lock );
814 :
815 12 : FD_COMPILER_MFENCE();
816 12 : FD_VOLATILE( stake_delegations->magic ) = FD_STAKE_DELEGATIONS_MAGIC;
817 12 : FD_COMPILER_MFENCE();
818 :
819 12 : return mem;
820 12 : }
821 :
822 : fd_stake_delegations_t *
823 : fd_stake_delegations_join( void * mem,
824 24 : int disk_fd ) {
825 24 : if( FD_UNLIKELY( !mem ) ) {
826 0 : FD_LOG_WARNING(( "NULL mem" ));
827 0 : return NULL;
828 0 : }
829 :
830 24 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_stake_delegations_align() ) ) ) {
831 0 : FD_LOG_WARNING(( "misaligned mem" ));
832 0 : return NULL;
833 0 : }
834 :
835 24 : fd_stake_delegations_t * stake_delegations = (fd_stake_delegations_t *)mem;
836 :
837 24 : if( FD_UNLIKELY( stake_delegations->magic!=FD_STAKE_DELEGATIONS_MAGIC ) ) {
838 0 : FD_LOG_WARNING(( "Invalid stake delegations magic" ));
839 0 : return NULL;
840 0 : }
841 :
842 24 : if( FD_UNLIKELY( stake_delegations->disk_fd_!=disk_fd ) ) {
843 0 : FD_LOG_WARNING(( "stake delegations disk fd mismatch (expected %d, got %d)",
844 0 : stake_delegations->disk_fd_, disk_fd ));
845 0 : return NULL;
846 0 : }
847 :
848 24 : return stake_delegations;
849 24 : }
850 :
851 : void
852 611 : fd_stake_delegations_reset( fd_stake_delegations_t * stake_delegations ) {
853 611 : fd_rwlock_write( &stake_delegations->lock );
854 611 : root_pool_reset( get_root_pool( stake_delegations ) );
855 611 : root_map_reset( get_root_map( stake_delegations ) );
856 611 : delta_pool_reset( get_delta_pool( stake_delegations ) );
857 611 : delta_map_reset( get_delta_map( stake_delegations ) );
858 611 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
859 611 : ulong max_forks = fork_pool_max( fork_pool );
860 1833 : for( ulong i=0UL; i<max_forks; i++ ) {
861 1222 : fork_pool[ i ].parent = FORK_PARENT_FREE;
862 1222 : fork_pool[ i ].delta_head = UINT_MAX;
863 1222 : fork_pool[ i ].disk_delta_head = UINT_MAX;
864 1222 : }
865 611 : fork_pool_reset( fork_pool );
866 611 : stake_delegations->disk_root_cnt_ = 0UL;
867 611 : stake_delegations->disk_delta_cnt_ = 0UL;
868 611 : stake_delegations->disk_root_tombstone_cnt_ = 0UL;
869 611 : stake_delegations->disk_delta_tombstone_cnt_ = 0UL;
870 611 : stake_delegations->disk_root_gen_ = disk_gen_next( stake_delegations->disk_root_gen_ );
871 611 : stake_delegations->disk_delta_gen_ = disk_gen_next( stake_delegations->disk_delta_gen_ );
872 611 : stake_delegations->effective_stake = 0UL;
873 611 : stake_delegations->activating_stake = 0UL;
874 611 : stake_delegations->deactivating_stake = 0UL;
875 611 : stake_delegations->frontier_query_epoch = ULONG_MAX;
876 611 : stake_delegations->frontier_query_fork = USHORT_MAX;
877 611 : stake_delegations->pool_idx_wmk_ = 0UL;
878 611 : fd_rwlock_unwrite( &stake_delegations->lock );
879 611 : }
880 :
881 : struct root_query {
882 : fd_stake_delegation_t * ele;
883 : ulong disk_bucket_idx;
884 : uint disk_idx;
885 : int is_disk;
886 : fd_stake_delegation_t disk_ele;
887 : };
888 : typedef struct root_query root_query_t;
889 :
890 : static int
891 : root_query( fd_stake_delegations_t * stake_delegations,
892 : fd_pubkey_t const * stake_account,
893 1289 : root_query_t * query ) {
894 1289 : query->ele = NULL;
895 1289 : query->disk_bucket_idx = ULONG_MAX;
896 1289 : query->disk_idx = UINT_MAX;
897 1289 : query->is_disk = 0;
898 :
899 1289 : fd_stake_delegation_t * pool = get_root_pool( stake_delegations );
900 1289 : root_map_t * map = get_root_map( stake_delegations );
901 :
902 1289 : fd_stake_delegation_t * in_memory = root_map_ele_query( map, stake_account, NULL, pool );
903 1289 : if( FD_LIKELY( in_memory ) ) {
904 272 : query->ele = in_memory;
905 272 : return 1;
906 272 : }
907 :
908 1017 : if( FD_LIKELY( !stake_delegations->disk_root_cnt_ ) ) return 0;
909 1 : ulong free_bucket_idx;
910 1 : disk_root_find( stake_delegations, stake_account, &query->disk_ele,
911 1 : &query->disk_idx, &query->disk_bucket_idx, &free_bucket_idx );
912 1 : if( FD_LIKELY( query->disk_idx==UINT_MAX ) ) return 0;
913 1 : query->ele = &query->disk_ele;
914 1 : query->is_disk = 1;
915 1 : return 1;
916 1 : }
917 :
918 : static void
919 : root_insert( fd_stake_delegations_t * stake_delegations,
920 1017 : fd_stake_delegation_t const * delegation ) {
921 1017 : fd_stake_delegation_t * pool = get_root_pool( stake_delegations );
922 1017 : if( FD_LIKELY( root_pool_free( pool ) ) ) {
923 1017 : fd_stake_delegation_t * in_memory = root_pool_ele_acquire( pool );
924 1017 : *in_memory = *delegation;
925 1017 : stake_delegations->pool_idx_wmk_ = fd_ulong_max( stake_delegations->pool_idx_wmk_, root_pool_idx( pool, in_memory )+1UL );
926 1017 : FD_CHECK_CRIT( root_map_ele_insert( get_root_map( stake_delegations ), in_memory, pool ),
927 1017 : "unable to insert stake delegation into root map" );
928 1017 : return;
929 1017 : }
930 :
931 0 : disk_root_insert( stake_delegations, delegation );
932 0 : }
933 :
934 : #if FD_HAS_DOUBLE
935 :
936 : static void
937 : disk_root_promote( fd_stake_delegations_t * stake_delegations,
938 : uint disk_idx,
939 0 : fd_stake_delegation_t const * delegation ) {
940 0 : FD_CHECK_CRIT( root_pool_free( get_root_pool( stake_delegations ) ),
941 0 : "no in-memory slot for promoted stake delegation root" );
942 0 : root_insert( stake_delegations, delegation );
943 :
944 0 : uint found_idx;
945 0 : ulong found_bucket_idx;
946 0 : ulong free_bucket_idx;
947 0 : disk_root_find( stake_delegations, &delegation->stake_account, NULL,
948 0 : &found_idx, &found_bucket_idx, &free_bucket_idx );
949 0 : FD_CHECK_CRIT( found_idx==disk_idx, "missing promoted stake delegation disk root" );
950 0 : disk_root_remove( stake_delegations, disk_idx, found_bucket_idx );
951 0 : }
952 :
953 : #endif
954 :
955 : static void
956 : root_store( fd_stake_delegations_t * stake_delegations,
957 : root_query_t const * query,
958 272 : fd_stake_delegation_t const * delegation ) {
959 272 : if( FD_LIKELY( !query->is_disk ) ) {
960 272 : uint next = query->ele->next_;
961 272 : *query->ele = *delegation;
962 272 : query->ele->next_ = next;
963 272 : } else {
964 0 : disk_root_write( stake_delegations, query->disk_idx, delegation );
965 0 : }
966 272 : }
967 :
968 : static void
969 : root_remove( fd_stake_delegations_t * stake_delegations,
970 0 : root_query_t const * query ) {
971 0 : if( FD_LIKELY( !query->is_disk ) ) {
972 0 : fd_stake_delegation_t * pool = get_root_pool( stake_delegations );
973 0 : uint idx = (uint)root_pool_idx( pool, query->ele );
974 0 : fd_pubkey_t stake_account = query->ele->stake_account;
975 0 : root_map_idx_remove( get_root_map( stake_delegations ), &stake_account, idx, pool );
976 0 : query->ele->in_use = 0;
977 0 : root_pool_idx_release( pool, idx );
978 0 : } else {
979 0 : disk_root_remove( stake_delegations, query->disk_idx, query->disk_bucket_idx );
980 0 : }
981 0 : }
982 :
983 : /* Unlocked root upsert used by public updates and fork rooting. */
984 :
985 : static void
986 : root_upsert( fd_stake_delegations_t * stake_delegations,
987 : root_query_t const * query,
988 1017 : fd_stake_delegation_t * delegation ) {
989 1017 : delegation->delta_idx = UINT_MAX;
990 1017 : delegation->in_use = 1;
991 :
992 1017 : if( FD_LIKELY( query->ele ) ) {
993 0 : delegation->dne_in_root = query->ele->dne_in_root;
994 0 : root_store( stake_delegations, query, delegation );
995 0 : return;
996 0 : }
997 :
998 1017 : delegation->dne_in_root = 0;
999 1017 : root_insert( stake_delegations, delegation );
1000 1017 : }
1001 :
1002 : fd_stake_history_entry_t
1003 28 : fd_stake_delegations_totals( fd_stake_delegations_t const * stake_delegations ) {
1004 28 : return (fd_stake_history_entry_t) {
1005 28 : .effective = stake_delegations->effective_stake,
1006 28 : .activating = stake_delegations->activating_stake,
1007 28 : .deactivating = stake_delegations->deactivating_stake,
1008 28 : };
1009 28 : }
1010 :
1011 : void
1012 : fd_stake_delegations_metrics_snap( fd_stake_delegations_t const * stake_delegations,
1013 0 : fd_stake_delegations_metrics_t * out ) {
1014 0 : fd_stake_delegation_t const * root_pool = get_root_pool ( stake_delegations );
1015 0 : fd_stake_delegation_t const * delta_pool = get_delta_pool( stake_delegations );
1016 0 : out->root_used = root_pool_used ( root_pool );
1017 0 : out->root_capacity = root_pool_max ( root_pool );
1018 0 : out->root_disk_cnt = FD_VOLATILE_CONST( stake_delegations->disk_root_cnt_ );
1019 0 : out->delta_used = delta_pool_used( delta_pool );
1020 0 : out->delta_capacity = delta_pool_max ( delta_pool );
1021 0 : out->delta_disk_cnt = FD_VOLATILE_CONST( stake_delegations->disk_delta_cnt_ );
1022 0 : out->root_spilled_cnt = FD_VOLATILE_CONST( stake_delegations->root_spilled_cnt_ );
1023 0 : out->delta_spilled_cnt = FD_VOLATILE_CONST( stake_delegations->delta_spilled_cnt_ );
1024 0 : out->disk_read_cnt = FD_VOLATILE_CONST( stake_delegations->disk_read_cnt_ );
1025 0 : out->disk_written_cnt = FD_VOLATILE_CONST( stake_delegations->disk_written_cnt_ );
1026 0 : out->disk_ticks = FD_VOLATILE_CONST( stake_delegations->disk_ticks_ );
1027 0 : }
1028 :
1029 : void
1030 : fd_stake_delegations_set_totals( fd_stake_delegations_t * stake_delegations,
1031 : ulong effective,
1032 : ulong activating,
1033 0 : ulong deactivating ) {
1034 0 : fd_rwlock_write( &stake_delegations->lock );
1035 0 : stake_delegations->effective_stake = effective;
1036 0 : stake_delegations->activating_stake = activating;
1037 0 : stake_delegations->deactivating_stake = deactivating;
1038 0 : fd_rwlock_unwrite( &stake_delegations->lock );
1039 0 : }
1040 :
1041 : #if FD_HAS_DOUBLE
1042 :
1043 : ulong
1044 : fd_stake_delegations_prune_inactive_root( fd_stake_delegations_t * stake_delegations,
1045 : ulong epoch,
1046 : fd_stake_history_t const * stake_history,
1047 : ulong * warmup_cooldown_rate_epoch,
1048 0 : fd_bank_t const * emit_bank ) {
1049 0 : fd_rwlock_write( &stake_delegations->lock );
1050 :
1051 0 : root_map_t * map = get_root_map( stake_delegations );
1052 0 : fd_stake_delegation_t * pool = get_root_pool( stake_delegations );
1053 0 : ulong prev_epoch = epoch ? epoch-1UL : 0UL;
1054 0 : ulong pruned = 0UL;
1055 :
1056 0 : for( ulong idx=0UL; idx<stake_delegations->pool_idx_wmk_; idx++ ) {
1057 0 : fd_stake_delegation_t * delegation = pool+idx;
1058 0 : if( FD_LIKELY( !delegation->in_use ) ) continue;
1059 :
1060 0 : if( FD_LIKELY( !fd_stake_delegation_is_inactive( delegation, epoch, stake_history, warmup_cooldown_rate_epoch ) ||
1061 0 : !fd_stake_delegation_is_inactive( delegation, prev_epoch, stake_history, warmup_cooldown_rate_epoch ) ) ) continue;
1062 :
1063 0 : if( FD_UNLIKELY( emit_bank ) ) fd_event_runtime_stake_delegation_remove_emit( emit_bank, delegation->stake_account.uc );
1064 0 : pruned++;
1065 :
1066 0 : fd_pubkey_t stake_account = delegation->stake_account;
1067 0 : root_map_idx_remove( map, &stake_account, (uint)idx, pool );
1068 0 : delegation->in_use = 0;
1069 0 : root_pool_idx_release( pool, (uint)idx );
1070 0 : }
1071 :
1072 0 : for( uint idx=0U; (ulong)idx<stake_delegations->disk_root_cnt_; ) {
1073 0 : fd_stake_delegation_t delegation;
1074 0 : disk_root_read( stake_delegations, idx, &delegation );
1075 0 : if( FD_LIKELY( !fd_stake_delegation_is_inactive( &delegation, epoch, stake_history, warmup_cooldown_rate_epoch ) ||
1076 0 : !fd_stake_delegation_is_inactive( &delegation, prev_epoch, stake_history, warmup_cooldown_rate_epoch ) ) ) {
1077 0 : idx++;
1078 0 : continue;
1079 0 : }
1080 :
1081 0 : if( FD_UNLIKELY( emit_bank ) ) fd_event_runtime_stake_delegation_remove_emit( emit_bank, delegation.stake_account.uc );
1082 0 : pruned++;
1083 :
1084 0 : uint found_idx;
1085 0 : ulong found_bucket_idx;
1086 0 : ulong free_bucket_idx;
1087 0 : disk_root_find( stake_delegations, &delegation.stake_account, NULL, &found_idx, &found_bucket_idx, &free_bucket_idx );
1088 0 : FD_CHECK_CRIT( found_idx==idx, "missing inactive stake delegation disk root" );
1089 0 : disk_root_remove( stake_delegations, idx, found_bucket_idx );
1090 0 : }
1091 :
1092 : /* Keep the in-memory root tier packed after pruning. */
1093 0 : while( root_pool_free( pool ) && stake_delegations->disk_root_cnt_ ) {
1094 0 : uint idx = (uint)(stake_delegations->disk_root_cnt_-1UL);
1095 0 : fd_stake_delegation_t delegation;
1096 0 : disk_root_read( stake_delegations, idx, &delegation );
1097 0 : disk_root_promote( stake_delegations, idx, &delegation );
1098 0 : }
1099 :
1100 0 : fd_rwlock_unwrite( &stake_delegations->lock );
1101 :
1102 0 : return pruned;
1103 0 : }
1104 :
1105 : void
1106 : fd_stake_delegations_refresh( fd_stake_delegations_t * stake_delegations,
1107 : ulong epoch,
1108 : fd_stake_history_t const * stake_history,
1109 : ulong * warmup_cooldown_rate_epoch,
1110 611 : int remove_inactive_stakes ) {
1111 611 : fd_rwlock_write( &stake_delegations->lock );
1112 :
1113 611 : stake_delegations->effective_stake = 0UL;
1114 611 : stake_delegations->activating_stake = 0UL;
1115 611 : stake_delegations->deactivating_stake = 0UL;
1116 :
1117 611 : fd_stake_delegation_t * pool = get_root_pool( stake_delegations );
1118 611 : ulong const wmk = stake_delegations->pool_idx_wmk_;
1119 611 : ulong prev_epoch = epoch ? epoch-1UL : 0UL;
1120 :
1121 1628 : for( ulong i=0UL; i<wmk; i++ ) {
1122 1017 : if( FD_UNLIKELY( !pool[ i ].in_use ) ) continue;
1123 1017 : fd_stake_delegation_t * delegation = pool+i;
1124 1017 : root_query_t query = { .ele = delegation };
1125 :
1126 : /* Tombstone left by fd_stake_delegations_fork_remove. */
1127 1017 : if( FD_UNLIKELY( !delegation->lamports ) ) {
1128 0 : root_remove( stake_delegations, &query );
1129 0 : continue;
1130 0 : }
1131 :
1132 1017 : if( FD_UNLIKELY( remove_inactive_stakes &&
1133 1017 : fd_stake_delegation_is_inactive( delegation, epoch, stake_history, warmup_cooldown_rate_epoch ) &&
1134 1017 : fd_stake_delegation_is_inactive( delegation, prev_epoch, stake_history, warmup_cooldown_rate_epoch ) ) ) {
1135 0 : root_remove( stake_delegations, &query );
1136 0 : continue;
1137 0 : }
1138 :
1139 1017 : fd_stake_history_entry_t history = fd_stake_delegation_activation_status( delegation, epoch, stake_history, warmup_cooldown_rate_epoch );
1140 1017 : stake_delegations->effective_stake += history.effective;
1141 1017 : stake_delegations->activating_stake += history.activating;
1142 1017 : stake_delegations->deactivating_stake += history.deactivating;
1143 :
1144 1017 : delegation->slot = 0U;
1145 1017 : delegation->state = fd_stake_delegation_classify( delegation, history, epoch ) & 0x7U;
1146 1017 : }
1147 :
1148 : /* Disk roots are dense, so removals leave the moved last record at
1149 : the current index. Survivors move back to in-memory storage
1150 : whenever pruning made a root-pool slot available. */
1151 611 : uint disk_idx = 0U;
1152 611 : while( (ulong)disk_idx<stake_delegations->disk_root_cnt_ ) {
1153 0 : fd_stake_delegation_t delegation;
1154 0 : disk_root_read( stake_delegations, disk_idx, &delegation );
1155 :
1156 0 : int remove = !delegation.lamports;
1157 0 : if( FD_LIKELY( !remove ) && FD_UNLIKELY( remove_inactive_stakes ) ) {
1158 0 : remove = fd_stake_delegation_is_inactive( &delegation, epoch, stake_history, warmup_cooldown_rate_epoch ) &&
1159 0 : fd_stake_delegation_is_inactive( &delegation, prev_epoch, stake_history, warmup_cooldown_rate_epoch );
1160 0 : }
1161 :
1162 0 : if( FD_UNLIKELY( remove ) ) {
1163 0 : uint found_idx;
1164 0 : ulong found_bucket_idx;
1165 0 : ulong free_bucket_idx;
1166 0 : disk_root_find( stake_delegations, &delegation.stake_account, NULL, &found_idx, &found_bucket_idx, &free_bucket_idx );
1167 0 : FD_CHECK_CRIT( found_idx==disk_idx, "missing refreshed stake delegation disk root" );
1168 0 : disk_root_remove( stake_delegations, disk_idx, found_bucket_idx );
1169 0 : continue;
1170 0 : }
1171 :
1172 0 : fd_stake_history_entry_t history = fd_stake_delegation_activation_status( &delegation, epoch, stake_history, warmup_cooldown_rate_epoch );
1173 0 : stake_delegations->effective_stake += history.effective;
1174 0 : stake_delegations->activating_stake += history.activating;
1175 0 : stake_delegations->deactivating_stake += history.deactivating;
1176 :
1177 0 : delegation.slot = 0U;
1178 0 : delegation.state = fd_stake_delegation_classify( &delegation, history, epoch ) & 0x7U;
1179 0 : root_query_t query = {
1180 0 : .ele = &delegation,
1181 0 : .disk_idx = disk_idx,
1182 0 : .is_disk = 1,
1183 0 : };
1184 0 : root_upsert( stake_delegations, &query, &delegation );
1185 :
1186 0 : if( FD_UNLIKELY( root_pool_free( pool ) ) ) {
1187 0 : disk_root_promote( stake_delegations, disk_idx, &delegation );
1188 0 : continue;
1189 0 : }
1190 0 : disk_idx++;
1191 0 : }
1192 :
1193 611 : fd_rwlock_unwrite( &stake_delegations->lock );
1194 611 : }
1195 :
1196 : #endif
1197 :
1198 : fd_stake_history_entry_t
1199 : fd_stake_delegations_vote_account_status( fd_stake_delegations_t * stake_delegations,
1200 : fd_pubkey_t const * vote_account,
1201 : ulong epoch,
1202 : fd_stake_history_t const * stake_history,
1203 0 : ulong * warmup_cooldown_rate_epoch ) {
1204 0 : fd_stake_history_entry_t total = {0};
1205 0 : fd_rwlock_write( &stake_delegations->lock );
1206 0 : fd_stake_delegations_iter_t iter_[1];
1207 0 : for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
1208 0 : !fd_stake_delegations_iter_done( iter );
1209 0 : fd_stake_delegations_iter_next( iter ) ) {
1210 0 : fd_stake_delegation_t const * d = fd_stake_delegations_iter_ele( iter );
1211 0 : if( FD_LIKELY( !fd_pubkey_eq( &d->vote_account, vote_account ) ) ) continue;
1212 0 : fd_stake_history_entry_t status = fd_stake_delegation_activation_status( d, epoch, stake_history, warmup_cooldown_rate_epoch );
1213 0 : total.effective += status.effective;
1214 0 : total.activating += status.activating;
1215 0 : total.deactivating += status.deactivating;
1216 0 : }
1217 0 : fd_rwlock_unwrite( &stake_delegations->lock );
1218 0 : return total;
1219 0 : }
1220 :
1221 : /* Fork-aware delta operations */
1222 :
1223 : ushort
1224 : fd_stake_delegations_new_fork( fd_stake_delegations_t * stake_delegations,
1225 623 : ushort parent_fork_idx ) {
1226 623 : fd_rwlock_write( &stake_delegations->lock );
1227 623 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1228 623 : FD_CHECK_CRIT( parent_fork_idx==USHORT_MAX ||
1229 623 : ((ulong)parent_fork_idx<fork_pool_max( fork_pool ) && fork_pool[ parent_fork_idx ].parent!=FORK_PARENT_FREE),
1230 623 : "invalid stake delegation parent fork" );
1231 623 : FD_CHECK_CRIT( fork_pool_free( fork_pool ), "no free forks in pool. The system has forked too wide." );
1232 623 : ushort fork_idx = (ushort)fork_pool_idx_acquire( fork_pool );
1233 623 : fork_pool[ fork_idx ].parent = parent_fork_idx;
1234 623 : fork_pool[ fork_idx ].delta_head = UINT_MAX;
1235 623 : fork_pool[ fork_idx ].disk_delta_head = UINT_MAX;
1236 623 : fd_rwlock_unwrite( &stake_delegations->lock );
1237 :
1238 623 : return fork_idx;
1239 623 : }
1240 :
1241 : struct fork_query {
1242 : fd_stake_delegation_t * ele; /* in-memory delta, or NULL */
1243 : disk_delta_query_t disk_query; /* spilled delta, record_idx UINT_MAX if absent */
1244 : disk_delta_t disk_ele;
1245 : };
1246 : typedef struct fork_query fork_query_t;
1247 :
1248 : /* Finds the fork's delta for stake_account in memory or on disk.
1249 : Returns it, or NULL if the fork has none. */
1250 :
1251 : static fd_stake_delegation_t const *
1252 : fork_query( fd_stake_delegations_t * stake_delegations,
1253 : ushort fork_idx,
1254 : fd_pubkey_t const * stake_account,
1255 136 : fork_query_t * query ) {
1256 136 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1257 136 : fd_stake_delegation_key_t key = {
1258 136 : .stake_account = *stake_account,
1259 136 : .fork_idx = fork_idx
1260 136 : };
1261 136 : query->ele = delta_map_ele_query( get_delta_map( stake_delegations ), &key, NULL, delta_pool );
1262 136 : query->disk_query.record_idx = UINT_MAX;
1263 136 : query->disk_query.bucket_idx = ULONG_MAX;
1264 136 : if( FD_LIKELY( query->ele ) ) return query->ele;
1265 136 : if( FD_UNLIKELY( stake_delegations->disk_delta_cnt_ || !delta_pool_free( delta_pool ) ) ) {
1266 0 : disk_delta_find( stake_delegations, fork_idx, stake_account, &query->disk_ele, &query->disk_query );
1267 0 : if( FD_UNLIKELY( query->disk_query.record_idx!=UINT_MAX ) ) return &query->disk_ele.delegation;
1268 0 : }
1269 136 : return NULL;
1270 136 : }
1271 :
1272 : /* Overwrites the queried delta or inserts a new one. */
1273 :
1274 : static void
1275 : fork_upsert( fd_stake_delegations_t * stake_delegations,
1276 : ushort fork_idx,
1277 : fork_query_t * query,
1278 136 : fd_stake_delegation_t const * delegation ) {
1279 136 : if( FD_LIKELY( query->ele ) ) {
1280 0 : fd_stake_delegation_t * in_memory = query->ele;
1281 0 : uint next = in_memory->next_;
1282 0 : uint fork_next = in_memory->fork_next;
1283 0 : *in_memory = *delegation;
1284 0 : in_memory->next_ = next;
1285 0 : in_memory->fork_next = fork_next;
1286 0 : in_memory->fork_idx = fork_idx;
1287 0 : return;
1288 0 : }
1289 :
1290 136 : if( FD_UNLIKELY( query->disk_query.record_idx!=UINT_MAX ) ) {
1291 0 : query->disk_ele.delegation = *delegation;
1292 0 : disk_delta_write( stake_delegations, query->disk_query.record_idx, &query->disk_ele );
1293 0 : return;
1294 0 : }
1295 :
1296 136 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1297 136 : if( FD_LIKELY( delta_pool_free( delta_pool ) ) ) {
1298 136 : fork_pool_ele_t * fork = get_fork_pool( stake_delegations ) + fork_idx;
1299 136 : fd_stake_delegation_t * in_memory = delta_pool_ele_acquire( delta_pool );
1300 136 : *in_memory = *delegation;
1301 136 : uint idx = (uint)delta_pool_idx( delta_pool, in_memory );
1302 136 : in_memory->fork_next = fork->delta_head;
1303 136 : in_memory->fork_idx = fork_idx;
1304 136 : FD_CHECK_CRIT( delta_map_ele_insert( get_delta_map( stake_delegations ), in_memory, delta_pool ),
1305 136 : "unable to insert stake delegation into delta map" );
1306 136 : fork->delta_head = idx;
1307 136 : } else {
1308 0 : disk_delta_insert( stake_delegations, fork_idx, delegation, &query->disk_query );
1309 0 : }
1310 136 : }
1311 :
1312 : /* Root tier: newest slot wins. Caller holds the write lock. */
1313 :
1314 : static void
1315 : root_write( fd_stake_delegations_t * stake_delegations,
1316 1017 : fd_stake_delegation_t * delegation ) {
1317 1017 : root_query_t query;
1318 1017 : root_query( stake_delegations, &delegation->stake_account, &query );
1319 1017 : if( FD_UNLIKELY( query.ele && query.ele->slot>delegation->slot ) ) return;
1320 1017 : root_upsert( stake_delegations, &query, delegation );
1321 1017 : }
1322 :
1323 : /* Fork tier: newest slot wins. Caller holds the write lock. */
1324 :
1325 : static void
1326 : fork_write( fd_stake_delegations_t * stake_delegations,
1327 : ushort fork_idx,
1328 136 : fd_stake_delegation_t const * delegation ) {
1329 136 : fork_query_t query;
1330 136 : fd_stake_delegation_t const * existing = fork_query( stake_delegations, fork_idx, &delegation->stake_account, &query );
1331 136 : if( FD_UNLIKELY( existing && existing->slot>delegation->slot ) ) return;
1332 136 : fork_upsert( stake_delegations, fork_idx, &query, delegation );
1333 136 : }
1334 :
1335 : /* fork_idx USHORT_MAX writes the root, otherwise the fork.
1336 : replacing_full_entry writes nothing unless the root holds the
1337 : account. */
1338 :
1339 : static void
1340 : delegation_write( fd_stake_delegations_t * stake_delegations,
1341 : ushort fork_idx,
1342 : fd_stake_delegation_t * delegation,
1343 1153 : int replacing_full_entry ) {
1344 1153 : fd_rwlock_write( &stake_delegations->lock );
1345 1153 : root_query_t query;
1346 1153 : if( FD_LIKELY( !replacing_full_entry || root_query( stake_delegations, &delegation->stake_account, &query ) ) ) {
1347 1153 : if( FD_LIKELY( fork_idx==USHORT_MAX ) ) root_write( stake_delegations, delegation );
1348 136 : else fork_write( stake_delegations, fork_idx, delegation );
1349 1153 : }
1350 1153 : fd_rwlock_unwrite( &stake_delegations->lock );
1351 1153 : }
1352 :
1353 : void
1354 : fd_stake_delegations_fork_update( fd_stake_delegations_t * stake_delegations,
1355 : ushort fork_idx,
1356 : ulong slot,
1357 : fd_pubkey_t const * stake_account,
1358 : fd_pubkey_t const * vote_account,
1359 : ulong stake,
1360 : ulong activation_epoch,
1361 : ulong deactivation_epoch,
1362 : ulong credits_observed,
1363 : ulong lamports,
1364 1152 : uint acc_dlen ) {
1365 1152 : FD_CHECK_ERR( (long)activation_epoch <USHORT_MAX, "activation_epoch overflow" );
1366 1152 : FD_CHECK_ERR( (long)deactivation_epoch<USHORT_MAX, "deactivation_epoch overflow" );
1367 1152 : FD_CHECK_CRIT( slot<=UINT_MAX, "slot exceeds 2^32-1" );
1368 1152 : fd_stake_delegation_t delegation = {
1369 1152 : .stake_account = *stake_account,
1370 1152 : .vote_account = *vote_account,
1371 1152 : .stake = stake,
1372 1152 : .lamports = lamports,
1373 1152 : .credits_observed = credits_observed,
1374 1152 : .acc_dlen = acc_dlen,
1375 1152 : .activation_epoch = (ushort)activation_epoch,
1376 1152 : .deactivation_epoch = (ushort)deactivation_epoch,
1377 1152 : .slot = (uint)slot,
1378 1152 : };
1379 1152 : delegation_write( stake_delegations, fork_idx, &delegation, 0 );
1380 1152 : }
1381 :
1382 : void
1383 : fd_stake_delegations_root_update( fd_stake_delegations_t * stake_delegations,
1384 : fd_pubkey_t const * stake_account,
1385 : fd_pubkey_t const * vote_account,
1386 : ulong stake,
1387 : ulong activation_epoch,
1388 : ulong deactivation_epoch,
1389 : ulong credits_observed,
1390 : ulong lamports,
1391 1016 : uint acc_dlen ) {
1392 1016 : fd_stake_delegations_fork_update( stake_delegations, USHORT_MAX, 0UL, stake_account, vote_account, stake,
1393 1016 : activation_epoch, deactivation_epoch, credits_observed, lamports, acc_dlen );
1394 1016 : }
1395 :
1396 : void
1397 : fd_stake_delegations_fork_remove( fd_stake_delegations_t * stake_delegations,
1398 : ushort fork_idx,
1399 : ulong slot,
1400 : fd_pubkey_t const * stake_account,
1401 0 : int replacing_full_entry ) {
1402 0 : FD_CHECK_CRIT( slot<=UINT_MAX, "slot exceeds 2^32-1" );
1403 0 : fd_stake_delegation_t tombstone = {
1404 0 : .stake_account = *stake_account,
1405 0 : .is_tombstone = 1,
1406 0 : .slot = (uint)slot,
1407 0 : };
1408 0 : delegation_write( stake_delegations, fork_idx, &tombstone, replacing_full_entry );
1409 0 : }
1410 :
1411 : void
1412 : fd_stake_delegations_evict_fork( fd_stake_delegations_t * stake_delegations,
1413 611 : ushort fork_idx ) {
1414 611 : if( fork_idx==USHORT_MAX ) return;
1415 :
1416 611 : fd_rwlock_write( &stake_delegations->lock );
1417 :
1418 611 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1419 611 : delta_map_t * delta_map = get_delta_map( stake_delegations );
1420 611 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1421 611 : FD_CHECK_CRIT( (ulong)fork_idx<fork_pool_max( fork_pool ) && fork_pool[ fork_idx ].parent!=FORK_PARENT_FREE,
1422 611 : "invalid stake delegation fork" );
1423 :
1424 611 : uint idx = fork_pool[ fork_idx ].delta_head;
1425 747 : while( idx!=UINT_MAX ) {
1426 136 : fd_stake_delegation_t * ele = delta_pool + idx;
1427 136 : FD_CHECK_CRIT( delta_map_ele_remove( delta_map, &ele->key, NULL, delta_pool )==ele,
1428 136 : "stake delegation missing from delta map" );
1429 136 : idx = ele->fork_next;
1430 136 : delta_pool_ele_release( delta_pool, ele );
1431 136 : }
1432 611 : fork_pool[ fork_idx ].delta_head = UINT_MAX;
1433 :
1434 611 : while( fork_pool[ fork_idx ].disk_delta_head!=UINT_MAX ) {
1435 0 : uint disk_idx = fork_pool[ fork_idx ].disk_delta_head;
1436 0 : disk_delta_t disk_delta;
1437 0 : disk_delta_read( stake_delegations, disk_idx, &disk_delta );
1438 0 : disk_delta_remove( stake_delegations, disk_idx, &disk_delta );
1439 0 : }
1440 611 : fork_pool[ fork_idx ].disk_delta_head = UINT_MAX;
1441 :
1442 611 : fork_pool[ fork_idx ].parent = FORK_PARENT_FREE;
1443 611 : fork_pool_idx_release( fork_pool, fork_idx );
1444 :
1445 611 : fd_rwlock_unwrite( &stake_delegations->lock );
1446 611 : }
1447 :
1448 : /* skip_stake_math applies the delta without updating totals or
1449 : state, for the snapshot loader; fd_stake_delegations_refresh
1450 : recomputes them afterwards. */
1451 :
1452 : static void
1453 : apply_delta( ulong epoch,
1454 : fd_stake_history_t const * stake_history,
1455 : ulong * warmup_cooldown_rate_epoch,
1456 : int skip_stake_math,
1457 : fd_stake_delegations_t * stake_delegations,
1458 0 : fd_stake_delegation_t const * delta ) {
1459 0 : root_query_t query;
1460 0 : int found = root_query( stake_delegations, &delta->stake_account, &query );
1461 :
1462 0 : if( FD_UNLIKELY( skip_stake_math ) ) {
1463 0 : if( FD_UNLIKELY( delta->is_tombstone ) ) {
1464 0 : if( FD_LIKELY( found ) ) root_remove( stake_delegations, &query );
1465 0 : return;
1466 0 : }
1467 0 : fd_stake_delegation_t root_delegation = *delta;
1468 0 : root_upsert( stake_delegations, &query, &root_delegation );
1469 0 : return;
1470 0 : }
1471 :
1472 0 : if( FD_LIKELY( found ) ) {
1473 0 : fd_stake_history_entry_t old_entry = fd_stake_delegation_activation_status(
1474 0 : query.ele, epoch, stake_history, warmup_cooldown_rate_epoch );
1475 0 : stake_delegations->effective_stake -= old_entry.effective;
1476 0 : stake_delegations->activating_stake -= old_entry.activating;
1477 0 : stake_delegations->deactivating_stake -= old_entry.deactivating;
1478 0 : }
1479 :
1480 0 : if( FD_UNLIKELY( delta->is_tombstone ) ) {
1481 0 : if( FD_LIKELY( found ) ) root_remove( stake_delegations, &query );
1482 0 : return;
1483 0 : }
1484 :
1485 0 : fd_stake_delegation_t root_delegation = *delta;
1486 0 : fd_stake_history_entry_t new_entry = fd_stake_delegation_activation_status(
1487 0 : delta, epoch, stake_history, warmup_cooldown_rate_epoch );
1488 0 : stake_delegations->effective_stake += new_entry.effective;
1489 0 : stake_delegations->activating_stake += new_entry.activating;
1490 0 : stake_delegations->deactivating_stake += new_entry.deactivating;
1491 :
1492 0 : root_delegation.state = fd_stake_delegation_classify( &root_delegation, new_entry, epoch ) & 0x7U;
1493 0 : root_upsert( stake_delegations, &query, &root_delegation );
1494 0 : }
1495 :
1496 : static void
1497 : apply_fork_delta( ulong epoch,
1498 : fd_stake_history_t const * stake_history,
1499 : ulong * warmup_cooldown_rate_epoch,
1500 : int skip_stake_math,
1501 : fd_stake_delegations_t * stake_delegations,
1502 : ushort fork_idx,
1503 0 : fd_stake_delegations_delta_stats_t * stake_delegations_delta_stats ) {
1504 0 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1505 0 : ulong upserts = 0UL;
1506 0 : ulong removes = 0UL;
1507 :
1508 : /* Apply disk deltas first. */
1509 0 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1510 0 : while( fork_pool[ fork_idx ].disk_delta_head!=UINT_MAX ) {
1511 0 : uint disk_idx = fork_pool[ fork_idx ].disk_delta_head;
1512 0 : disk_delta_t disk_delta;
1513 0 : disk_delta_read( stake_delegations, disk_idx, &disk_delta );
1514 0 : fd_stake_delegation_t delegation = disk_delta.delegation;
1515 0 : disk_delta_remove( stake_delegations, disk_idx, &disk_delta );
1516 0 : upserts += (ulong)!delegation.is_tombstone;
1517 0 : removes += (ulong)!!delegation.is_tombstone;
1518 0 : apply_delta( epoch, stake_history, warmup_cooldown_rate_epoch,
1519 0 : skip_stake_math, stake_delegations, &delegation );
1520 0 : }
1521 :
1522 : /* Apply in-memory deltas. */
1523 0 : for( uint idx=fork_pool[ fork_idx ].delta_head; idx!=UINT_MAX; idx=delta_pool[ idx ].fork_next ) {
1524 0 : fd_stake_delegation_t * delegation = delta_pool + idx;
1525 0 : upserts += (ulong)!delegation->is_tombstone;
1526 0 : removes += (ulong)!!delegation->is_tombstone;
1527 0 : apply_delta( epoch, stake_history, warmup_cooldown_rate_epoch,
1528 0 : skip_stake_math, stake_delegations, delegation );
1529 0 : }
1530 :
1531 0 : if( stake_delegations_delta_stats ) {
1532 0 : stake_delegations_delta_stats->upserts += upserts;
1533 0 : stake_delegations_delta_stats->removes += removes;
1534 0 : }
1535 0 : }
1536 :
1537 : void
1538 : fd_stake_delegations_advance_root( ulong epoch,
1539 : fd_stake_history_t const * stake_history,
1540 : ulong * warmup_cooldown_rate_epoch,
1541 : int skip_stake_math,
1542 : fd_stake_delegations_t * stake_delegations,
1543 : ushort fork_idx,
1544 0 : fd_stake_delegations_delta_stats_t * stake_delegations_delta_stats ) {
1545 0 : fd_rwlock_write( &stake_delegations->lock );
1546 :
1547 0 : ushort fork_ids[ FD_STAKE_DELEGATIONS_FORK_MAX ];
1548 0 : ulong fork_id_cnt = fork_ancestry( stake_delegations, fork_idx, fork_ids );
1549 0 : for( ulong i=fork_id_cnt; i; i-- ) {
1550 0 : apply_fork_delta( epoch, stake_history, warmup_cooldown_rate_epoch,
1551 0 : skip_stake_math, stake_delegations, fork_ids[ i-1UL ], stake_delegations_delta_stats );
1552 0 : }
1553 : /* The target is now rooted. Its children no longer need a parent
1554 : delta layer, and its descriptor can be freed by the caller. */
1555 0 : if( fork_idx!=USHORT_MAX ) {
1556 0 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1557 0 : for( ulong i=0UL; i<fork_pool_max( fork_pool ); i++ ) {
1558 0 : if( fork_pool[ i ].parent==fork_idx ) fork_pool[ i ].parent = USHORT_MAX;
1559 0 : }
1560 0 : fork_pool[ fork_idx ].parent = USHORT_MAX;
1561 0 : }
1562 0 : if( FD_UNLIKELY( stake_delegations_delta_stats ) ) {
1563 0 : stake_delegations_delta_stats->root_cnt = root_pool_used( get_root_pool( stake_delegations ) ) + stake_delegations->disk_root_cnt_;
1564 0 : }
1565 0 : FD_LOG_DEBUG(( "effective_stake=%lu, activating_stake=%lu, deactivating_stake=%lu", stake_delegations->effective_stake, stake_delegations->activating_stake, stake_delegations->deactivating_stake ));
1566 :
1567 0 : fd_rwlock_unwrite( &stake_delegations->lock );
1568 0 : }
1569 :
1570 : void
1571 : fd_stake_delegations_iter_read_disk_delta_private( fd_stake_delegations_iter_t * iter,
1572 0 : uint delta_idx ) {
1573 0 : FD_CHECK_CRIT( (ulong)delta_idx<iter->stake_delegations->disk_delta_cnt_,
1574 0 : "invalid stake delegation iterator disk delta" );
1575 0 : disk_delta_t delta;
1576 0 : disk_delta_read( iter->stake_delegations, delta_idx, &delta );
1577 0 : iter->disk_ele = delta.delegation;
1578 0 : iter->ele = iter->disk_ele.is_tombstone ? NULL : &iter->disk_ele;
1579 0 : }
1580 :
1581 : void
1582 806 : fd_stake_delegations_iter_advance_disk_root_private( fd_stake_delegations_iter_t * iter ) {
1583 806 : fd_stake_delegations_t const * stake_delegations = iter->stake_delegations;
1584 806 : while( iter->disk_idx<stake_delegations->disk_root_cnt_ ) {
1585 0 : ulong batch_idx = iter->disk_idx % FD_STAKE_DELEGATIONS_ITER_BATCH_CNT;
1586 0 : if( FD_UNLIKELY( !batch_idx ) ) {
1587 0 : ulong cnt = fd_ulong_min( FD_STAKE_DELEGATIONS_ITER_BATCH_CNT, stake_delegations->disk_root_cnt_-iter->disk_idx );
1588 0 : disk_read( stake_delegations, iter->disk_batch,
1589 0 : disk_root_record_off( stake_delegations ) + iter->disk_idx*sizeof(fd_stake_delegation_t),
1590 0 : cnt*sizeof(fd_stake_delegation_t) );
1591 0 : }
1592 0 : fd_stake_delegation_t * root = iter->disk_batch + batch_idx;
1593 0 : iter->idx = stake_delegations->max_stake_accounts_ + iter->disk_idx;
1594 :
1595 0 : uint delta_idx = root->delta_idx;
1596 0 : if( FD_LIKELY( delta_idx==UINT_MAX ) ) {
1597 0 : iter->ele = root;
1598 0 : return;
1599 0 : }
1600 0 : if( FD_UNLIKELY( delta_idx & FD_STAKE_DELEGATIONS_DELTA_DISK_TAG ) ) {
1601 0 : fd_stake_delegations_iter_read_disk_delta_private( iter, delta_idx & FD_STAKE_DELEGATIONS_DELTA_IDX_MASK );
1602 0 : if( FD_LIKELY( iter->ele ) ) return;
1603 0 : } else {
1604 0 : fd_stake_delegation_t * delta = iter->delta_pool + delta_idx;
1605 0 : if( FD_LIKELY( !delta->is_tombstone ) ) {
1606 0 : iter->ele = delta;
1607 0 : return;
1608 0 : }
1609 0 : }
1610 0 : iter->disk_idx++;
1611 0 : }
1612 806 : iter->ele = NULL;
1613 806 : }
1614 :
1615 : fd_stake_delegations_iter_t *
1616 : fd_stake_delegations_iter_init( fd_stake_delegations_iter_t * iter,
1617 806 : fd_stake_delegations_t const * stake_delegations ) {
1618 806 : if( FD_UNLIKELY( !stake_delegations ) ) {
1619 0 : FD_LOG_CRIT(( "NULL stake_delegations" ));
1620 0 : }
1621 :
1622 806 : iter->root_pool = get_root_pool( stake_delegations );
1623 806 : iter->delta_pool = get_delta_pool( stake_delegations );
1624 806 : iter->stake_delegations = stake_delegations;
1625 806 : iter->idx = 0UL;
1626 806 : iter->wmk = stake_delegations->pool_idx_wmk_;
1627 806 : iter->root_max = stake_delegations->max_stake_accounts_;
1628 806 : iter->disk_idx = 0UL;
1629 806 : fd_stake_delegations_iter_advance_private( iter );
1630 :
1631 806 : return iter;
1632 806 : }
1633 :
1634 : static fd_stake_delegation_t const *
1635 : delta_ref_query( fd_stake_delegations_t const * stake_delegations,
1636 : uint delta_ref,
1637 0 : fd_stake_delegation_t * disk_copy ) {
1638 0 : if( FD_UNLIKELY( delta_ref & FD_STAKE_DELEGATIONS_DELTA_DISK_TAG ) ) {
1639 0 : uint idx = delta_ref & FD_STAKE_DELEGATIONS_DELTA_IDX_MASK;
1640 0 : FD_CHECK_CRIT( (ulong)idx<stake_delegations->disk_delta_cnt_, "invalid stake delegation disk delta reference" );
1641 0 : disk_delta_t disk_delta;
1642 0 : disk_delta_read( stake_delegations, idx, &disk_delta );
1643 0 : *disk_copy = disk_delta.delegation;
1644 0 : return disk_copy;
1645 0 : }
1646 0 : return get_delta_pool( stake_delegations ) + delta_ref;
1647 0 : }
1648 :
1649 : static void
1650 : mark_delta_one( fd_stake_delegations_t * stake_delegations,
1651 : ulong epoch,
1652 : fd_stake_history_t const * stake_history,
1653 : ulong * warmup_cooldown_rate_epoch,
1654 : fd_stake_delegation_t const * delta,
1655 136 : uint delta_ref ) {
1656 136 : root_query_t query;
1657 136 : int found = root_query( stake_delegations, &delta->stake_account, &query );
1658 136 : fd_stake_delegation_t base;
1659 136 : if( FD_UNLIKELY( !found ) ) {
1660 0 : base = (fd_stake_delegation_t) {
1661 0 : .stake_account = delta->stake_account,
1662 0 : .delta_idx = delta_ref,
1663 0 : .dne_in_root = 1,
1664 0 : .in_use = 1,
1665 0 : };
1666 0 : root_insert( stake_delegations, &base );
1667 136 : } else {
1668 136 : base = *query.ele;
1669 136 : fd_stake_delegation_t disk_copy;
1670 136 : fd_stake_delegation_t const * old = base.delta_idx==UINT_MAX
1671 136 : ? &base
1672 136 : : delta_ref_query( stake_delegations, base.delta_idx, &disk_copy );
1673 136 : if( FD_LIKELY( !old->is_tombstone ) ) {
1674 136 : fd_stake_history_entry_t old_entry = fd_stake_delegation_activation_status(
1675 136 : old, epoch, stake_history, warmup_cooldown_rate_epoch );
1676 136 : stake_delegations->effective_stake -= old_entry.effective;
1677 136 : stake_delegations->activating_stake -= old_entry.activating;
1678 136 : stake_delegations->deactivating_stake -= old_entry.deactivating;
1679 136 : }
1680 136 : base.delta_idx = delta_ref;
1681 136 : root_store( stake_delegations, &query, &base );
1682 136 : }
1683 :
1684 136 : if( FD_LIKELY( !delta->is_tombstone ) ) {
1685 136 : fd_stake_history_entry_t new_entry = fd_stake_delegation_activation_status(
1686 136 : delta, epoch, stake_history, warmup_cooldown_rate_epoch );
1687 136 : stake_delegations->effective_stake += new_entry.effective;
1688 136 : stake_delegations->activating_stake += new_entry.activating;
1689 136 : stake_delegations->deactivating_stake += new_entry.deactivating;
1690 136 : }
1691 136 : }
1692 :
1693 : static void
1694 : unmark_delta_one( fd_stake_delegations_t * stake_delegations,
1695 : ulong epoch,
1696 : fd_stake_history_t const * stake_history,
1697 : ulong * warmup_cooldown_rate_epoch,
1698 : fd_stake_delegation_t const * delta,
1699 136 : uint delta_ref ) {
1700 136 : root_query_t query;
1701 136 : if( FD_UNLIKELY( !root_query( stake_delegations, &delta->stake_account, &query ) ||
1702 136 : query.ele->delta_idx!=delta_ref ) ) return;
1703 136 : fd_stake_delegation_t base = *query.ele;
1704 :
1705 136 : if( FD_LIKELY( !delta->is_tombstone ) ) {
1706 136 : fd_stake_history_entry_t entry = fd_stake_delegation_activation_status(
1707 136 : delta, epoch, stake_history, warmup_cooldown_rate_epoch );
1708 136 : stake_delegations->effective_stake -= entry.effective;
1709 136 : stake_delegations->activating_stake -= entry.activating;
1710 136 : stake_delegations->deactivating_stake -= entry.deactivating;
1711 136 : }
1712 :
1713 136 : if( FD_UNLIKELY( base.dne_in_root ) ) {
1714 0 : root_remove( stake_delegations, &query );
1715 0 : return;
1716 0 : }
1717 :
1718 136 : base.delta_idx = UINT_MAX;
1719 136 : root_store( stake_delegations, &query, &base );
1720 136 : fd_stake_history_entry_t entry = fd_stake_delegation_activation_status(
1721 136 : &base, epoch, stake_history, warmup_cooldown_rate_epoch );
1722 136 : stake_delegations->effective_stake += entry.effective;
1723 136 : stake_delegations->activating_stake += entry.activating;
1724 136 : stake_delegations->deactivating_stake += entry.deactivating;
1725 136 : }
1726 :
1727 : void
1728 : fd_stake_delegations_view_begin( fd_stake_delegations_t * stake_delegations,
1729 : ulong epoch,
1730 : fd_stake_history_t const * stake_history,
1731 : ulong * warmup_cooldown_rate_epoch,
1732 680 : ushort fork_idx ) {
1733 680 : fd_rwlock_write( &stake_delegations->lock );
1734 :
1735 680 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1736 680 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1737 :
1738 680 : ushort fork_ids[ FD_STAKE_DELEGATIONS_FORK_MAX ];
1739 680 : ulong fork_id_cnt = fork_ancestry( stake_delegations, fork_idx, fork_ids );
1740 680 : stake_delegations->frontier_query_epoch = epoch;
1741 680 : stake_delegations->frontier_query_fork = fork_idx;
1742 1360 : for( ulong i=fork_id_cnt; i; i-- ) {
1743 680 : fork_pool_ele_t const * fork = fork_pool + fork_ids[ i-1UL ];
1744 816 : for( uint delta_ref=fork->delta_head; delta_ref!=UINT_MAX; delta_ref=delta_pool[ delta_ref ].fork_next ) {
1745 136 : fd_stake_delegation_t * delta_delegation = delta_pool + delta_ref;
1746 136 : mark_delta_one( stake_delegations, epoch, stake_history, warmup_cooldown_rate_epoch, delta_delegation, delta_ref );
1747 136 : }
1748 :
1749 680 : uint disk_idx = fork->disk_delta_head;
1750 680 : while( disk_idx!=UINT_MAX ) {
1751 0 : disk_delta_t disk_delta;
1752 0 : disk_delta_read( stake_delegations, disk_idx, &disk_delta );
1753 0 : mark_delta_one( stake_delegations, epoch, stake_history, warmup_cooldown_rate_epoch, &disk_delta.delegation,
1754 0 : disk_idx | FD_STAKE_DELEGATIONS_DELTA_DISK_TAG );
1755 0 : disk_idx = disk_delta.next;
1756 0 : }
1757 680 : }
1758 680 : }
1759 :
1760 : void
1761 : fd_stake_delegations_view_end( fd_stake_delegations_t * stake_delegations,
1762 : fd_stake_history_t const * stake_history,
1763 680 : ulong * warmup_cooldown_rate_epoch ) {
1764 680 : ulong epoch = stake_delegations->frontier_query_epoch;
1765 :
1766 680 : fd_stake_delegation_t * delta_pool = get_delta_pool( stake_delegations );
1767 680 : fork_pool_ele_t * fork_pool = get_fork_pool( stake_delegations );
1768 :
1769 680 : ushort fork_ids[ FD_STAKE_DELEGATIONS_FORK_MAX ];
1770 680 : ulong fork_id_cnt = fork_ancestry( stake_delegations, stake_delegations->frontier_query_fork, fork_ids );
1771 1360 : for( ulong i=fork_id_cnt; i; i-- ) {
1772 680 : fork_pool_ele_t const * fork = fork_pool + fork_ids[ i-1UL ];
1773 816 : for( uint delta_ref=fork->delta_head; delta_ref!=UINT_MAX; delta_ref=delta_pool[ delta_ref ].fork_next ) {
1774 136 : fd_stake_delegation_t * delta_delegation = delta_pool + delta_ref;
1775 136 : unmark_delta_one( stake_delegations, epoch, stake_history, warmup_cooldown_rate_epoch, delta_delegation, delta_ref );
1776 136 : }
1777 :
1778 680 : uint disk_idx = fork->disk_delta_head;
1779 680 : while( disk_idx!=UINT_MAX ) {
1780 0 : disk_delta_t disk_delta;
1781 0 : disk_delta_read( stake_delegations, disk_idx, &disk_delta );
1782 0 : unmark_delta_one( stake_delegations, epoch, stake_history, warmup_cooldown_rate_epoch, &disk_delta.delegation,
1783 0 : disk_idx | FD_STAKE_DELEGATIONS_DELTA_DISK_TAG );
1784 0 : disk_idx = disk_delta.next;
1785 0 : }
1786 680 : }
1787 680 : stake_delegations->frontier_query_epoch = ULONG_MAX;
1788 680 : stake_delegations->frontier_query_fork = USHORT_MAX;
1789 680 : disk_root_maintain( stake_delegations );
1790 680 : fd_rwlock_unwrite( &stake_delegations->lock );
1791 680 : }
|