LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stake_delegations.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 479 1194 40.1 %
Date: 2026-10-22 18:10:43 Functions: 37 122 30.3 %

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

Generated by: LCOV version 1.14