LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 400 477 83.9 %
Date: 2026-11-11 12:40:43 Functions: 18 28 64.3 %

          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        7843 : #define FD_BASIS_POINTS_PER_UNIT             (10000UL)
      21           0 : #define FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS (2500UL)
      22        7843 : #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/warmup_cooldown_allowance.rs#L7-L14 */
      39             : static ulong
      40        7843 : fd_stake_warmup_cooldown_rate_bps( ulong current_epoch, ulong * opt_rate_change_activation_epoch ) {
      41        7843 :   if( opt_rate_change_activation_epoch && current_epoch>=*opt_rate_change_activation_epoch ) {
      42        7843 :     return FD_TOWER_WARMUP_COOLDOWN_RATE_BPS;
      43        7843 :   } else {
      44           0 :     return FD_ORIGINAL_WARMUP_COOLDOWN_RATE_BPS;
      45           0 :   }
      46        7843 : }
      47             : 
      48             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L54-L93 */
      49             : static ulong
      50             : calculate_stake_change_allowance( ulong   epoch,
      51             :                                   ulong   account_portion,
      52             :                                   ulong   cluster_portion,
      53             :                                   ulong   cluster_effective,
      54        7846 :                                   ulong * opt_rate_change_activation_epoch ) {
      55        7846 :   if( account_portion==0UL || cluster_portion==0UL || cluster_effective==0UL ) {
      56           3 :     return 0UL;
      57           3 :   }
      58             : 
      59        7843 :   ulong rate_bps = fd_stake_warmup_cooldown_rate_bps( epoch, opt_rate_change_activation_epoch );
      60             : 
      61        7843 :   uint128 numerator   = fd_uint128_sat_mul( fd_uint128_sat_mul( (uint128)account_portion, (uint128)cluster_effective ),
      62        7843 :                                             (uint128)rate_bps );
      63        7843 :   uint128 denominator = fd_uint128_sat_mul( (uint128)cluster_portion, (uint128)FD_BASIS_POINTS_PER_UNIT );
      64             : 
      65             :   /* denominator is never zero due to guard above */
      66        7843 :   uint128 delta = numerator / denominator;
      67        7843 :   uint128 cap   = (uint128)account_portion;
      68        7843 :   return (ulong)( delta<cap ? delta : cap );
      69        7846 : }
      70             : 
      71             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L16-L33 */
      72             : ulong
      73             : fd_stake_calculate_activation_allowance( ulong                            current_epoch,
      74             :                                          ulong                            account_activating_stake,
      75             :                                          fd_stake_history_entry_t const * prev_epoch_cluster_state,
      76        6805 :                                          ulong *                          opt_rate_change_activation_epoch ) {
      77        6805 :   return calculate_stake_change_allowance( current_epoch,
      78        6805 :                                            account_activating_stake,
      79        6805 :                                            prev_epoch_cluster_state->activating,
      80        6805 :                                            prev_epoch_cluster_state->effective,
      81        6805 :                                            opt_rate_change_activation_epoch );
      82        6805 : }
      83             : 
      84             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/warmup_cooldown_allowance.rs#L35-L52 */
      85             : static ulong
      86             : fd_stake_calculate_deactivation_allowance( ulong                            current_epoch,
      87             :                                            ulong                            account_deactivating_stake,
      88             :                                            fd_stake_history_entry_t const * prev_epoch_cluster_state,
      89        1041 :                                            ulong *                          opt_rate_change_activation_epoch ) {
      90        1041 :   return calculate_stake_change_allowance( current_epoch,
      91        1041 :                                            account_deactivating_stake,
      92        1041 :                                            prev_epoch_cluster_state->deactivating,
      93        1041 :                                            prev_epoch_cluster_state->effective,
      94        1041 :                                            opt_rate_change_activation_epoch );
      95        1041 : }
      96             : 
      97             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L881-L971 */
      98             : static effective_activating_t
      99             : stake_and_activating( fd_delegation_t const *    self,
     100             :                       ulong                      target_epoch,
     101             :                       fd_stake_history_t const * history,
     102        1886 :                       ulong *                    new_rate_activation_epoch ) {
     103        1886 :   ulong delegated_stake = self->stake;
     104             : 
     105        1886 :   fd_stake_history_entry_t const * prev_cluster_stake = NULL;
     106             : 
     107        1886 :   if( self->activation_epoch==ULONG_MAX ) {
     108         309 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
     109        1577 :   } else if( self->activation_epoch==self->deactivation_epoch ) {
     110           0 :     return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
     111        1577 :   } else if( target_epoch==self->activation_epoch ) {
     112          24 :     return ( effective_activating_t ){ .effective = 0UL, .activating = delegated_stake };
     113        1553 :   } else if( target_epoch<self->activation_epoch ) {
     114         164 :     return ( effective_activating_t ){ .effective = 0UL, .activating = 0UL };
     115        1389 :   } else if( history &&
     116        1389 :              ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->activation_epoch ) ) ) {
     117             : 
     118         324 :     ulong prev_epoch = self->activation_epoch;
     119             : 
     120         324 :     ulong current_epoch;
     121         324 :     ulong activated_stake_amount = 0UL;
     122        6805 :     for(;;) {
     123        6805 :       current_epoch = prev_epoch + 1UL;
     124             : 
     125             :       /* If there is no activating stake at prev epoch, we should have
     126             :          been fully effective at this moment */
     127        6805 :       if( FD_LIKELY( prev_cluster_stake->activating==0UL ) ) break;
     128             : 
     129             :       /* Calculate how much of this account's remaining stake becomes
     130             :          effective in current_epoch. */
     131        6805 :       ulong remaining_activating_stake = delegated_stake - activated_stake_amount;
     132        6805 :       ulong newly_effective_stake      = fd_stake_calculate_activation_allowance( current_epoch,
     133        6805 :                                                                                   remaining_activating_stake,
     134        6805 :                                                                                   prev_cluster_stake,
     135        6805 :                                                                                   new_rate_activation_epoch );
     136             : 
     137             :       /* Add the newly effective stake, clamping the per-epoch increase
     138             :          to at least 1 lamport so warmup always makes progress */
     139        6805 :       activated_stake_amount += fd_ulong_max( newly_effective_stake, 1UL );
     140             : 
     141             :       /* Stop if we've fully warmed up this account's stake. */
     142        6805 :       if( FD_LIKELY( activated_stake_amount>=delegated_stake ) ) {
     143         247 :         activated_stake_amount = delegated_stake;
     144         247 :         break;
     145         247 :       }
     146             : 
     147             :       /* Stop when we've reached the time bound for this query */
     148        6558 :       if( FD_LIKELY( current_epoch>=target_epoch || current_epoch>=self->deactivation_epoch ) ) break;
     149             : 
     150             :       /* Advance to the next epoch if we have history,
     151             :          otherwise we can't model further warmup */
     152        6490 :       fd_stake_history_entry_t const * current_cluster_stake =
     153        6490 :           fd_sysvar_stake_history_query( history, current_epoch );
     154        6490 :       if( FD_LIKELY( current_cluster_stake ) ) {
     155        6481 :         prev_epoch         = current_epoch;
     156        6481 :         prev_cluster_stake = current_cluster_stake;
     157        6481 :       } else {
     158           9 :         break;
     159           9 :       }
     160        6490 :     }
     161             : 
     162         324 :     return ( effective_activating_t ){ .effective  = activated_stake_amount,
     163         324 :                                        .activating = delegated_stake - activated_stake_amount };
     164        1065 :   } else {
     165        1065 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0UL };
     166        1065 :   }
     167        1886 : }
     168             : 
     169             : /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L790-L879 */
     170             : static fd_stake_history_entry_t
     171             : stake_activating_and_deactivating( fd_delegation_t const *    self,
     172             :                                    ulong                      target_epoch,
     173             :                                    fd_stake_history_t const * history,
     174        1886 :                                    ulong *                    new_rate_activation_epoch ) {
     175        1886 :   effective_activating_t effective_activating =
     176        1886 :       stake_and_activating( self, target_epoch, history, new_rate_activation_epoch );
     177        1886 :   ulong effective_stake  = effective_activating.effective;
     178        1886 :   ulong activating_stake = effective_activating.activating;
     179             : 
     180        1886 :   fd_stake_history_entry_t const * prev_cluster_stake = NULL;
     181             : 
     182        1886 :   if( target_epoch<self->deactivation_epoch ) {
     183        1766 :     if( activating_stake==0UL ) {
     184        1685 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = 0UL };
     185        1685 :     } else {
     186          81 :       return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = activating_stake, .deactivating = 0UL };
     187          81 :     }
     188        1766 :   } else if( target_epoch==self->deactivation_epoch ) {
     189           4 :     return ( fd_stake_history_entry_t ){ .effective = effective_stake, .activating = 0UL, .deactivating = effective_stake };
     190         116 :   } else if( history &&
     191         116 :              ( prev_cluster_stake = fd_sysvar_stake_history_query( history, self->deactivation_epoch ) ) ) {
     192          84 :     ulong prev_epoch = self->deactivation_epoch;
     193             : 
     194             :     /* https://github.com/solana-program/stake/blob/interface@v4.3.0/interface/src/state.rs#L830-L871 */
     195          84 :     ulong current_epoch;
     196          84 :     ulong remaining_deactivating_stake = effective_stake;
     197        1041 :     for(;;) {
     198        1041 :       current_epoch = prev_epoch + 1UL;
     199             : 
     200             :       /* If there is no deactivating stake at prev epoch, we should
     201             :          have been fully undelegated at this moment */
     202        1041 :       if( FD_LIKELY( prev_cluster_stake->deactivating==0UL ) ) break;
     203             : 
     204             :       /* Compute how much of this account's stake cools down in
     205             :          current_epoch */
     206        1041 :       ulong newly_deactivated_stake = fd_stake_calculate_deactivation_allowance( current_epoch,
     207        1041 :                                                                                  remaining_deactivating_stake,
     208        1041 :                                                                                  prev_cluster_stake,
     209        1041 :                                                                                  new_rate_activation_epoch );
     210             : 
     211             :       /* Subtract the newly deactivated stake, clamping the per-epoch
     212             :          decrease to at least 1 lamport so cooldown always makes
     213             :          progress */
     214        1041 :       remaining_deactivating_stake =
     215        1041 :           fd_ulong_sat_sub( remaining_deactivating_stake, fd_ulong_max( newly_deactivated_stake, 1UL ) );
     216             : 
     217             :       /* Stop if we've fully cooled down this account */
     218        1041 :       if( remaining_deactivating_stake==0UL ) break;
     219             : 
     220             :       /* Stop when we've reached the time bound for this query */
     221         988 :       if( current_epoch>=target_epoch ) break;
     222             : 
     223             :       /* Advance to the next epoch if we have history,
     224             :          otherwise we can't model further cooldown */
     225         961 :       fd_stake_history_entry_t const * current_cluster_stake =
     226         961 :           fd_sysvar_stake_history_query( history, current_epoch );
     227         961 :       if( FD_LIKELY( current_cluster_stake ) ) {
     228         957 :         prev_epoch         = current_epoch;
     229         957 :         prev_cluster_stake = current_cluster_stake;
     230         957 :       } else {
     231           4 :         break;
     232           4 :       }
     233         961 :     }
     234             : 
     235          84 :     return ( fd_stake_history_entry_t ){ .effective    = remaining_deactivating_stake,
     236          84 :                                          .activating    = 0UL,
     237          84 :                                          .deactivating = remaining_deactivating_stake };
     238          84 :   } else {
     239          32 :     return ( fd_stake_history_entry_t ){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
     240          32 :   }
     241        1886 : }
     242             : 
     243             : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/runtime/src/stake_delegation.rs#L27-L41 */
     244             : fd_stake_history_entry_t
     245             : fd_delegation_activation_status( fd_delegation_t const *    self,
     246             :                                  ulong                      target_epoch,
     247             :                                  fd_stake_history_t const * stake_history,
     248        1886 :                                  ulong *                    new_rate_activation_epoch ) {
     249        1886 :   return stake_activating_and_deactivating( self, target_epoch, stake_history, new_rate_activation_epoch );
     250        1886 : }
     251             : 
     252             : int
     253             : fd_delegation_is_inactive( fd_delegation_t const *    delegation,
     254             :                            ulong                      target_epoch,
     255             :                            fd_stake_history_t const * stake_history,
     256           0 :                            ulong *                    new_rate_activation_epoch ) {
     257           0 :   fd_stake_history_entry_t status = fd_delegation_activation_status(
     258           0 :       delegation,
     259           0 :       target_epoch,
     260           0 :       stake_history,
     261           0 :       new_rate_activation_epoch );
     262           0 :   return !status.effective && !status.activating;
     263           0 : }
     264             : 
     265             : static fd_delegation_t
     266        1886 : fd_delegation_from_stake_delegation( fd_stake_delegation_t const * delegation ) {
     267        1886 :   return (fd_delegation_t) {
     268        1886 :     .voter_pubkey       = delegation->vote_account,
     269        1886 :     .stake              = delegation->stake,
     270        1886 :     .deactivation_epoch = delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : delegation->deactivation_epoch,
     271        1886 :     .activation_epoch   = delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : delegation->activation_epoch,
     272        1886 :   };
     273        1886 : }
     274             : 
     275             : /**********************************************************************/
     276             : /* Public API                                                         */
     277             : /**********************************************************************/
     278             : 
     279             : fd_stake_state_t const *
     280             : fd_stake_state_view( uchar const * data,
     281        1158 :                      ulong         data_sz ) {
     282        1158 :   if( FD_UNLIKELY( data_sz<4UL ) ) return NULL;
     283        1158 :   uint stake_type = FD_LOAD( uint, data );
     284        1158 :   switch( stake_type ) {
     285           4 :   case FD_STAKE_STATE_UNINITIALIZED:
     286           4 :     break;
     287           2 :   case FD_STAKE_STATE_INITIALIZED:
     288           2 :     if( FD_UNLIKELY( data_sz<124 ) ) return NULL;
     289           2 :     break;
     290        1152 :   case FD_STAKE_STATE_STAKE:
     291        1152 :     if( FD_UNLIKELY( data_sz<197 ) ) return NULL;
     292        1151 :     break;
     293        1151 :   case FD_STAKE_STATE_REWARDS_POOL:
     294           0 :     break;
     295           0 :   default:
     296           0 :     return NULL;
     297        1158 :   }
     298        1157 :   return fd_type_pun_const( data );
     299        1158 : }
     300             : 
     301             : fd_stake_state_t const *
     302         435 : fd_stakes_get_state( fd_acc_t const * acc ) {
     303         435 :   if( FD_UNLIKELY( memcmp( acc->owner, &fd_solana_stake_program_id, 32UL ) ) ) return NULL;
     304         136 :   if( FD_UNLIKELY( acc->lamports==0UL ) ) return NULL;
     305         136 :   return fd_stake_state_view( acc->data, acc->data_len );
     306         136 : }
     307             : 
     308             : fd_stake_history_entry_t
     309             : fd_stake_delegation_activation_status( fd_stake_delegation_t const * stake_delegation,
     310             :                                        ulong                         target_epoch,
     311             :                                        fd_stake_history_t const *    stake_history,
     312        1886 :                                        ulong *                       new_rate_activation_epoch ) {
     313        1886 :   fd_delegation_t delegation = fd_delegation_from_stake_delegation( stake_delegation );
     314        1886 :   return fd_delegation_activation_status( &delegation, target_epoch, stake_history, new_rate_activation_epoch );
     315        1886 : }
     316             : 
     317             : int
     318             : fd_stake_delegation_is_inactive( fd_stake_delegation_t const * delegation,
     319             :                                  ulong                         target_epoch,
     320             :                                  fd_stake_history_t const *    stake_history,
     321           0 :                                  ulong *                       new_rate_activation_epoch ) {
     322           0 :   fd_delegation_t raw_delegation = fd_delegation_from_stake_delegation( delegation );
     323           0 :   return fd_delegation_is_inactive( &raw_delegation,
     324           0 :                                     target_epoch,
     325           0 :                                     stake_history,
     326           0 :                                     new_rate_activation_epoch );
     327           0 : }
     328             : 
     329             : ulong
     330             : fd_stake_weights_by_node( fd_vote_stakes_t *       vote_stakes,
     331             :                           ushort                   fork_id,
     332             :                           int                      iter_kind,
     333         639 :                           fd_vote_stake_weight_t * weights ) {
     334             : 
     335             :   /* We don't care if an account is invalid, we just want to get the
     336             :      stake weights: they are calculated from an older snapshot of
     337             :      vote account stakes. */
     338         639 :   ulong weights_cnt = 0;
     339         639 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     340         639 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
     341        1794 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
     342        1155 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
     343        1155 :     fd_vote_stakes_ele_t ele[1];
     344        1155 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, ele );
     345             : 
     346        1155 :     FD_TEST( weights_cnt<MAX_STAKE_WEIGHTS );
     347        1155 :     fd_memcpy( weights[ weights_cnt ].vote_key.uc, &ele->pubkey, sizeof(fd_pubkey_t) );
     348        1155 :     fd_memcpy( weights[ weights_cnt ].id_key.uc, &ele->node_account, sizeof(fd_pubkey_t) );
     349        1155 :     fd_memcpy( weights[ weights_cnt ].bls_key, ele->bls_key, sizeof(weights[ weights_cnt ].bls_key) );
     350        1155 :     weights[ weights_cnt ].location = ele->location;
     351        1155 :     weights[ weights_cnt ].stake = ele->stake;
     352        1155 :     weights[ weights_cnt ].rank  = ele->rank;
     353        1155 :     weights_cnt++;
     354        1155 :   }
     355             : 
     356         639 :   sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
     357             : 
     358             :   /* https://github.com/anza-xyz/agave/blob/v4.0.0-beta.7/leader-schedule/src/lib.rs#L80-L83
     359             :      We do not deduplicate the weights here, unlike Agave, as it is
     360             :      guaranteed there will be no duplicate stake entries for a given fork
     361             :      in the stakes map. */
     362             : 
     363         639 :   return weights_cnt;
     364         639 : }
     365             : 
     366             : static void
     367             : get_vote_credits( uchar const *        account_data,
     368             :                   ulong                account_data_len,
     369             :                   ushort               commission,
     370         101 :                   fd_epoch_credits_t * epoch_credits ) {
     371             : 
     372         101 :   ulong                           cnt                = 0UL;
     373         101 :   fd_vote_epoch_credits_t const * vote_epoch_credits = fd_vote_account_epoch_credits( account_data, account_data_len, &cnt );
     374         101 :   FD_TEST( vote_epoch_credits );
     375         101 :   FD_TEST( cnt<=FD_EPOCH_CREDITS_MAX );
     376         101 :   epoch_credits->commission = commission;
     377             : 
     378         101 :   ulong n    = 0UL;
     379         101 :   ulong base = 0UL;
     380        2093 :   for( ulong i=0UL; i<cnt; i++ ) {
     381        1992 :     fd_vote_epoch_credits_t const * ele = &vote_epoch_credits[ i ];
     382             : 
     383        1992 :     if( fd_vote_epoch_credits_is_alpenglow_marker( ele ) ) continue;
     384        1992 :     if( !n ) base = ele->prev_credits;
     385             : 
     386        1992 :     FD_TEST( ele->epoch<=USHORT_MAX );           /* Epoch should fit. */
     387        1992 :      FD_TEST( ele->prev_credits>=base );          /* Prevent unsigned underflow. */
     388        1992 :      FD_TEST( ele->credits>=base );               /* Prevent unsigned underflow. */
     389        1992 :      FD_TEST( ele->prev_credits<=ele->credits );  /* Vote credits should be monotonic. */
     390             : 
     391        1992 :     epoch_credits->epoch[ n ]              = (ushort)ele->epoch;
     392        1992 :     epoch_credits->credits_delta[ n ]      = ele->credits      - base;
     393        1992 :     epoch_credits->prev_credits_delta[ n ] = ele->prev_credits - base;
     394        1992 :     n++;
     395        1992 :   }
     396             : 
     397         101 :   epoch_credits->cnt          = (uchar)n;
     398         101 :   epoch_credits->base_credits = base;
     399         101 : }
     400             : 
     401             : int
     402             : fd_stakes_vote_account_is_admissible( fd_bank_t const * bank,
     403             :                                       ulong             lamports,
     404             :                                       uchar const *     owner,
     405             :                                       uchar const *     data,
     406         106 :                                       ulong             data_len ) {
     407             :   /* The VAT filter also checks lamports against the rent-exempt
     408             :      minimum for the account's size, plus one epoch's VAT burn. */
     409         106 :   ulong vat_to_burn_per_epoch = fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch;
     410         106 :   ulong minimum_balance       = fd_rent_exempt_minimum_balance( &bank->f.rent, data_len ) + vat_to_burn_per_epoch;
     411         106 :   return lamports>=minimum_balance &&
     412         106 :          fd_vsv_is_correct_size_owner_and_init( owner, data, data_len ) &&
     413         106 :          fd_vote_account_has_bls_pubkey( data, data_len ) &&
     414         106 :          !( FD_FEATURE_ACTIVE_BANK( bank, enforce_validator_location_registration ) &&
     415         101 :             !fd_vote_account_is_valid_v5( data, data_len ) );
     416         106 : }
     417             : 
     418             : void
     419             : fd_refresh_vote_accounts( fd_bank_t *                    bank,
     420             :                           fd_accdb_t *                   accdb,
     421             :                           fd_runtime_stack_t *           runtime_stack,
     422             :                           fd_stake_delegations_t const * stake_delegations,
     423             :                           fd_stake_history_t const *     history,
     424             :                           ulong                          rewarded_epoch,
     425          28 :                           ulong *                        new_rate_activation_epoch ) {
     426          28 :   bank->epoch_credits_fork_id = fd_epoch_credits_store_new_fork( fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id );
     427             : 
     428          28 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     429          28 :   ushort             fork_id     = bank->vote_stakes_fork_id;
     430             : 
     431          28 :   fd_stake_accum_map_reset( runtime_stack->stakes.stake_accum_map );
     432          28 :   ulong epoch                      = bank->f.epoch;
     433          28 :   ulong total_stake                = 0UL;
     434          28 :   ulong total_activating           = 0UL;
     435          28 :   ulong total_deactivating         = 0UL;
     436          28 :   ulong staked_accounts            = 0UL;
     437          28 :   int   block_revenue_sharing      = FD_FEATURE_ACTIVE_BANK( bank, block_revenue_sharing );
     438          28 :   int   accumulate_reward_stakes   = rewarded_epoch!=ULONG_MAX;
     439             : 
     440          28 :   fd_stake_accum_t *     stake_accum_pool = runtime_stack->stakes.stake_accum;
     441          28 :   fd_stake_accum_map_t * stake_accum_map  = runtime_stack->stakes.stake_accum_map;
     442             : 
     443             :   /* Accumulate stakes across all delegations for all vote accounts. */
     444          28 :   fd_stake_delegations_iter_t iter_[1];
     445          28 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     446         184 :       !fd_stake_delegations_iter_done( iter );
     447         156 :       fd_stake_delegations_iter_next( iter ) ) {
     448             : 
     449         156 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     450             : 
     451         156 :     fd_stake_history_entry_t new_acc;
     452         156 :     uchar st = stake_delegation->state;
     453         156 :     if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
     454         117 :       new_acc = (fd_stake_history_entry_t){ .effective = stake_delegation->stake, .activating = 0UL, .deactivating = 0UL };
     455         117 :     } else if( st==FD_STAKE_DELEGATION_STATE_COOLED ) {
     456           8 :       new_acc = (fd_stake_history_entry_t){ .effective = 0UL, .activating = 0UL, .deactivating = 0UL };
     457          31 :     } else {
     458          31 :       new_acc = fd_stake_delegation_activation_status( stake_delegation, epoch, history, new_rate_activation_epoch );
     459          31 :     }
     460             : 
     461         156 :     ulong reward_stake = 0UL;
     462         156 :     if( FD_UNLIKELY( accumulate_reward_stakes ) ) {
     463         156 :       if( FD_LIKELY( st==FD_STAKE_DELEGATION_STATE_WARMED ) ) {
     464         117 :         reward_stake = stake_delegation->stake;
     465         117 :       } else if( st!=FD_STAKE_DELEGATION_STATE_COOLED ) {
     466          31 :         reward_stake = fd_stake_delegation_activation_status( stake_delegation, rewarded_epoch, history, new_rate_activation_epoch ).effective;
     467          31 :       }
     468         156 :     }
     469             : 
     470         156 :     total_stake        += new_acc.effective;
     471         156 :     total_activating   += new_acc.activating;
     472         156 :     total_deactivating += new_acc.deactivating;
     473         156 :     if( FD_UNLIKELY( !new_acc.effective && !reward_stake ) ) continue;
     474             : 
     475         117 :     fd_stake_accum_t * stake_accum = fd_stake_accum_map_ele_query( stake_accum_map, &stake_delegation->vote_account, NULL, stake_accum_pool );
     476         117 :     if( FD_UNLIKELY( !stake_accum ) ) {
     477         115 :       if( FD_UNLIKELY( staked_accounts>=runtime_stack->max_staked_vote_accounts ) ) {
     478           0 :         FD_LOG_ERR(( "invariant violation: staked_accounts >= max_vote_accounts" ));
     479           0 :       }
     480         115 :       stake_accum               = &runtime_stack->stakes.stake_accum[ staked_accounts ];
     481         115 :       stake_accum->pubkey       = stake_delegation->vote_account;
     482         115 :       stake_accum->stake        = new_acc.effective;
     483         115 :       stake_accum->reward_stake = reward_stake;
     484         115 :       fd_stake_accum_map_ele_insert( stake_accum_map, stake_accum, stake_accum_pool );
     485         115 :       staked_accounts++;
     486         115 :     } else {
     487           2 :       stake_accum->stake += new_acc.effective;
     488           2 :       FD_TEST( !__builtin_uaddl_overflow( stake_accum->reward_stake, reward_stake, &stake_accum->reward_stake ) );
     489           2 :     }
     490         117 :   }
     491             : 
     492             :   /* Only update total_*_stake at the epoch boundary.  These values
     493             :      are snapshots of the stake totals for the current epoch. */
     494          28 :   bank->f.total_activating_stake   = total_activating;
     495          28 :   bank->f.total_deactivating_stake = total_deactivating;
     496          28 :   bank->f.total_effective_stake    = total_stake;
     497             : 
     498             :   /* Iterate over the valid delegated vote accounts and insert them into
     499             :      the top votes set for the t-1 epoch. */
     500             : 
     501          28 :   ulong top_votes_eligible = 0UL;
     502          28 :   for( fd_stake_accum_map_iter_t iter = fd_stake_accum_map_iter_init( stake_accum_map, stake_accum_pool );
     503         143 :        !fd_stake_accum_map_iter_done( iter, stake_accum_map, stake_accum_pool );
     504         115 :        iter = fd_stake_accum_map_iter_next( iter, stake_accum_map, stake_accum_pool ) ) {
     505         115 :     fd_stake_accum_t * stake_accum = fd_stake_accum_map_iter_ele( iter, stake_accum_map, stake_accum_pool );
     506             : 
     507         115 :     fd_pubkey_t node_account_t_1 = {0};
     508         115 :     ulong       stake_t_1        = stake_accum->stake;
     509         115 :     ushort      commission_t_1   = 0;
     510         115 :     uchar       bls_key_t_1[ FD_BLS_PUBKEY_COMPRESSED_SZ ];
     511             : 
     512         115 :     if( FD_UNLIKELY( !stake_t_1 ) ) continue;
     513             : 
     514             :     /* Stake can be delegated to any address, so reject accounts that
     515             :        can't be vote accounts before reading their data. */
     516         111 :     ulong data_len = fd_accdb_data_len( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
     517         111 :     if( FD_UNLIKELY( data_len!=FD_VOTE_STATE_V2_SZ && data_len!=FD_VOTE_STATE_V3_SZ &&
     518         111 :                      data_len!=FD_VOTE_STATE_V4_SZ && data_len!=FD_VOTE_STATE_V5_SZ ) ) continue;
     519             : 
     520         106 :     fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, stake_accum->pubkey.uc );
     521         106 :     if( FD_UNLIKELY( !acc.lamports ) ) {
     522           0 :       fd_accdb_unread_one( accdb, &acc );
     523           0 :       continue;
     524           0 :     }
     525             : 
     526             :     /* SIMD-0123: delegator rewards do not count towards the balance
     527             :        https://github.com/anza-xyz/agave/blob/v4.4/vote/src/vote_account.rs#L225-L234 */
     528         106 :     ulong available_balance = acc.lamports;
     529         106 :     if( block_revenue_sharing ) {
     530           0 :       ulong pending_delegator_rewards = 0UL;
     531           0 :       if( FD_LIKELY( !fd_vote_account_pending_delegator_rewards( acc.data, acc.data_len, &pending_delegator_rewards ) ) ) {
     532           0 :         available_balance = fd_ulong_sat_sub( acc.lamports, pending_delegator_rewards );
     533           0 :       }
     534           0 :     }
     535         106 :     if( FD_UNLIKELY( !fd_stakes_vote_account_is_admissible( bank, available_balance, acc.owner, acc.data, acc.data_len ) ) ) {
     536           5 :       fd_accdb_unread_one( accdb, &acc );
     537           5 :       continue;
     538           5 :     }
     539             : 
     540         101 :     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 ) );
     541         101 :     FD_TEST( !fd_vote_account_node_pubkey( acc.data, acc.data_len, &node_account_t_1 ) );
     542         101 :     if( FD_LIKELY( fd_vote_account_has_bls_pubkey( acc.data, acc.data_len ) ) ) {
     543         101 :       FD_TEST( !fd_vote_account_bls_pubkey( acc.data, acc.data_len, bls_key_t_1 ) );
     544         101 :     }
     545             : 
     546             :     /* SIMD-0123 fields and SIMD-0232 collectors from the same account
     547             :        image as the inflation commission. */
     548         101 :     ushort      block_revenue_commission_bps;
     549         101 :     ulong       pending_delegator_rewards;
     550         101 :     fd_pubkey_t inflation_collector;
     551         101 :     fd_pubkey_t block_collector;
     552         101 :     FD_TEST( !fd_vote_account_block_revenue_commission_bps( acc.data, acc.data_len, &block_revenue_commission_bps ) );
     553         101 :     FD_TEST( !fd_vote_account_pending_delegator_rewards( acc.data, acc.data_len, &pending_delegator_rewards ) );
     554         101 :     FD_TEST( !fd_vote_account_collectors( acc.data, acc.data_len, &stake_accum->pubkey, &node_account_t_1, &inflation_collector, &block_collector ) );
     555             : 
     556         101 :     fd_validator_location_t location = fd_vote_account_location( acc.data, acc.data_len );
     557         101 :     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,
     558         101 :                            block_revenue_commission_bps, pending_delegator_rewards, &inflation_collector, &block_collector, &location );
     559         101 :     top_votes_eligible++;
     560         101 :     fd_accdb_unread_one( accdb, &acc );
     561         101 :   }
     562             : 
     563             :   /* The t-1 set is fixed from here on: rank it for Alpenglow. */
     564          28 :   fd_vote_stakes_finalize( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1 );
     565             : 
     566             :   /* Seed validity for the t-2 top votes set. */
     567          28 :   fd_vote_stakes_refresh( vote_stakes, fork_id, accdb, bank->accdb_fork_id );
     568             : 
     569             :   /* Populate the vote rewards map with the final set of filtered vote
     570             :      accounts. */
     571          28 :   fd_vote_rewards_map_t * vote_reward_map = runtime_stack->stakes.vote_map;
     572          28 :   fd_vote_rewards_map_reset( vote_reward_map );
     573          28 :   ulong vote_reward_cnt = 0UL;
     574             : 
     575          28 :   fd_epoch_credits_view_t epoch_credits_view[1];
     576          28 :   FD_TEST( fd_epoch_credits_view_init( epoch_credits_view, fd_bank_epoch_credits( bank ), bank->epoch_credits_fork_id ) );
     577             : 
     578             :   /* Populate the vote rewards map with the final set of filtered vote
     579             :      accounts for the t-1 epoch. */
     580          28 :   bank->f.total_epoch_stake = 0UL;
     581          28 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) t_1_iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     582          28 :   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 );
     583         129 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
     584         101 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
     585         101 :     fd_vote_stakes_ele_t ele[1];
     586         101 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, ele );
     587             : 
     588         101 :     fd_vote_stakes_ele_t ele_t_3[1];
     589         101 :     fd_vote_stakes_ele_t ele_t_2[1];
     590         101 :     int    exists_t_3     = fd_vote_stakes_query_t_3( vote_stakes, fork_id, &ele->pubkey, ele_t_3 );
     591         101 :     int    exists_t_2     = fd_vote_stakes_query_t_2( vote_stakes, fork_id, &ele->pubkey, ele_t_2 );
     592         101 :     ushort commission_t_3 = exists_t_3 ? ele_t_3->commission : 0;
     593         101 :     ushort commission_t_2 = exists_t_2 ? ele_t_2->commission : 0;
     594             : 
     595         101 :     fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_reward_cnt ];
     596         101 :     vote_ele->pubkey             = ele->pubkey;
     597         101 :     vote_ele->vote_rewards       = 0UL;
     598         101 :     if( FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) {
     599         101 :       vote_ele->commission = exists_t_3 ? commission_t_3 : (exists_t_2 ? commission_t_2 : ele->commission);
     600         101 :     } else {
     601           0 :       vote_ele->commission = ele->commission;
     602           0 :     }
     603             : 
     604         101 :     fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, ele->pubkey.uc );
     605         101 :     FD_TEST( acc.lamports );
     606             : 
     607         101 :     if( FD_UNLIKELY( vote_reward_cnt>=FD_RUNTIME_MAX_VAT_VOTE_ACCOUNTS ) ) {
     608           0 :       FD_LOG_ERR(( "invariant violation: vote_reward_cnt >= epoch credits max" ));
     609           0 :     }
     610         101 :     fd_epoch_credits_t * epoch_credits = &epoch_credits_view->credits[ vote_reward_cnt ];
     611         101 :     fd_memcpy( epoch_credits->pubkey, &ele->pubkey, sizeof(fd_pubkey_t) );
     612         101 :     get_vote_credits( acc.data, acc.data_len, vote_ele->commission, epoch_credits );
     613         101 :     fd_accdb_unread_one( accdb, &acc );
     614             : 
     615         101 :     if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) {
     616           0 :       fd_event_runtime_vote_account_emit( bank, ele->pubkey.uc, ele->node_account.uc, ele->stake,
     617           0 :                                           ele->commission, exists_t_2, commission_t_2, exists_t_3, commission_t_3,
     618           0 :                                           vote_ele->commission, epoch_credits );
     619           0 :     }
     620             : 
     621         101 :     fd_vote_rewards_map_ele_insert( vote_reward_map, vote_ele, runtime_stack->stakes.vote_ele );
     622         101 :     vote_reward_cnt++;
     623         101 :     bank->f.total_epoch_stake += ele->stake;
     624         101 :   }
     625          28 :   epoch_credits_view->len = vote_reward_cnt;
     626          28 :   fd_epoch_credits_view_fini( epoch_credits_view );
     627          28 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_votes( staked_accounts, top_votes_eligible );
     628          28 : }
     629             : 
     630             : /* https://github.com/anza-xyz/agave/blob/v4.3.0-beta.0/runtime/src/bank.rs#L2644-L2695 */
     631             : static void
     632             : fd_stakes_burn_vat( fd_bank_t *         bank,
     633             :                     fd_accdb_t *        accdb,
     634          28 :                     fd_capture_ctx_t *  capture_ctx ) {
     635          28 :   fd_vote_stakes_t * vote_stakes    = fd_bank_vote_stakes( bank );
     636          28 :   ushort             fork_id        = bank->vote_stakes_fork_id;
     637          28 :   ulong              burn_per_epoch = fd_slot_params_at_slot( bank, bank->f.slot ).vat_to_burn_per_epoch;
     638          28 :   ulong              total_vat      = 0UL;
     639             : 
     640          28 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     641          28 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter_mem );
     642         129 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter );
     643         101 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter ) ) {
     644         101 :     fd_vote_stakes_ele_t ele[1];
     645         101 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_1, iter, ele );
     646             : 
     647         101 :     fd_accdb_svm_update_t update[1];
     648         101 :     fd_acc_t              acc = fd_accdb_svm_open_rw( bank, accdb, update, &ele->pubkey, 0 );
     649         101 :     FD_TEST( acc.lamports>=burn_per_epoch );
     650         101 :     total_vat    += burn_per_epoch;
     651         101 :     acc.lamports -= burn_per_epoch;
     652         101 :     update->skip_event_diff = 1;
     653         101 :     fd_accdb_svm_close_rw( bank, accdb, capture_ctx, &acc, update );
     654         101 :   }
     655             : 
     656          28 :   fd_accdb_svm_credit( bank, accdb, capture_ctx, &fd_sysvar_incinerator_id, total_vat, 0 );
     657             : 
     658          28 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_vat_burn( burn_per_epoch );
     659          28 : }
     660             : 
     661             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
     662             : void
     663             : fd_stakes_activate_epoch( fd_bank_t *                    bank,
     664             :                           fd_runtime_stack_t *           runtime_stack,
     665             :                           fd_accdb_t *                   accdb,
     666             :                           fd_capture_ctx_t *             capture_ctx,
     667             :                           fd_stake_delegations_t *       stake_delegations,
     668          28 :                           ulong *                        new_rate_activation_epoch ) {
     669             :   /* We can update our stake history sysvar based on the bank stake values.
     670             :      Afterward, we can refresh the stake values for the vote accounts. */
     671             : 
     672          28 :   fd_stake_history_entry_t elem = fd_stake_delegations_totals( stake_delegations );
     673          28 :   elem.epoch = bank->f.epoch;
     674             : 
     675          28 :   fd_sysvar_stake_history_update( bank, accdb, capture_ctx, &elem );
     676          28 :   if( FD_UNLIKELY( fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_epoch_stake_history( &elem );
     677             : 
     678          28 :   uchar              stake_history_data[ FD_SYSVAR_STAKE_HISTORY_BINCODE_SZ ];
     679          28 :   fd_stake_history_t stake_history[1];
     680          28 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, bank->accdb_fork_id, stake_history_data, stake_history ) ) ) {
     681           0 :     FD_LOG_ERR(( "StakeHistory sysvar missing or invalid" ));
     682           0 :   }
     683             : 
     684             :   /* Now increment the epoch and recompute the stakes for the vote
     685             :      accounts for the new epoch value.  The rewarded epoch trails the
     686             :      current epoch by 1 except for the first epoch (genesis) case. */
     687             : 
     688          28 :   ulong rewarded_epoch = bank->f.epoch;
     689          28 :   bank->f.epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, bank->f.slot, NULL );
     690             : 
     691          28 :   fd_refresh_vote_accounts( bank,
     692          28 :                             accdb,
     693          28 :                             runtime_stack,
     694          28 :                             stake_delegations,
     695          28 :                             stake_history,
     696          28 :                             rewarded_epoch,
     697          28 :                             new_rate_activation_epoch );
     698          28 :   fd_stakes_burn_vat( bank, accdb, capture_ctx );
     699          28 : }
     700             : 
     701             : 
     702             : void
     703             : fd_stakes_update_stake_delegation( fd_pubkey_t const *        pubkey,
     704             :                                    fd_acc_t const *           acc,
     705             :                                    fd_bank_t *                bank,
     706             :                                    fd_stake_history_t const * stake_history,
     707         300 :                                    fd_txn_in_t const *        txn_in ) {
     708             : 
     709         300 :   fd_stake_state_t const * stake_state       = fd_stakes_get_state( acc );
     710         300 :   fd_stake_state_t const * prior_stake_state = NULL;
     711         300 :   if( FD_LIKELY( acc->prior_lamports && !memcmp( acc->prior_owner, &fd_solana_stake_program_id, 32UL ) ) ) {
     712           0 :     prior_stake_state = fd_stake_state_view( acc->prior_data, acc->prior_data_len );
     713           0 :   }
     714             : 
     715         300 :   int current_has_delegation = stake_state && stake_state->stake_type==FD_STAKE_STATE_STAKE;
     716         300 :   int prior_has_delegation   = prior_stake_state && prior_stake_state->stake_type==FD_STAKE_STATE_STAKE;
     717             : 
     718             :   /* If the current stake state isn't a delegation and it was a
     719             :      delegation in the previous stake state, insert a tombstone into the
     720             :      stake delegation's fork. */
     721         300 :   if( FD_UNLIKELY( !current_has_delegation ) ) {
     722         300 :     if( FD_LIKELY( !prior_has_delegation ) ) return; /* nothing to remove from */
     723           0 :     fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
     724           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey, 0 );
     725           0 :     if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
     726           0 :     return;
     727         300 :   }
     728             : 
     729             :   /* Agave replaces the cached version of the account whenever the
     730             :      account changes. */
     731             : 
     732           0 :   int account_changed = acc->prior_lamports  !=acc->lamports   ||
     733           0 :                         acc->prior_executable!=acc->executable ||
     734           0 :                         acc->prior_data_len  !=acc->data_len   ||
     735           0 :                         memcmp( acc->prior_owner, acc->owner, sizeof(fd_pubkey_t) );
     736           0 :   if( FD_LIKELY( !account_changed && acc->data_len ) ) {
     737           0 :     account_changed = !!memcmp( acc->prior_data, acc->data, acc->data_len );
     738           0 :   }
     739           0 :   if( FD_LIKELY( prior_has_delegation && !account_changed ) ) return;
     740             : 
     741           0 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
     742           0 :   if( FD_FEATURE_ACTIVE_BANK( bank, remove_inactive_stakes ) ) {
     743           0 :     fd_delegation_t const * delegation = &stake_state->stake.stake.delegation;
     744           0 :     if( FD_UNLIKELY( fd_delegation_is_inactive( delegation, bank->f.epoch, stake_history, &bank->f.warmup_cooldown_rate_epoch ) &&
     745           0 :                      fd_delegation_is_inactive( delegation, fd_ulong_sat_sub( bank->f.epoch, 1UL ), stake_history, &bank->f.warmup_cooldown_rate_epoch ) ) ) {
     746           0 :       fd_stake_delegations_fork_remove( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey, 0 );
     747           0 :       if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, NULL );
     748           0 :       return;
     749           0 :     }
     750           0 :   }
     751             : 
     752           0 :   ulong new_stake = stake_state->stake.stake.delegation.stake;
     753           0 :   fd_stake_delegations_fork_update( stake_delegations, bank->stake_delegations_fork_id, 0UL, pubkey,
     754           0 :                                     &stake_state->stake.stake.delegation.voter_pubkey,
     755           0 :                                     new_stake,
     756           0 :                                     stake_state->stake.stake.delegation.activation_epoch,
     757           0 :                                     stake_state->stake.stake.delegation.deactivation_epoch,
     758           0 :                                     stake_state->stake.stake.credits_observed,
     759           0 :                                     acc->lamports,
     760           0 :                                     (uint)acc->data_len );
     761           0 :   if( FD_UNLIKELY( txn_in && fd_bank_report_runtime_diffs( bank ) ) ) fd_event_runtime_stake_delegation_emit( txn_in, bank, pubkey, stake_state );
     762           0 : }

Generated by: LCOV version 1.14