LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stake_delegations.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 462 1178 39.2 %
Date: 2026-11-11 12:40:43 Functions: 35 122 28.7 %

          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 : }

Generated by: LCOV version 1.14