LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 488 626 78.0 %
Date: 2026-09-17 04:28:31 Functions: 21 30 70.0 %

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

Generated by: LCOV version 1.14