LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 386 544 71.0 %
Date: 2026-08-13 04:56:22 Functions: 17 24 70.8 %

          Line data    Source code
       1             : #include "fd_stakes.h"
       2             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       3             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
       4             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       5             : #include "../runtime/program/fd_vote_program.h"
       6             : #include "../runtime/fd_runtime_stack.h"
       7             : #include "../runtime/fd_system_ids.h"
       8             : #include "../../util/bits/fd_sat.h"
       9             : 
      10             : /**********************************************************************/
      11             : /* Constants                                                          */
      12             : /**********************************************************************/
      13             : 
      14             : #define DEFAULT_SLASH_PENALTY                ( 12 )
      15             : 
      16             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L3-L5 */
      17     6000036 : #define FD_BASIS_POINTS_PER_UNIT             (10000UL)
      18     8993637 : #define FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS (2500UL)
      19     3006417 : #define FD_TOWER_WARMUP_COOLDOWN_RATE_BPS    (900UL)
      20             : 
      21             : /**********************************************************************/
      22             : /* Types                                                              */
      23             : /**********************************************************************/
      24             : 
      25             : struct effective_activating {
      26             :   ulong effective;
      27             :   ulong activating;
      28             : };
      29             : typedef struct effective_activating effective_activating_t;
      30             : 
      31             : /**********************************************************************/
      32             : /* Static helpers                                                     */
      33             : /**********************************************************************/
      34             : 
      35             : // https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L694-L778
      36             : static effective_activating_t
      37             : stake_and_activating( fd_delegation_t const *    self,
      38             :                       ulong                      target_epoch,
      39             :                       fd_stake_history_t const * history,
      40         948 :                       ulong *                    new_rate_activation_epoch ) {
      41         948 :   ulong delegated_stake = self->stake;
      42             : 
      43         948 :   fd_stake_history_entry_t const * cluster_stake_at_activation_epoch;
      44         948 :   if( self->activation_epoch==ULONG_MAX ) {
      45         483 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
      46         483 :   } else if( self->activation_epoch==self->deactivation_epoch ) {
      47         135 :     return ( effective_activating_t ){ .effective = 0, .activating = 0 };
      48         330 :   } else if( target_epoch==self->activation_epoch ) {
      49          21 :     return ( effective_activating_t ){ .effective = 0, .activating = delegated_stake };
      50         309 :   } else if( target_epoch<self->activation_epoch ) {
      51           0 :     return ( effective_activating_t ){ .effective = 0, .activating = 0 };
      52         309 :   } else if( history &&
      53         309 :               ( cluster_stake_at_activation_epoch = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
      54           3 :     ulong                            prev_epoch         = self->activation_epoch;
      55           3 :     fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_activation_epoch;
      56             : 
      57           3 :     ulong current_epoch;
      58           3 :     ulong current_effective_stake = 0;
      59           3 :     for( ;; ) {
      60           3 :       current_epoch = prev_epoch + 1;
      61           3 :       if( FD_LIKELY( prev_cluster_stake->activating==0 ) ) {
      62           3 :         break;
      63           3 :       }
      64             : 
      65           0 :       ulong remaining_activating_stake = delegated_stake - current_effective_stake;
      66           0 :       ulong newly_effective_stake = fd_ulong_max( fd_stake_calculate_change_allowance_float( current_epoch, remaining_activating_stake, prev_cluster_stake->activating, prev_cluster_stake->effective, new_rate_activation_epoch ), 1 );
      67             : 
      68           0 :       current_effective_stake += newly_effective_stake;
      69           0 :       if( FD_LIKELY( current_effective_stake>=delegated_stake ) ) {
      70           0 :         current_effective_stake = delegated_stake;
      71           0 :         break;
      72           0 :       }
      73             : 
      74           0 :       if( FD_LIKELY( current_epoch>=target_epoch ||
      75           0 :                      current_epoch>=self->deactivation_epoch ) ) {
      76           0 :         break;
      77           0 :       }
      78             : 
      79           0 :       fd_stake_history_entry_t const * current_cluster_stake = fd_sysvar_stake_history_query( history, current_epoch );
      80           0 :       if( FD_LIKELY( current_cluster_stake ) ) {
      81           0 :         prev_epoch         = current_epoch;
      82           0 :         prev_cluster_stake = current_cluster_stake;
      83           0 :       } else {
      84           0 :         break;
      85           0 :       }
      86           0 :     }
      87           3 :     return ( effective_activating_t ){ .effective  = current_effective_stake,
      88           3 :                                        .activating = delegated_stake - current_effective_stake };
      89         306 :   } else {
      90         306 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
      91         306 :   }
      92         948 : }
      93             : 
      94             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L606-L690 */
      95             : fd_stake_history_entry_t
      96             : stake_activating_and_deactivating( fd_delegation_t const *    self,
      97             :                                    ulong                      target_epoch,
      98             :                                    fd_stake_history_t const * stake_history,
      99         948 :                                    ulong *                    new_rate_activation_epoch ) {
     100             : 
     101         948 :   effective_activating_t effective_activating = stake_and_activating( self, target_epoch, stake_history, new_rate_activation_epoch );
     102             : 
     103         948 :   ulong effective_stake  = effective_activating.effective;
     104         948 :   ulong activating_stake = effective_activating.activating;
     105             : 
     106         948 :   fd_stake_history_entry_t const * cluster_stake_at_deactivation_epoch = NULL;
     107             : 
     108         948 :   if( target_epoch<self->deactivation_epoch ) {
     109         789 :     if( activating_stake==0 ) {
     110         765 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = 0, .activating = 0 };
     111         765 :     } else {
     112          24 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = 0, .activating = activating_stake };
     113          24 :     }
     114         789 :   } else if( target_epoch==self->deactivation_epoch ) {
     115           6 :     return ( fd_stake_history_entry_t ){ .effective = effective_stake, .deactivating = effective_stake, .activating = 0 };
     116         153 :   } else if( stake_history &&
     117         153 :              ( cluster_stake_at_deactivation_epoch = fd_sysvar_stake_history_query( stake_history, self->deactivation_epoch ) ) ) {
     118           0 :     ulong                            prev_epoch         = self->deactivation_epoch;
     119           0 :     fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_deactivation_epoch;
     120             : 
     121           0 :     ulong current_epoch;
     122           0 :     ulong current_effective_stake = effective_stake;
     123           0 :     for( ;; ) {
     124           0 :       current_epoch = prev_epoch + 1;
     125           0 :       if( prev_cluster_stake->deactivating==0 ) break;
     126             : 
     127           0 :       ulong newly_not_effective_stake = fd_ulong_max( fd_stake_calculate_change_allowance_float( current_epoch, current_effective_stake, prev_cluster_stake->deactivating, prev_cluster_stake->effective, new_rate_activation_epoch ), 1 );
     128             : 
     129           0 :       current_effective_stake = fd_ulong_sat_sub( current_effective_stake, newly_not_effective_stake );
     130           0 :       if( current_effective_stake==0 ) break;
     131             : 
     132           0 :       if( current_epoch>=target_epoch ) break;
     133             : 
     134           0 :       fd_stake_history_entry_t const * current_cluster_stake = NULL;
     135           0 :       if( ( current_cluster_stake = fd_sysvar_stake_history_query(stake_history, current_epoch ) ) ) {
     136           0 :         prev_epoch         = current_epoch;
     137           0 :         prev_cluster_stake = current_cluster_stake;
     138           0 :       } else {
     139           0 :         break;
     140           0 :       }
     141           0 :     }
     142           0 :     return ( fd_stake_history_entry_t ){ .effective    = current_effective_stake,
     143           0 :                                          .deactivating = current_effective_stake,
     144           0 :                                          .activating   = 0 };
     145         153 :   } else {
     146         153 :     return ( fd_stake_history_entry_t ){ .effective = 0, .activating = 0, .deactivating = 0 };
     147         153 :   }
     148         948 : }
     149             : 
     150             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L7-L14 */
     151             : static ulong
     152    12000054 : fd_stake_warmup_cooldown_rate_bps( ulong current_epoch, ulong * opt_rate_change_activation_epoch ) {
     153    12000054 :   if( opt_rate_change_activation_epoch && current_epoch>=*opt_rate_change_activation_epoch ) {
     154     3006417 :     return FD_TOWER_WARMUP_COOLDOWN_RATE_BPS;
     155     8993637 :   } else {
     156     8993637 :     return FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS;
     157     8993637 :   }
     158    12000054 : }
     159             : 
     160             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L54-L93 */
     161             : static ulong
     162             : calculate_stake_change_allowance( ulong   epoch,
     163             :                                   ulong   account_portion,
     164             :                                   ulong   cluster_portion,
     165             :                                   ulong   cluster_effective,
     166     6000054 :                                   ulong * opt_rate_change_activation_epoch ) {
     167     6000054 :   if( account_portion==0UL || cluster_portion==0UL || cluster_effective==0UL ) {
     168          18 :     return 0UL;
     169          18 :   }
     170             : 
     171     6000036 :   ulong rate_bps = fd_stake_warmup_cooldown_rate_bps( epoch, opt_rate_change_activation_epoch );
     172             : 
     173     6000036 :   uint128 numerator   = fd_uint128_sat_mul( fd_uint128_sat_mul( (uint128)account_portion, (uint128)cluster_effective ),
     174     6000036 :                                             (uint128)rate_bps );
     175     6000036 :   uint128 denominator = fd_uint128_sat_mul( (uint128)cluster_portion, (uint128)FD_BASIS_POINTS_PER_UNIT );
     176             : 
     177             :   /* denominator is never zero due to guard above */
     178     6000036 :   uint128 delta = numerator / denominator;
     179     6000036 :   uint128 cap   = (uint128)account_portion;
     180     6000036 :   return (ulong)( delta<cap ? delta : cap );
     181     6000054 : }
     182             : 
     183             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L16-L33 */
     184             : ulong
     185             : fd_stake_calculate_activation_allowance( ulong                            current_epoch,
     186             :                                          ulong                            account_activating_stake,
     187             :                                          fd_stake_history_entry_t const * prev_epoch_cluster_state,
     188     6000030 :                                          ulong *                          opt_rate_change_activation_epoch ) {
     189     6000030 :   return calculate_stake_change_allowance( current_epoch,
     190     6000030 :                                            account_activating_stake,
     191     6000030 :                                            prev_epoch_cluster_state->activating,
     192     6000030 :                                            prev_epoch_cluster_state->effective,
     193     6000030 :                                            opt_rate_change_activation_epoch );
     194     6000030 : }
     195             : 
     196             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L35-L52 */
     197             : static ulong
     198             : fd_stake_calculate_deactivation_allowance( ulong                            current_epoch,
     199             :                                            ulong                            account_deactivating_stake,
     200             :                                            fd_stake_history_entry_t const * prev_epoch_cluster_state,
     201          24 :                                            ulong *                          opt_rate_change_activation_epoch ) {
     202          24 :   return calculate_stake_change_allowance( current_epoch,
     203          24 :                                            account_deactivating_stake,
     204          24 :                                            prev_epoch_cluster_state->deactivating,
     205          24 :                                            prev_epoch_cluster_state->effective,
     206          24 :                                            opt_rate_change_activation_epoch );
     207          24 : }
     208             : 
     209             : /* Fixed-point version of stake_and_activating.
     210             :    Mirrors exactly the logic in the on-chain stake program:
     211             :    https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L881-L971 */
     212             : static effective_activating_t
     213             : stake_and_activating_v2( fd_delegation_t const *    self,
     214             :                          ulong                      target_epoch,
     215             :                          fd_stake_history_t const * history,
     216         168 :                          ulong *                    new_rate_activation_epoch ) {
     217         168 :   ulong delegated_stake = self->stake;
     218             : 
     219         168 :   fd_stake_history_entry_t const * prev_cluster_stake = NULL;
     220             : 
     221         168 :   if( self->activation_epoch==ULONG_MAX ) {
     222           6 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
     223         162 :   } else if( self->activation_epoch==self->deactivation_epoch ) {
     224         135 :     return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
     225         135 :   } else if( target_epoch==self->activation_epoch ) {
     226           0 :     return ( effective_activating_t ){ .effective = 0UL, .activating = delegated_stake };
     227          27 :   } else if( target_epoch<self->activation_epoch ) {
     228          18 :     return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
     229          18 :   } else if( history &&
     230           9 :              ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
     231             : 
     232           0 :     ulong prev_epoch = self->activation_epoch;
     233             : 
     234           0 :     ulong current_epoch;
     235           0 :     ulong activated_stake_amount = 0UL;
     236           0 :     for(;;) {
     237           0 :       current_epoch = prev_epoch + 1UL;
     238             : 
     239             :       /* If there is no activating stake at prev epoch, we should have
     240             :          been fully effective at this moment */
     241           0 :       if( FD_LIKELY( prev_cluster_stake->activating==0UL ) ) break;
     242             : 
     243             :       /* Calculate how much of this account's remaining stake becomes
     244             :          effective in current_epoch. */
     245           0 :       ulong remaining_activating_stake = delegated_stake - activated_stake_amount;
     246           0 :       ulong newly_effective_stake      = fd_stake_calculate_activation_allowance( current_epoch,
     247           0 :                                                                                   remaining_activating_stake,
     248           0 :                                                                                   prev_cluster_stake,
     249           0 :                                                                                   new_rate_activation_epoch );
     250             : 
     251             :       /* Add the newly effective stake, clamping the per-epoch increase
     252             :          to at least 1 lamport so warmup always makes progress */
     253           0 :       activated_stake_amount += fd_ulong_max( newly_effective_stake, 1UL );
     254             : 
     255             :       /* Stop if we've fully warmed up this account's stake. */
     256           0 :       if( FD_LIKELY( activated_stake_amount>=delegated_stake ) ) {
     257           0 :         activated_stake_amount = delegated_stake;
     258           0 :         break;
     259           0 :       }
     260             : 
     261             :       /* Stop when we've reached the time bound for this query */
     262           0 :       if( FD_LIKELY( current_epoch>=target_epoch || current_epoch>=self->deactivation_epoch ) ) break;
     263             : 
     264             :       /* Advance to the next epoch if we have history,
     265             :          otherwise we can't model further warmup */
     266           0 :       fd_stake_history_entry_t const * current_cluster_stake =
     267           0 :           fd_sysvar_stake_history_query( history, current_epoch );
     268           0 :       if( FD_LIKELY( current_cluster_stake ) ) {
     269           0 :         prev_epoch         = current_epoch;
     270           0 :         prev_cluster_stake = current_cluster_stake;
     271           0 :       } else {
     272           0 :         break;
     273           0 :       }
     274           0 :     }
     275             : 
     276           0 :     return ( effective_activating_t ){ .effective  = activated_stake_amount,
     277           0 :                                        .activating = delegated_stake - activated_stake_amount };
     278           9 :   } else {
     279           9 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
     280           9 :   }
     281         168 : }
     282             : 
     283             : /* Fixed-point version of stake_activating_and_deactivating.
     284             :    Mirrors exactly the logic in the on-chain stake program:
     285             :    https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L790-L879 */
     286             : static fd_stake_history_entry_t
     287             : stake_activating_and_deactivating_v2( fd_delegation_t const *    self,
     288             :                                       ulong                      target_epoch,
     289             :                                       fd_stake_history_t const * history,
     290         168 :                                       ulong *                    new_rate_activation_epoch ) {
     291         168 :   effective_activating_t effective_activating =
     292         168 :       stake_and_activating_v2( self, target_epoch, history, new_rate_activation_epoch );
     293         168 :   ulong effective_stake  = effective_activating.effective;
     294         168 :   ulong activating_stake = effective_activating.activating;
     295             : 
     296         168 :   fd_stake_history_entry_t const * prev_cluster_stake = NULL;
     297             : 
     298         168 :   if( target_epoch<self->deactivation_epoch ) {
     299         111 :     if( activating_stake==0UL ) {
     300         111 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = 0UL };
     301         111 :     } else {
     302           0 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = activating_stake, .deactivating = 0UL };
     303           0 :     }
     304         111 :   } else if( target_epoch==self->deactivation_epoch ) {
     305           0 :     return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = effective_stake };
     306          57 :   } else if( history &&
     307          57 :              ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->deactivation_epoch ) ) ) {
     308           0 :     ulong prev_epoch = self->deactivation_epoch;
     309             : 
     310             :     /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L830-L871 */
     311           0 :     ulong current_epoch;
     312           0 :     ulong remaining_deactivating_stake = effective_stake;
     313           0 :     for(;;) {
     314           0 :       current_epoch = prev_epoch + 1UL;
     315             : 
     316             :       /* If there is no deactivating stake at prev epoch, we should
     317             :          have been fully undelegated at this moment */
     318           0 :       if( FD_LIKELY( prev_cluster_stake->deactivating==0UL ) ) break;
     319             : 
     320             :       /* Compute how much of this account's stake cools down in
     321             :          current_epoch */
     322           0 :       ulong newly_deactivated_stake = fd_stake_calculate_deactivation_allowance( current_epoch,
     323           0 :                                                                                  remaining_deactivating_stake,
     324           0 :                                                                                  prev_cluster_stake,
     325           0 :                                                                                  new_rate_activation_epoch );
     326             : 
     327             :       /* Subtract the newly deactivated stake, clamping the per-epoch
     328             :          decrease to at least 1 lamport so cooldown always makes
     329             :          progress */
     330           0 :       remaining_deactivating_stake =
     331           0 :           fd_ulong_sat_sub( remaining_deactivating_stake, fd_ulong_max( newly_deactivated_stake, 1UL ) );
     332             : 
     333             :       /* Stop if we've fully cooled down this account */
     334           0 :       if( remaining_deactivating_stake==0UL ) break;
     335             : 
     336             :       /* Stop when we've reached the time bound for this query */
     337           0 :       if( current_epoch>=target_epoch ) break;
     338             : 
     339             :       /* Advance to the next epoch if we have history,
     340             :          otherwise we can't model further cooldown */
     341           0 :       fd_stake_history_entry_t const * current_cluster_stake =
     342           0 :           fd_sysvar_stake_history_query( history, current_epoch );
     343           0 :       if( FD_LIKELY( current_cluster_stake ) ) {
     344           0 :         prev_epoch         = current_epoch;
     345           0 :         prev_cluster_stake = current_cluster_stake;
     346           0 :       } else {
     347           0 :         break;
     348           0 :       }
     349           0 :     }
     350             : 
     351           0 :     return ( fd_stake_history_entry_t ){ .effective    = remaining_deactivating_stake,
     352           0 :                                          .activating    = 0UL,
     353           0 :                                          .deactivating = remaining_deactivating_stake };
     354          57 :   } else {
     355          57 :     return ( fd_stake_history_entry_t ){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
     356          57 :   }
     357         168 : }
     358             : 
     359             : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stake_delegation.rs#L27-L41 */
     360             : fd_stake_history_entry_t
     361             : fd_delegation_activation_status( fd_delegation_t const *    self,
     362             :                                  ulong                      target_epoch,
     363             :                                  fd_stake_history_t const * stake_history,
     364             :                                  ulong *                    new_rate_activation_epoch,
     365        1116 :                                  int                        use_fixed_point_stake_math ) {
     366        1116 :   if( use_fixed_point_stake_math ) {
     367         168 :     return stake_activating_and_deactivating_v2( self, target_epoch, stake_history, new_rate_activation_epoch );
     368         948 :   } else {
     369         948 :     return stake_activating_and_deactivating( self, target_epoch, stake_history, new_rate_activation_epoch );
     370         948 :   }
     371        1116 : }
     372             : 
     373             : /**********************************************************************/
     374             : /* Public API                                                         */
     375             : /**********************************************************************/
     376             : 
     377             : fd_stake_state_t const *
     378             : fd_stake_state_view( uchar const * data,
     379         294 :                      ulong         data_sz ) {
     380         294 :   if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
     381         294 :   uint stake_type = FD_LOAD( uint, data );
     382         294 :   switch( stake_type ) {
     383           0 :   case FD_STAKE_STATE_UNINITIALIZED:
     384           0 :     break;
     385           3 :   case FD_STAKE_STATE_INITIALIZED:
     386           3 :     if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
     387           3 :     break;
     388         291 :   case FD_STAKE_STATE_STAKE:
     389         291 :     if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
     390         291 :     break;
     391         291 :   case FD_STAKE_STATE_REWARDS_POOL:
     392           0 :     break;
     393           0 :   default:
     394           0 :     return NULL;
     395         294 :   }
     396         294 :   return fd_type_pun_const( data );
     397         294 : }
     398             : 
     399             : fd_stake_state_t const *
     400         210 : fd_stakes_get_state( fd_acc_t const * acc ) {
     401         210 :   if( FD_UNLIKELY( memcmp( acc->owner, &fd_solana_stake_program_id, 32UL ) ) ) return NULL;
     402         201 :   if( FD_UNLIKELY( acc->lamports==0UL ) ) return NULL;
     403         201 :   return fd_stake_state_view( acc->data, acc->data_len );
     404         201 : }
     405             : 
     406             : fd_stake_history_entry_t
     407             : fd_stakes_activating_and_deactivating( fd_stake_delegation_t const * stake_delegation,
     408             :                                        ulong                         target_epoch,
     409             :                                        fd_stake_history_t const *    stake_history,
     410             :                                        ulong *                       new_rate_activation_epoch,
     411        1116 :                                        int                           use_fixed_point_stake_math ) {
     412        1116 :   fd_delegation_t delegation = {
     413        1116 :     .voter_pubkey         = stake_delegation->vote_account,
     414        1116 :     .stake                = stake_delegation->stake,
     415        1116 :     .deactivation_epoch   = stake_delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->deactivation_epoch,
     416        1116 :     .activation_epoch     = stake_delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->activation_epoch,
     417        1116 :     .warmup_cooldown_rate = fd_stake_delegations_warmup_cooldown_rate_to_double( stake_delegation->warmup_cooldown_rate ),
     418        1116 :   };
     419             : 
     420        1116 :   return fd_delegation_activation_status( &delegation, target_epoch, stake_history, new_rate_activation_epoch, use_fixed_point_stake_math );
     421        1116 : }
     422             : 
     423             : ulong
     424             : fd_stake_weights_by_node( fd_vote_stakes_t const * vote_stakes,
     425             :                           ulong                    fork_id,
     426             :                           int                      use_t_1,
     427         264 :                           fd_vote_stake_weight_t * weights ) {
     428             : 
     429             :   /* We don't care if an account is invalid, we just want to get the
     430             :      stake weights: they are calculated from an older snapshot of
     431             :      vote account stakes. */
     432         264 :   ulong weights_cnt = 0;
     433         264 :   uchar __attribute__((aligned(FD_VOTE_STAKES_T_1_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_T_1_ITER_FOOTPRINT ];
     434         264 :   if( use_t_1 ) {
     435           0 :     for( fd_vote_stakes_t_1_iter_t * iter = fd_vote_stakes_t_1_iter_init( vote_stakes, fork_id, iter_mem );
     436           0 :          !fd_vote_stakes_t_1_iter_done( vote_stakes, fork_id, iter );
     437           0 :          fd_vote_stakes_t_1_iter_next( vote_stakes, fork_id, iter ) ) {
     438           0 :       fd_pubkey_t pubkey;
     439           0 :       ulong       stake;
     440           0 :       fd_pubkey_t node_account;
     441           0 :       fd_vote_stakes_t_1_iter_ele( vote_stakes, fork_id, iter, &pubkey, &node_account, &stake, NULL );
     442             : 
     443           0 :       FD_TEST( weights_cnt<MAX_STAKE_WEIGHTS );
     444           0 :       fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
     445           0 :       fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account, sizeof(fd_pubkey_t) );
     446           0 :       weights[ weights_cnt ].stake = stake;
     447           0 :       weights_cnt++;
     448           0 :     }
     449         264 :   } else {
     450         264 :     for( fd_vote_stakes_t_2_iter_t * iter = fd_vote_stakes_t_2_iter_init( vote_stakes, fork_id, iter_mem );
     451         588 :          !fd_vote_stakes_t_2_iter_done( vote_stakes, fork_id, iter );
     452         324 :          fd_vote_stakes_t_2_iter_next( vote_stakes, fork_id, iter ) ) {
     453         324 :       fd_pubkey_t pubkey;
     454         324 :       ulong       stake;
     455         324 :       fd_pubkey_t node_account;
     456         324 :       fd_vote_stakes_t_2_iter_ele( vote_stakes, fork_id, iter, &pubkey, &node_account, &stake, NULL, NULL, NULL, NULL );
     457             : 
     458         324 :       FD_TEST( weights_cnt<MAX_STAKE_WEIGHTS );
     459         324 :       fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
     460         324 :       fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account, sizeof(fd_pubkey_t) );
     461         324 :       weights[ weights_cnt ].stake = stake;
     462         324 :       weights_cnt++;
     463         324 :     }
     464         264 :   }
     465             : 
     466         264 :   sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
     467             : 
     468             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
     469             :      We do not deduplicate the weights here, unlike Agave, as it is
     470             :      guaranteed there will be no duplicate stake entries for a given fork
     471             :      in the stakes map. */
     472             : 
     473         264 :   return weights_cnt;
     474         264 : }
     475             : 
     476             : static void
     477             : get_vote_credits( uchar const *        account_data,
     478             :                   ulong                account_data_len,
     479             :                   ushort               commission,
     480         321 :                   fd_epoch_credits_t * epoch_credits ) {
     481             : 
     482         321 :   ulong                           cnt                = 0UL;
     483         321 :   fd_vote_epoch_credits_t const * vote_epoch_credits = fd_vote_account_epoch_credits( account_data, account_data_len, &cnt );
     484         321 :   FD_TEST( vote_epoch_credits );
     485         321 :   FD_TEST( cnt<=FD_EPOCH_CREDITS_MAX );
     486         321 :   epoch_credits->cnt        = (uchar)cnt;
     487         321 :   epoch_credits->commission = commission;
     488             : 
     489         321 :   ulong base = cnt ? vote_epoch_credits[0].prev_credits : 0UL;
     490         417 :   for( ulong i=0UL; i<cnt; i++ ) {
     491          96 :     fd_vote_epoch_credits_t const * ele = &vote_epoch_credits[ i ];
     492             : 
     493          96 :     FD_TEST( ele->credits-base<=UINT_MAX );      /* Final delta should fit. */
     494          96 :     FD_TEST( ele->prev_credits-base<=UINT_MAX ); /* Initial delta should fit. */
     495          96 :     FD_TEST( ele->epoch<=USHORT_MAX );           /* Epoch should fit. */
     496             : 
     497          96 :     epoch_credits->epoch[ i ]              = (ushort)ele->epoch;
     498          96 :     epoch_credits->credits_delta[ i ]      = (uint)( ele->credits      - base );
     499          96 :     epoch_credits->prev_credits_delta[ i ] = (uint)( ele->prev_credits - base );
     500          96 :   }
     501             : 
     502         321 :   epoch_credits->base_credits = base;
     503         321 :   epoch_credits->fast_path_ok = fd_epoch_credits_fast_path_ok( epoch_credits );
     504         321 : }
     505             : 
     506             : void
     507             : fd_refresh_vote_accounts( fd_bank_t *                    bank,
     508             :                           fd_accdb_t *                   accdb,
     509             :                           fd_runtime_stack_t *           runtime_stack,
     510             :                           fd_stake_delegations_t const * stake_delegations,
     511             :                           fd_stake_history_t const *     history,
     512         264 :                           ulong *                        new_rate_activation_epoch ) {
     513         264 :   fd_bank_epoch_credits_new_fork( bank );
     514             : 
     515         264 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     516         264 :   ulong              fork_id     = bank->vote_stakes_fork_id;
     517             : 
     518         264 :   fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
     519         264 :   ulong epoch                      = bank->f.epoch;
     520         264 :   ulong total_stake                = 0UL;
     521         264 :   ulong total_activating           = 0UL;
     522         264 :   ulong total_deactivating         = 0UL;
     523         264 :   ulong staked_accounts            = 0UL;
     524         264 :   int   use_fixed_point_stake_math = FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 );
     525             : 
     526         264 :   fd_stake_accum_t *     stake_accum_pool = runtime_stack->stakes.stake_accum;
     527         264 :   fd_stake_accum_map_t * stake_accum_map  = runtime_stack->stakes.stake_accum_map;
     528             : 
     529             :   /* Accumulate stakes across all delegations for all vote accounts. */
     530         264 :   fd_stake_delegations_iter_t iter_[1];
     531         264 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, epoch, new_rate_activation_epoch );
     532         597 :       !fd_stake_delegations_iter_done( iter );
     533         333 :       fd_stake_delegations_iter_next( iter ) ) {
     534             : 
     535         333 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     536             : 
     537         333 :     fd_stake_history_entry_t new_acc;
     538         333 :     uchar st = stake_delegation->state;
     539         333 :     if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
     540          66 :       new_acc = (fd_stake_history_entry_t){ .effective = stake_delegation->stake, .activating = 0UL, .deactivating = 0UL };
     541         267 :     } else if( st==FD_STAKE_DELEGATION_STATE_COOLED ) {
     542           0 :       new_acc = (fd_stake_history_entry_t){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
     543         267 :     } else {
     544         267 :       new_acc = fd_stakes_activating_and_deactivating( stake_delegation, epoch, history, new_rate_activation_epoch, use_fixed_point_stake_math );
     545         267 :     }
     546         333 :     total_stake        += new_acc.effective;
     547         333 :     total_activating   += new_acc.activating;
     548         333 :     total_deactivating += new_acc.deactivating;
     549         333 :     if( FD_UNLIKELY( !new_acc.effective ) ) continue;
     550             : 
     551         330 :     fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
     552         330 :     if( FD_UNLIKELY( !stake_accum ) ) {
     553         330 :       if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_staked_vote_accounts ) ) {
     554           0 :         FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
     555           0 :       }
     556         330 :       stake_accum = &runtime_stack->stakes.stake_accum[ staked_accounts ];
     557         330 :       stake_accum->pubkey = stake_delegation->vote_account;
     558         330 :       stake_accum->stake  = new_acc.effective;
     559         330 :       fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
     560         330 :       staked_accounts++;
     561         330 :     } else {
     562           0 :       stake_accum->stake += new_acc.effective;
     563           0 :     }
     564         330 :   }
     565             : 
     566             :   /* Only update total_*_stake at the epoch boundary.  These values
     567             :      are snapshots of the stake totals for the current epoch. */
     568         264 :   bank->f.total_activating_stake   = total_activating;
     569         264 :   bank->f.total_deactivating_stake = total_deactivating;
     570         264 :   bank->f.total_effective_stake    = total_stake;
     571             : 
     572             :   /* Iterate over the valid delegated vote accounts and insert them into
     573             :      the top votes set for the t-1 epoch. */
     574             : 
     575             :   /* Rotate the SIMD-0232 collector override fork for the new epoch. */
     576         264 :   fd_collector_overrides_t * overrides = fd_bank_collector_overrides( bank );
     577         264 :   ushort co_child = fd_collector_overrides_new_child( overrides );
     578         264 :   fd_collector_overrides_inherit( overrides, bank->collector_overrides_fork_id, co_child, fd_ulong_sat_sub( bank->f.epoch, 1UL ) );
     579         264 :   bank->collector_overrides_fork_id = co_child;
     580             : 
     581         264 :   for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
     582         594 :        !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
     583         330 :        iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
     584         330 :     fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
     585             : 
     586         330 :     fd_pubkey_t node_account_t_1 = {0};
     587         330 :     ulong       stake_t_1        = stake_accum->stake;
     588         330 :     ushort      commission_t_1   = 0;
     589             : 
     590         330 :     if( FD_UNLIKELY( !stake_t_1 ) ) continue;
     591             : 
     592         330 :     fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
     593             :     /* Agave's VAT filter also checks lamports against the VoteStateV4
     594             :        rent-exempt minimum. */
     595         330 :     if( FD_UNLIKELY( !acc.lamports ) ) {
     596           0 :       fd_accdb_unread_one( accdb, &acc );
     597           0 :       continue;
     598           0 :     }
     599             : 
     600         330 :     ulong vote_account_lamports            = acc.lamports;
     601         330 :     ulong vote_account_rent_exempt_minimum = fd_rent_exempt_minimum_balance( &bank->f.rent, FD_VOTE_STATE_V4_SZ );
     602         330 :     if( FD_UNLIKELY( vote_account_lamports < vote_account_rent_exempt_minimum ) ) {
     603           0 :       fd_accdb_unread_one( accdb, &acc );
     604           0 :       continue;
     605           0 :     }
     606         330 :     if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( acc.owner, acc.data, acc.data_len ) ||
     607         330 :                      !fd_vote_account_is_v4_with_bls_pubkey( acc.data, acc.data_len ) ) ) {
     608           9 :       fd_accdb_unread_one( accdb, &acc );
     609           9 :       continue;
     610           9 :     }
     611             : 
     612         321 :     FD_TEST( !fd_vote_account_commission_bps( acc.data, acc.data_len, FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ), &commission_t_1 ) );
     613         321 :     FD_TEST( !fd_vote_account_node_pubkey( acc.data, acc.data_len, &node_account_t_1 ) );
     614             : 
     615         321 :     fd_vote_stakes_insert( vote_stakes, fork_id, &stake_accum->pubkey, &node_account_t_1, stake_t_1, commission_t_1 );
     616         321 :     fd_accdb_unread_one( accdb, &acc );
     617         321 :   }
     618             : 
     619             :   /* Capture SIMD-0232 collector overrides for the admitted t-1 set.
     620             :      Only admitted vote accounts can be scheduled as leaders or earn
     621             :      inflation rewards, so collectors of accounts outside the set are
     622             :      never consulted.  Capturing after selection bounds the override
     623             :      store by the admitted set size. */
     624         264 :   {
     625         264 :     uchar __attribute__((aligned(FD_VOTE_STAKES_T_1_ITER_ALIGN))) co_iter_mem[ FD_VOTE_STAKES_T_1_ITER_FOOTPRINT ];
     626         264 :     for( fd_vote_stakes_t_1_iter_t * iter = fd_vote_stakes_t_1_iter_init( vote_stakes, fork_id, co_iter_mem );
     627         585 :          !fd_vote_stakes_t_1_iter_done( vote_stakes, fork_id, iter );
     628         321 :          fd_vote_stakes_t_1_iter_next( vote_stakes, fork_id, iter ) ) {
     629         321 :       fd_pubkey_t vote_pubkey;
     630         321 :       fd_pubkey_t node_pubkey;
     631         321 :       fd_vote_stakes_t_1_iter_ele( vote_stakes, fork_id, iter, &vote_pubkey, &node_pubkey, NULL, NULL );
     632             : 
     633         321 :       fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, vote_pubkey.uc );
     634         321 :       fd_pubkey_t inflation_collector;
     635         321 :       fd_pubkey_t block_collector;
     636         321 :       FD_TEST( !fd_vote_account_collectors( acc.data, acc.data_len, &vote_pubkey, &node_pubkey, &inflation_collector, &block_collector ) );
     637         321 :       int has_inflation = !fd_pubkey_eq( &inflation_collector, &vote_pubkey );
     638         321 :       int has_block     = !fd_pubkey_eq( &block_collector, &node_pubkey );
     639         321 :       if( FD_UNLIKELY( has_inflation | has_block ) ) {
     640          54 :         fd_collector_overrides_upsert( overrides, co_child, bank->f.epoch, &vote_pubkey,
     641          54 :                                        has_inflation, &inflation_collector,
     642          54 :                                        has_block, &block_collector );
     643          54 :       }
     644         321 :       fd_accdb_unread_one( accdb, &acc );
     645         321 :     }
     646         264 :   }
     647             : 
     648             :   /* Seed status for the t-2 top votes set for clock calculation. */
     649         264 :   fd_vote_stakes_refresh( vote_stakes, fork_id, accdb, bank->accdb_fork_id );
     650             : 
     651             :   /* Populate the vote rewards map with the final set of filtered vote
     652             :      accounts. */
     653         264 :   fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
     654         264 :   fd_vote_rewards_map_reset( vote_reward_map );
     655         264 :   ulong vote_reward_cnt = 0UL;
     656             : 
     657             :   /* Populate the vote rewards map with the final set of filtered vote
     658             :      accounts for the t-1 epoch. */
     659         264 :   bank->f.total_epoch_stake = 0UL;
     660         264 :   uchar __attribute__((aligned(FD_VOTE_STAKES_T_1_ITER_ALIGN))) t_1_iter_mem[ FD_VOTE_STAKES_T_1_ITER_FOOTPRINT ];
     661         264 :   for( fd_vote_stakes_t_1_iter_t * iter = fd_vote_stakes_t_1_iter_init( vote_stakes, fork_id, t_1_iter_mem );
     662         585 :        !fd_vote_stakes_t_1_iter_done( vote_stakes, fork_id, iter );
     663         321 :        fd_vote_stakes_t_1_iter_next( vote_stakes, fork_id, iter ) ) {
     664         321 :     fd_pubkey_t pubkey;
     665         321 :     ulong       stake;
     666         321 :     ushort      commission_t_1 = 0;
     667         321 :     fd_vote_stakes_t_1_iter_ele( vote_stakes, fork_id, iter, &pubkey, NULL, &stake, &commission_t_1 );
     668             : 
     669         321 :     ushort commission_t_3 = 0;
     670         321 :     int    exists_t_3     = fd_vote_stakes_query_t_3( vote_stakes, fork_id, &pubkey, NULL, NULL, &commission_t_3 );
     671             : 
     672         321 :     ushort commission_t_2 = 0;
     673         321 :     int    exists_t_2     = fd_vote_stakes_query_t_2( vote_stakes, fork_id, &pubkey, NULL, NULL, NULL, NULL, &commission_t_2, NULL );
     674             : 
     675         321 :     fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
     676         321 :     vote_ele->pubkey             = pubkey;
     677         321 :     vote_ele->vote_rewards       = 0UL;
     678         321 :     if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
     679          72 :       vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : commission_t_1);
     680         249 :     } else {
     681         249 :       vote_ele->commission = commission_t_1;
     682         249 :     }
     683             : 
     684         321 :     fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, pubkey.uc );
     685         321 :     FD_TEST( acc.lamports );
     686             : 
     687         321 :     if( FD_UNLIKELY( vote_reward_cnt>=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
     688           0 :       FD_LOG_ERR(( "invariant violation: vote_reward_cnt >= epoch credits max" ));
     689           0 :     }
     690         321 :     fd_epoch_credits_t * epoch_credits = &fd_bank_epoch_credits( bank )[ vote_reward_cnt ];
     691         321 :     fd_memcpy( epoch_credits->pubkey, &pubkey, sizeof(fd_pubkey_t) );
     692         321 :     get_vote_credits( acc.data, acc.data_len, vote_ele->commission, epoch_credits );
     693         321 :     fd_accdb_unread_one( accdb, &acc );
     694             : 
     695         321 :     fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
     696         321 :     vote_reward_cnt++;
     697         321 :     bank->f.total_epoch_stake += stake;
     698         321 :   }
     699         264 :   *fd_bank_epoch_credits_len( bank ) = vote_reward_cnt;
     700         264 : }
     701             : 
     702             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
     703             : void
     704             : fd_stakes_activate_epoch( fd_bank_t *                    bank,
     705             :                           fd_runtime_stack_t *           runtime_stack,
     706             :                           fd_accdb_t *                   accdb,
     707             :                           fd_capture_ctx_t *             capture_ctx,
     708             :                           fd_stake_delegations_t *       stake_delegations,
     709         264 :                           ulong *                        new_rate_activation_epoch ) {
     710             :   /* We can update our stake history sysvar based on the bank stake values.
     711             :      Afterward, we can refresh the stake values for the vote accounts. */
     712             : 
     713         264 :   fd_stake_history_entry_t elem = {
     714         264 :     .epoch        = bank->f.epoch,
     715         264 :     .effective    = stake_delegations->effective_stake,
     716         264 :     .activating   = stake_delegations->activating_stake,
     717         264 :     .deactivating = stake_delegations->deactivating_stake,
     718         264 :   };
     719             : 
     720             :   /* Agave recomputes each stake history entry from scratch every epoch
     721             :      boundary, whereas Firedancer keeps running totals. Therefore,
     722             :      at the boundary where upgrade_bpf_stake_program_to_v5_1 is
     723             :      activated, we need to recompute the stake history entry for the
     724             :      epoch that has just ended, so that all the delegations for this
     725             :      entry are summed using the new fixed point arithmetic. We only
     726             :      need to do this once, at the feature activation epoch boundary.
     727             : 
     728             :      https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stakes.rs#L444-L477
     729             : 
     730             :      The same recomputation needs to be done as soon as fallback stake
     731             :      accounts are enabled. */
     732         264 :   int fallback = fd_stake_delegations_pubkey_fallback( stake_delegations );
     733         264 :   if( FD_UNLIKELY( fallback || FD_FEATURE_JUST_ACTIVATED_BANK( bank, upgrade_bpf_stake_program_to_v5_1 ) ) ) {
     734           0 :     fd_stake_history_t history[1];
     735           0 :     if( FD_UNLIKELY( !fd_sysvar_cache_stake_history_view( &bank->f.sysvar_cache, history ) ) ) {
     736           0 :       FD_LOG_CRIT(( "invariant violation: StakeHistory sysvar missing or invalid" ));
     737           0 :     }
     738           0 :     ulong effective    = 0UL;
     739           0 :     ulong activating   = 0UL;
     740           0 :     ulong deactivating = 0UL;
     741             : 
     742           0 :     int use_fixed_point_stake_math = FD_FEATURE_ACTIVE_BANK( bank, upgrade_bpf_stake_program_to_v5_1 );
     743             : 
     744           0 :     fd_stake_delegations_iter_t iter_[1];
     745           0 :     for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations, accdb, bank->accdb_fork_id, bank->f.epoch, new_rate_activation_epoch );
     746           0 :          !fd_stake_delegations_iter_done( iter );
     747           0 :          fd_stake_delegations_iter_next( iter ) ) {
     748           0 :       fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     749           0 :       fd_stake_history_entry_t      acc              = fd_stakes_activating_and_deactivating(
     750           0 :           stake_delegation, bank->f.epoch, history, new_rate_activation_epoch, use_fixed_point_stake_math );
     751           0 :       effective    += acc.effective;
     752           0 :       activating   += acc.activating;
     753           0 :       deactivating += acc.deactivating;
     754           0 :     }
     755             : 
     756           0 :     elem.effective    = effective;
     757           0 :     elem.activating   = activating;
     758           0 :     elem.deactivating = deactivating;
     759           0 :   }
     760             : 
     761         264 :   fd_sysvar_stake_history_update( bank, accdb, capture_ctx, &elem );
     762             : 
     763             :   /* Snapshot the stake history sysvar into a local buffer and release
     764             :      the accdb bracket before calling fd_refresh_vote_accounts, which
     765             :      performs its own accdb acquires.  fd_sysvar_stake_history_view
     766             :      aliases the source bytes, so the bracket cannot be held open across
     767             :      an inner acquire. */
     768         264 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
     769         264 :   fd_stake_history_t stake_history[1];
     770         264 :   {
     771         264 :     fd_acc_t ro = fd_accdb_read_one( accdb, bank->accdb_fork_id, fd_sysvar_stake_history_id.uc );
     772         264 :     if( FD_UNLIKELY( !ro.lamports ) ) FD_LOG_ERR(( "StakeHistory sysvar is missing" ));
     773         264 :     ulong copy_sz = fd_ulong_min( ro.data_len, FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ );
     774         264 :     fd_memcpy( stake_history_data, ro.data, copy_sz );
     775         264 :     fd_accdb_unread_one( accdb, &ro );
     776         264 :     if( FD_UNLIKELY( !fd_sysvar_stake_history_view( stake_history, stake_history_data, copy_sz ) ) ) {
     777           0 :       FD_LOG_HEXDUMP_ERR(( "Invalid StakeHistory sysvar", stake_history_data, copy_sz ));
     778           0 :     }
     779         264 :   }
     780             : 
     781         264 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_is_contiguous( stake_history ) ) ) {
     782           0 :     fd_stake_delegations_invalidate_warmed( stake_delegations );
     783           0 :   }
     784             : 
     785             :   /* Now increment the epoch and recompute the stakes for the vote
     786             :      accounts for the new epoch value. */
     787             : 
     788         264 :   bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.slot, NULL );
     789             : 
     790         264 :   fd_refresh_vote_accounts( bank,
     791         264 :                             accdb,
     792         264 :                             runtime_stack,
     793         264 :                             stake_delegations,
     794         264 :                             stake_history,
     795         264 :                             new_rate_activation_epoch );
     796         264 : }
     797             : 
     798             : 
     799             : void
     800             : fd_stakes_update_stake_delegation( fd_pubkey_t const * pubkey,
     801             :                                    fd_acc_t const *    acc,
     802          15 :                                    fd_bank_t *         bank ) {
     803             : 
     804          15 :   fd_stake_state_t const * stake_state       = fd_stakes_get_state( acc );
     805          15 :   fd_stake_state_t const * prior_stake_state = NULL;
     806          15 :   if( FD_LIKELY( acc->prior_lamports && !memcmp( acc->prior_owner, &fd_solana_stake_program_id, 32UL ) ) ) {
     807          15 :     prior_stake_state = fd_stake_state_view( acc->prior_data, acc->prior_data_len );
     808          15 :   }
     809             : 
     810          15 :   int current_has_delegation = stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE;
     811          15 :   int prior_has_delegation   = prior_stake_state && prior_stake_state->stake_type==FD_STAKE_STATE_STAKE;
     812             : 
     813             :   /* If the current stake state isn't a delegation and it was a
     814             :      delegation in the previous stake state, insert a tombstone into the
     815             :      stake delegation's fork. */
     816          15 :   if( FD_UNLIKELY( !current_has_delegation ) ) {
     817           9 :     if( FD_LIKELY( !prior_has_delegation ) ) return; /* nothing to remove from */
     818           6 :     fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
     819           6 :     fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, pubkey );
     820           6 :     return;
     821           9 :   }
     822             : 
     823             :   /* Agave replaces the cached version of the account whenever the
     824             :      account changes. */
     825             : 
     826           6 :   int account_changed = acc->prior_lamports  !=acc->lamports   ||
     827           6 :                         acc->prior_executable!=acc->executable ||
     828           6 :                         acc->prior_data_len  !=acc->data_len   ||
     829           6 :                         memcmp( acc->prior_owner, acc->owner, sizeof(fd_pubkey_t) );
     830           6 :   if( FD_LIKELY( !account_changed && acc->data_len ) ) {
     831           3 :     account_changed = !!memcmp( acc->prior_data, acc->data, acc->data_len );
     832           3 :   }
     833           6 :   if( FD_LIKELY( prior_has_delegation && !account_changed ) ) return;
     834             : 
     835           6 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
     836           6 :   ulong new_stake = stake_state->stake.stake.delegation.stake;
     837           6 :   fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, pubkey,
     838           6 :                                     &stake_state->stake.stake.delegation.voter_pubkey,
     839           6 :                                     new_stake,
     840           6 :                                     stake_state->stake.stake.delegation.activation_epoch,
     841           6 :                                     stake_state->stake.stake.delegation.deactivation_epoch,
     842           6 :                                     stake_state->stake.stake.credits_observed,
     843           6 :                                     acc->lamports,
     844           6 :                                     (uint)acc->data_len,
     845           6 :                                     fd_stake_warmup_cooldown_rate( bank->f.epoch, &bank->f.warmup_cooldown_rate_epoch ) );
     846           6 : }

Generated by: LCOV version 1.14