LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 518 608 85.2 %
Date: 2026-10-22 18:10:43 Functions: 20 31 64.5 %

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

Generated by: LCOV version 1.14