LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 231 462 50.0 %
Date: 2026-03-19 18:19:27 Functions: 10 16 62.5 %

          Line data    Source code
       1             : #include <limits.h>
       2             : 
       3             : #include "fd_stakes.h"
       4             : #include "../runtime/fd_bank.h"
       5             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       6             : #include "../runtime/sysvar/fd_sysvar_stake_history.h"
       7             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       8             : #include "../runtime/fd_runtime_stack.h"
       9             : #include "../runtime/fd_system_ids.h"
      10             : #include "fd_stake_delegations.h"
      11             : #include "../accdb/fd_accdb_impl_v1.h"
      12             : #include "../accdb/fd_accdb_sync.h"
      13             : #include "../../util/bits/fd_sat.h"
      14             : 
      15             : /**********************************************************************/
      16             : /* Constants                                                          */
      17             : /**********************************************************************/
      18             : 
      19           0 : #define DEFAULT_WARMUP_COOLDOWN_RATE               ( 0.25 )
      20           0 : #define NEW_WARMUP_COOLDOWN_RATE                   ( 0.09 )
      21           0 : #define DEFAULT_SLASH_PENALTY                      ( 12 )
      22             : 
      23             : /**********************************************************************/
      24             : /* Types                                                              */
      25             : /**********************************************************************/
      26             : 
      27             : struct effective_activating {
      28             :   ulong effective;
      29             :   ulong activating;
      30             : };
      31             : typedef struct effective_activating effective_activating_t;
      32             : 
      33             : typedef fd_stake_history_entry_t fd_stake_activation_status_t;
      34             : 
      35             : /**********************************************************************/
      36             : /* Static helpers                                                     */
      37             : /**********************************************************************/
      38             : 
      39             : static inline double
      40           0 : warmup_cooldown_rate( ulong current_epoch, ulong * new_rate_activation_epoch ) {
      41           0 :   ulong activation_epoch = new_rate_activation_epoch ? *new_rate_activation_epoch : ULONG_MAX;
      42           0 :   return current_epoch<activation_epoch ? DEFAULT_WARMUP_COOLDOWN_RATE : NEW_WARMUP_COOLDOWN_RATE;
      43           0 : }
      44             : 
      45             : static fd_stake_history_entry_t const *
      46             : fd_stake_history_ele_binary_search_const( fd_stake_history_t const * history,
      47           0 :                                           ulong epoch ) {
      48           0 :   ulong start = 0UL;
      49           0 :   ulong end  = history->fd_stake_history_len - 1;
      50             : 
      51           0 :   while ( start<=end ) {
      52           0 :     ulong mid = start + ( end - start ) / 2UL;
      53           0 :     if( history->fd_stake_history[mid].epoch==epoch ) {
      54           0 :       return &history->fd_stake_history[mid].entry;
      55           0 :     } else if( history->fd_stake_history[mid].epoch<epoch ) {
      56           0 :       if ( mid==0 ) return NULL;
      57           0 :       end = mid - 1;
      58           0 :     } else {
      59           0 :       start = mid + 1;
      60           0 :     }
      61           0 :   }
      62           0 :   return NULL;
      63           0 : }
      64             : 
      65             : static fd_stake_history_entry_t const *
      66             : fd_stake_history_ele_query_const( fd_stake_history_t const * history,
      67           0 :                                   ulong epoch ) {
      68           0 :   if( 0 == history->fd_stake_history_len ) {
      69           0 :     return NULL;
      70           0 :   }
      71             : 
      72           0 :   if( epoch > history->fd_stake_history[0].epoch ) {
      73           0 :     return NULL;
      74           0 :   }
      75             : 
      76           0 :   ulong off = (history->fd_stake_history[0].epoch - epoch);
      77           0 :   if( off >= history->fd_stake_history_len ) {
      78           0 :     return fd_stake_history_ele_binary_search_const( history, epoch );
      79           0 :   }
      80             : 
      81           0 :   ulong e = (off + history->fd_stake_history_offset) & (history->fd_stake_history_size - 1);
      82             : 
      83           0 :   if ( history->fd_stake_history[e].epoch == epoch ) {
      84           0 :     return &history->fd_stake_history[e].entry;
      85           0 :   }
      86             : 
      87           0 :   return fd_stake_history_ele_binary_search_const( history, epoch );
      88           0 : }
      89             : 
      90             : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L728
      91             : static effective_activating_t
      92             : stake_and_activating( fd_delegation_t const *    self,
      93             :                       ulong                      target_epoch,
      94             :                       fd_stake_history_t const * history,
      95          30 :                       ulong *                    new_rate_activation_epoch ) {
      96          30 :   ulong delegated_stake = self->stake;
      97             : 
      98          30 :   fd_stake_history_entry_t const * cluster_stake_at_activation_epoch;
      99          30 :   if( self->activation_epoch==ULONG_MAX ) {
     100          30 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
     101          30 :   } else if( self->activation_epoch==self->deactivation_epoch ) {
     102           0 :     return ( effective_activating_t ){ .effective = 0, .activating = 0 };
     103           0 :   } else if( target_epoch==self->activation_epoch ) {
     104           0 :     return ( effective_activating_t ){ .effective = 0, .activating = delegated_stake };
     105           0 :   } else if( target_epoch<self->activation_epoch ) {
     106           0 :     return ( effective_activating_t ){ .effective = 0, .activating = 0 };
     107           0 :   } else if( history &&
     108           0 :               ( cluster_stake_at_activation_epoch = fd_stake_history_ele_query_const(
     109           0 :                     history, self->activation_epoch ) ) ) {
     110           0 :     ulong                            prev_epoch         = self->activation_epoch;
     111           0 :     fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_activation_epoch;
     112             : 
     113           0 :     ulong current_epoch;
     114           0 :     ulong current_effective_stake = 0;
     115           0 :     for( ;; ) {
     116           0 :       current_epoch = prev_epoch + 1;
     117           0 :       if( FD_LIKELY( prev_cluster_stake->activating==0 ) ) {
     118           0 :         break;
     119           0 :       }
     120             : 
     121           0 :       ulong  remaining_activating_stake = delegated_stake - current_effective_stake;
     122           0 :       double weight = (double)remaining_activating_stake / (double)prev_cluster_stake->activating;
     123           0 :       double warmup_cooldown_rate_ =
     124           0 :           warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
     125             : 
     126           0 :       double newly_effective_cluster_stake =
     127           0 :           (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
     128           0 :       ulong newly_effective_stake =
     129           0 :           fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_effective_cluster_stake ), 1 );
     130             : 
     131           0 :       current_effective_stake += newly_effective_stake;
     132           0 :       if( FD_LIKELY( current_effective_stake>=delegated_stake ) ) {
     133           0 :         current_effective_stake = delegated_stake;
     134           0 :         break;
     135           0 :       }
     136             : 
     137           0 :       if( FD_LIKELY( current_epoch>=target_epoch ||
     138           0 :                      current_epoch>=self->deactivation_epoch ) ) {
     139           0 :         break;
     140           0 :       }
     141             : 
     142           0 :       fd_stake_history_entry_t const * current_cluster_stake =
     143           0 :           fd_stake_history_ele_query_const( history, current_epoch );
     144           0 :       if( FD_LIKELY( current_cluster_stake ) ) {
     145           0 :         prev_epoch         = current_epoch;
     146           0 :         prev_cluster_stake = current_cluster_stake;
     147           0 :       } else {
     148           0 :         break;
     149           0 :       }
     150           0 :     }
     151           0 :     return ( effective_activating_t ){ .effective  = current_effective_stake,
     152           0 :                                        .activating = delegated_stake - current_effective_stake };
     153           0 :   } else {
     154           0 :     return ( effective_activating_t ){ .effective = delegated_stake, .activating = 0 };
     155           0 :   }
     156          30 : }
     157             : 
     158             : // https://github.com/anza-xyz/agave/blob/c8685ce0e1bb9b26014f1024de2cd2b8c308cbde/sdk/program/src/stake/state.rs#L641
     159             : static fd_stake_activation_status_t
     160             : stake_activating_and_deactivating( fd_delegation_t const *    self,
     161             :                                    ulong                      target_epoch,
     162             :                                    fd_stake_history_t const * stake_history,
     163          30 :                                    ulong *                    new_rate_activation_epoch ) {
     164             : 
     165          30 :   effective_activating_t effective_activating =
     166          30 :       stake_and_activating( self, target_epoch, stake_history, new_rate_activation_epoch );
     167             : 
     168          30 :   ulong effective_stake  = effective_activating.effective;
     169          30 :   ulong activating_stake = effective_activating.activating;
     170             : 
     171          30 :   fd_stake_history_entry_t const * cluster_stake_at_deactivation_epoch = NULL;
     172             : 
     173          30 :   if( target_epoch<self->deactivation_epoch ) {
     174          30 :     if( activating_stake==0 ) {
     175          30 :       return ( fd_stake_history_entry_t ){
     176          30 :           .effective = effective_stake, .deactivating = 0, .activating = 0 };
     177          30 :     } else {
     178           0 :       return ( fd_stake_history_entry_t ){
     179           0 :           .effective = effective_stake, .deactivating = 0, .activating = activating_stake };
     180           0 :     }
     181          30 :   } else if( target_epoch==self->deactivation_epoch ) {
     182           0 :     return ( fd_stake_history_entry_t ){
     183           0 :         .effective = effective_stake, .deactivating = effective_stake, .activating = 0 };
     184           0 :   } else if( stake_history &&
     185           0 :              ( cluster_stake_at_deactivation_epoch = fd_stake_history_ele_query_const( stake_history, self->deactivation_epoch ) ) ) {
     186           0 :     ulong                      prev_epoch         = self->deactivation_epoch;
     187           0 :     fd_stake_history_entry_t const * prev_cluster_stake = cluster_stake_at_deactivation_epoch;
     188             : 
     189           0 :     ulong current_epoch;
     190           0 :     ulong current_effective_stake = effective_stake;
     191           0 :     for( ;; ) {
     192           0 :       current_epoch = prev_epoch + 1;
     193           0 :       if( prev_cluster_stake->deactivating==0 ) break;
     194             : 
     195           0 :       double weight = (double)current_effective_stake / (double)prev_cluster_stake->deactivating;
     196           0 :       double warmup_cooldown_rate_ =
     197           0 :           warmup_cooldown_rate( current_epoch, new_rate_activation_epoch );
     198             : 
     199           0 :       double newly_not_effective_cluster_stake =
     200           0 :           (double)prev_cluster_stake->effective * warmup_cooldown_rate_;
     201           0 :       ulong newly_not_effective_stake =
     202           0 :           fd_ulong_max( fd_rust_cast_double_to_ulong( weight * newly_not_effective_cluster_stake ), 1 );
     203             : 
     204           0 :       current_effective_stake =
     205           0 :           fd_ulong_sat_sub( current_effective_stake, newly_not_effective_stake );
     206           0 :       if( current_effective_stake==0 ) break;
     207             : 
     208           0 :       if( current_epoch>=target_epoch ) break;
     209             : 
     210           0 :       fd_stake_history_entry_t const * current_cluster_stake = NULL;
     211           0 :       if( ( current_cluster_stake = fd_stake_history_ele_query_const(stake_history, current_epoch ) ) ) {
     212           0 :         prev_epoch         = current_epoch;
     213           0 :         prev_cluster_stake = current_cluster_stake;
     214           0 :       } else {
     215           0 :         break;
     216           0 :       }
     217           0 :     }
     218           0 :     return ( fd_stake_history_entry_t ){ .effective    = current_effective_stake,
     219           0 :                                          .deactivating = current_effective_stake,
     220           0 :                                          .activating   = 0 };
     221           0 :   } else {
     222           0 :     return ( fd_stake_history_entry_t ){ .effective = 0, .activating = 0, .deactivating = 0 };
     223           0 :   }
     224          30 : }
     225             : 
     226             : static void
     227             : write_stake_config( fd_accdb_user_t *         accdb,
     228             :                     fd_funk_txn_xid_t const * xid,
     229           0 :                     fd_stake_config_t const * stake_config ) {
     230           0 :   ulong               data_sz = fd_stake_config_size( stake_config );
     231           0 :   fd_pubkey_t const * address = &fd_solana_stake_program_config_id;
     232             : 
     233           0 :   fd_accdb_rw_t rw[1];
     234           0 :   fd_accdb_open_rw( accdb, rw, xid, address, data_sz, FD_ACCDB_FLAG_CREATE );
     235             : 
     236             :   /* FIXME update capitalization? */
     237             :   /* FIXME set owner to Config program? */
     238             :   /* FIXME Agave reflink? */
     239             :   /* FIXME derive lamport balance from rent instead of hardcoding */
     240             : 
     241           0 :   fd_accdb_ref_lamports_set( rw, 960480UL );
     242           0 :   fd_accdb_ref_exec_bit_set( rw, 0 );
     243           0 :   fd_accdb_ref_data_sz_set( accdb, rw, data_sz, 0 );
     244           0 :   fd_bincode_encode_ctx_t ctx = {
     245           0 :     .data    = fd_accdb_ref_data( rw ),
     246           0 :     .dataend = (uchar *)fd_accdb_ref_data( rw ) + data_sz
     247           0 :   };
     248           0 :   if( fd_stake_config_encode( stake_config, &ctx ) )
     249           0 :     FD_LOG_ERR( ( "fd_stake_config_encode failed" ) );
     250             : 
     251           0 :   fd_accdb_close_rw( accdb, rw );
     252           0 : }
     253             : 
     254             : /**********************************************************************/
     255             : /* Public API                                                         */
     256             : /**********************************************************************/
     257             : 
     258             : int
     259             : fd_stakes_new_warmup_cooldown_rate_epoch(
     260             :     fd_epoch_schedule_t const * epoch_schedule,
     261             :     fd_features_t const *       features,
     262             :     /* out */ ulong *           epoch,
     263             :     int *                       err
     264          10 : ) {
     265          10 :   *err = 0;
     266             : 
     267          10 :   if( FD_UNLIKELY( !epoch_schedule ) ) {
     268           0 :     *epoch = ULONG_MAX;
     269           0 :     *err   = FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
     270           0 :     return 1;
     271           0 :   }
     272          10 :   *epoch = fd_slot_to_epoch( epoch_schedule, features->reduce_stake_warmup_cooldown, NULL );
     273          10 :   return 1;
     274          10 : }
     275             : 
     276             : void
     277             : fd_stakes_config_init( fd_accdb_user_t *         accdb,
     278           0 :                        fd_funk_txn_xid_t const * xid ) {
     279           0 :   fd_stake_config_t stake_config = {
     280           0 :       .warmup_cooldown_rate = DEFAULT_WARMUP_COOLDOWN_RATE,
     281           0 :       .slash_penalty        = DEFAULT_SLASH_PENALTY,
     282           0 :   };
     283           0 :   write_stake_config( accdb, xid, &stake_config );
     284           0 : }
     285             : 
     286             : int
     287             : fd_stakes_get_state( fd_account_meta_t const * meta,
     288         301 :                      fd_stake_state_v2_t *     out ) {
     289         301 :   int rc;
     290             : 
     291         301 :   fd_bincode_decode_ctx_t bincode_ctx = {
     292         301 :     .data    = fd_account_data( meta ),
     293         301 :     .dataend = fd_account_data( meta ) + meta->dlen,
     294         301 :   };
     295             : 
     296         301 :   ulong total_sz = 0UL;
     297         301 :   rc = fd_stake_state_v2_decode_footprint( &bincode_ctx, &total_sz );
     298         301 :   if( FD_UNLIKELY( rc!=FD_BINCODE_SUCCESS ) ) {
     299           0 :     return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
     300           0 :   }
     301             : 
     302         301 :   fd_stake_state_v2_decode( out, &bincode_ctx );
     303             : 
     304         301 :   return 0;
     305         301 : }
     306             : 
     307             : fd_stake_history_entry_t
     308             : fd_stakes_activating_and_deactivating( fd_stake_delegation_t const * stake_delegation,
     309             :                                        ulong                         target_epoch,
     310             :                                        fd_stake_history_t const *    stake_history,
     311          30 :                                        ulong *                       new_rate_activation_epoch ) {
     312          30 :   fd_delegation_t delegation = {
     313          30 :     .voter_pubkey         = stake_delegation->vote_account,
     314          30 :     .stake                = stake_delegation->stake,
     315          30 :     .deactivation_epoch   = stake_delegation->deactivation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->deactivation_epoch,
     316          30 :     .activation_epoch     = stake_delegation->activation_epoch==USHORT_MAX ? ULONG_MAX : stake_delegation->activation_epoch,
     317          30 :     .warmup_cooldown_rate = fd_stake_delegations_warmup_cooldown_rate_to_double( stake_delegation->warmup_cooldown_rate ),
     318          30 :   };
     319             : 
     320          30 :   return stake_activating_and_deactivating(
     321          30 :     &delegation, target_epoch, stake_history, new_rate_activation_epoch );
     322          30 : }
     323             : 
     324             : ulong
     325             : fd_stake_weights_by_node( fd_vote_stakes_t *       vote_stakes,
     326             :                           ushort                   fork_idx,
     327         300 :                           fd_vote_stake_weight_t * weights ) {
     328         300 :   ulong weights_cnt = 0;
     329         300 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     330         300 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
     331         601 :        !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter  );
     332         301 :        fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
     333         301 :     fd_pubkey_t pubkey;
     334         301 :     ulong       stake_t_2;
     335         301 :     fd_pubkey_t node_account_t_2;
     336         301 :     fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, NULL, &stake_t_2, NULL, &node_account_t_2 );
     337         301 :     if( FD_UNLIKELY( !stake_t_2 ) ) continue;
     338             : 
     339         301 :     fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
     340         301 :     fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_2, sizeof(fd_pubkey_t) );
     341         301 :     weights[ weights_cnt ].stake = stake_t_2;
     342         301 :     weights_cnt++;
     343         301 :   }
     344         300 :   sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
     345             : 
     346         300 :   return weights_cnt;
     347         300 : }
     348             : 
     349             : ulong
     350             : fd_stake_weights_by_node_next( fd_vote_stakes_t *       vote_stakes,
     351             :                                ushort                   fork_idx,
     352         301 :                                fd_vote_stake_weight_t * weights ) {
     353         301 :   ulong weights_cnt = 0;
     354         301 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     355         301 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, fork_idx, iter_mem );
     356         602 :        !fd_vote_stakes_fork_iter_done( vote_stakes, fork_idx, iter  );
     357         301 :        fd_vote_stakes_fork_iter_next( vote_stakes, fork_idx, iter ) ) {
     358         301 :     fd_pubkey_t pubkey;
     359         301 :     ulong       stake_t_1;
     360         301 :     fd_pubkey_t node_account_t_1;
     361         301 :     fd_vote_stakes_fork_iter_ele( vote_stakes, fork_idx, iter, &pubkey, &stake_t_1, NULL, &node_account_t_1, NULL );
     362         301 :     if( FD_UNLIKELY( !stake_t_1 ) ) continue;
     363             : 
     364         301 :     fd_memcpy( weights[ weights_cnt ].vote_key.uc, &pubkey, sizeof(fd_pubkey_t) );
     365         301 :     fd_memcpy( weights[ weights_cnt ].id_key.uc, &node_account_t_1, sizeof(fd_pubkey_t) );
     366         301 :     weights[ weights_cnt ].stake = stake_t_1;
     367         301 :     weights_cnt++;
     368         301 :   }
     369         301 :   sort_vote_weights_by_stake_vote_inplace( weights, weights_cnt );
     370             : 
     371         301 :   return weights_cnt;
     372         301 : }
     373             : 
     374             : static void
     375             : get_vote_credits_commission( uchar const *        account_data,
     376             :                              ulong                account_data_len,
     377             :                              uchar *              buf,
     378             :                              fd_vote_rewards_t *  vote_ele,
     379             :                              fd_pubkey_t *        node_account_t_1,
     380             :                              ulong *              last_vote_slot,
     381             :                              long *               last_vote_timestamp,
     382           2 :                              fd_epoch_credits_t * epoch_credits_opt ) {
     383             : 
     384           2 :   fd_bincode_decode_ctx_t ctx = {
     385           2 :     .data    = account_data,
     386           2 :     .dataend = account_data + account_data_len,
     387           2 :   };
     388             : 
     389           2 :   fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( buf, &ctx );
     390           2 :   if( FD_UNLIKELY( vsv==NULL ) ) {
     391           0 :     FD_LOG_CRIT(( "unable to decode vote state versioned" ));
     392           0 :   }
     393           2 :   fd_vote_epoch_credits_t * vote_epoch_credits = NULL;
     394             : 
     395           2 :   switch( vsv->discriminant ) {
     396           0 :   case fd_vote_state_versioned_enum_v1_14_11:
     397           0 :     vote_ele->commission = vsv->inner.v1_14_11.commission;
     398           0 :     *node_account_t_1    = vsv->inner.v1_14_11.node_pubkey;
     399           0 :     *last_vote_slot      = vsv->inner.v1_14_11.last_timestamp.slot;
     400           0 :     *last_vote_timestamp = vsv->inner.v1_14_11.last_timestamp.timestamp;
     401           0 :     vote_epoch_credits   = vsv->inner.v1_14_11.epoch_credits;
     402           0 :     break;
     403           0 :   case fd_vote_state_versioned_enum_v3:
     404           0 :     vote_ele->commission = vsv->inner.v3.commission;
     405           0 :     *node_account_t_1    = vsv->inner.v3.node_pubkey;
     406           0 :     *last_vote_slot      = vsv->inner.v3.last_timestamp.slot;
     407           0 :     *last_vote_timestamp = vsv->inner.v3.last_timestamp.timestamp;
     408           0 :     vote_epoch_credits   = vsv->inner.v3.epoch_credits;
     409           0 :     break;
     410           2 :   case fd_vote_state_versioned_enum_v4:
     411           2 :     vote_ele->commission = (uchar)(vsv->inner.v4.inflation_rewards_commission_bps/100);
     412           2 :     *node_account_t_1    = vsv->inner.v4.node_pubkey;
     413           2 :     *last_vote_slot      = vsv->inner.v4.last_timestamp.slot;
     414           2 :     *last_vote_timestamp = vsv->inner.v4.last_timestamp.timestamp;
     415           2 :     vote_epoch_credits   = vsv->inner.v4.epoch_credits;
     416           2 :     break;
     417           0 :   default:
     418           0 :     FD_LOG_CRIT(( "invalid vote state version %u", vsv->discriminant ));
     419           2 :   }
     420             : 
     421           2 :   if( !epoch_credits_opt ) return;
     422           2 :   epoch_credits_opt->cnt = 0UL;
     423           2 :   for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( vote_epoch_credits );
     424          15 :        !deq_fd_vote_epoch_credits_t_iter_done( vote_epoch_credits, iter );
     425          13 :        iter = deq_fd_vote_epoch_credits_t_iter_next( vote_epoch_credits, iter ) ) {
     426          13 :     fd_vote_epoch_credits_t * ele = deq_fd_vote_epoch_credits_t_iter_ele( vote_epoch_credits, iter );
     427          13 :     epoch_credits_opt->epoch[ epoch_credits_opt->cnt ]        = (ushort)ele->epoch;
     428          13 :     epoch_credits_opt->credits[ epoch_credits_opt->cnt ]      = ele->credits;
     429          13 :     epoch_credits_opt->prev_credits[ epoch_credits_opt->cnt ] = ele->prev_credits;
     430          13 :     epoch_credits_opt->cnt++;
     431          13 :   }
     432           2 : }
     433             : 
     434             : /* We need to update the amount of stake that each vote account has for
     435             :    the given epoch.  This can only be done after the stake history
     436             :    sysvar has been updated.  We also cache the stakes for each of the
     437             :    vote accounts for the previous epoch.
     438             : 
     439             :    https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L471 */
     440             : void
     441             : fd_refresh_vote_accounts( fd_bank_t *                    bank,
     442             :                           fd_accdb_user_t *              accdb,
     443             :                           fd_funk_txn_xid_t const *      xid,
     444             :                           fd_runtime_stack_t *           runtime_stack,
     445             :                           fd_stake_delegations_t const * stake_delegations,
     446             :                           fd_stake_history_t const *     history,
     447           2 :                           ulong *                        new_rate_activation_epoch ) {
     448             : 
     449           2 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes_locking_modify( bank );
     450             : 
     451           2 :   fd_top_votes_t * top_votes = fd_bank_top_votes_modify( bank );
     452           2 :   fd_top_votes_init( top_votes );
     453             : 
     454           2 :   ushort parent_idx = bank->data->vote_stakes_fork_id;
     455           2 :   ushort child_idx  = fd_vote_stakes_new_child( vote_stakes );
     456             : 
     457           2 :   bank->data->vote_stakes_fork_id = child_idx;
     458             : 
     459             : 
     460           2 :   uchar __attribute__((aligned(128))) vsv_buf[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
     461             : 
     462           2 :   fd_vote_rewards_map_t * vote_ele_map = fd_type_pun( runtime_stack->stakes.vote_map_mem );
     463           2 :   fd_vote_rewards_map_reset( vote_ele_map );
     464           2 :   ulong vote_ele_cnt = 0UL;
     465             : 
     466           2 :   ulong epoch = fd_bank_epoch_get( bank );
     467             : 
     468           2 :   ulong total_stake = 0UL;
     469           2 :   fd_stake_delegations_iter_t iter_[1];
     470           2 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     471           4 :        !fd_stake_delegations_iter_done( iter );
     472           2 :        fd_stake_delegations_iter_next( iter ) ) {
     473             : 
     474           2 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     475             : 
     476           2 :     fd_stake_history_entry_t new_entry = fd_stakes_activating_and_deactivating(
     477           2 :         stake_delegation,
     478           2 :         epoch,
     479           2 :         history,
     480           2 :         new_rate_activation_epoch );
     481             : 
     482           2 :     if( FD_UNLIKELY( !fd_vote_stakes_query_pubkey( vote_stakes, child_idx, &stake_delegation->vote_account ) ) ) {
     483           2 :       fd_accdb_ro_t vote_ro[1];
     484             : 
     485           2 :       ulong       old_stake_t_1        = 0UL;
     486           2 :       fd_pubkey_t old_node_account_t_1 = {0};
     487           2 :       int exists_prev = fd_vote_stakes_query_t_1( vote_stakes, parent_idx, &stake_delegation->vote_account, &old_stake_t_1, &old_node_account_t_1 );
     488           2 :       int exists_curr = 1;
     489           2 :       if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, vote_ro, xid, &stake_delegation->vote_account ) ) ) {
     490           0 :         exists_curr = 0;
     491           2 :       } else if( FD_UNLIKELY( !fd_vsv_is_correct_size_and_initialized( vote_ro->meta ) ) ) {
     492           0 :         fd_accdb_close_ro( accdb, vote_ro );
     493           0 :         exists_curr = 0;
     494           0 :       }
     495             : 
     496           2 :       if( FD_UNLIKELY( !exists_curr ) ) {
     497             : 
     498             :         /* If the vote account does not exist going into the epoch
     499             :            boundary, and did not exist at the end of the last epoch
     500             :            boundary, then we can fully skip it. */
     501           0 :         if( FD_UNLIKELY( !exists_prev ) ) continue;
     502             : 
     503             :         /* If the account does not exist but did in the previous epoch,
     504             :            it still needs to be added to the top votes and the vote
     505             :            stakes data structure in case the vote account is revived
     506             :            again. */
     507           0 :         fd_top_votes_insert( top_votes,
     508           0 :                              &stake_delegation->vote_account,
     509           0 :                              &old_node_account_t_1,
     510           0 :                              old_stake_t_1,
     511           0 :                              0UL,
     512           0 :                              0L );
     513           0 :         fd_top_votes_invalidate( top_votes, &stake_delegation->vote_account );
     514             : 
     515           0 :         fd_vote_stakes_insert_key(
     516           0 :             vote_stakes,
     517           0 :             child_idx,
     518           0 :             &stake_delegation->vote_account,
     519           0 :             &old_node_account_t_1,
     520           0 :             &old_node_account_t_1,
     521           0 :             old_stake_t_1,
     522           0 :             fd_bank_epoch_get( bank ),
     523           0 :             0 );
     524           2 :       } else {
     525             :         /* If the account currently exists, we need to insert the entry
     526             :            into the vote stakes data structure.  We will treat the t-2
     527             :            stake as 0 if the account did not exist at the end of the
     528             :            last epoch boundary.*/
     529           2 :         fd_pubkey_t curr_node_account_t_1;
     530           2 :         ulong       last_vote_slot;
     531           2 :         long        last_vote_timestamp;
     532           2 :         fd_epoch_credits_t * epoch_credits = vote_ele_cnt<runtime_stack->expected_vote_accounts ? &runtime_stack->stakes.epoch_credits[ vote_ele_cnt ] : NULL;
     533             : 
     534           2 :         get_vote_credits_commission( fd_accdb_ref_data_const( vote_ro ),
     535           2 :                                     fd_accdb_ref_data_sz( vote_ro ),
     536           2 :                                     vsv_buf,
     537           2 :                                     &runtime_stack->stakes.vote_ele[ vote_ele_cnt ],
     538           2 :                                     &curr_node_account_t_1,
     539           2 :                                     &last_vote_slot,
     540           2 :                                     &last_vote_timestamp,
     541           2 :                                     epoch_credits );
     542           2 :         fd_accdb_close_ro( accdb, vote_ro );
     543             : 
     544             :         /* If old_node_account_t_1 gets zero-initialized which means
     545             :            that it is still valid to use. */
     546           2 :         fd_vote_stakes_insert_key(
     547           2 :             vote_stakes,
     548           2 :             child_idx,
     549           2 :             &stake_delegation->vote_account,
     550           2 :             &curr_node_account_t_1,
     551           2 :             &old_node_account_t_1,
     552           2 :             old_stake_t_1,
     553           2 :             fd_bank_epoch_get( bank ),
     554           2 :             1 );
     555             : 
     556           2 :         fd_top_votes_insert(
     557           2 :             top_votes,
     558           2 :             &stake_delegation->vote_account,
     559           2 :             &old_node_account_t_1,
     560           2 :             old_stake_t_1,
     561           2 :             last_vote_slot,
     562           2 :             last_vote_timestamp );
     563             : 
     564           2 :         fd_vote_rewards_t * vote_ele = &runtime_stack->stakes.vote_ele[ vote_ele_cnt ];
     565           2 :         vote_ele->pubkey             = stake_delegation->vote_account;
     566           2 :         vote_ele->vote_rewards       = 0UL;
     567           2 :         fd_vote_rewards_map_ele_insert( vote_ele_map, vote_ele, runtime_stack->stakes.vote_ele );
     568           2 :         vote_ele_cnt++;
     569           2 :       }
     570           2 :     }
     571             : 
     572           2 :     fd_vote_stakes_insert_update( vote_stakes,
     573           2 :                                   child_idx,
     574           2 :                                   &stake_delegation->vote_account,
     575           2 :                                   new_entry.effective );
     576             : 
     577           2 :     total_stake += new_entry.effective;
     578           2 :   }
     579           2 :   fd_bank_total_epoch_stake_set( bank, total_stake );
     580             : 
     581           2 :   fd_vote_stakes_insert_fini( vote_stakes, child_idx );
     582             : 
     583           2 :   fd_bank_vote_stakes_end_locking_modify( bank );
     584           2 : }
     585             : 
     586             : /* https://github.com/anza-xyz/agave/blob/v3.0.4/runtime/src/stakes.rs#L280 */
     587             : void
     588             : fd_stakes_activate_epoch( fd_bank_t *                    bank,
     589             :                           fd_runtime_stack_t *           runtime_stack,
     590             :                           fd_accdb_user_t *              accdb,
     591             :                           fd_funk_txn_xid_t const *      xid,
     592             :                           fd_capture_ctx_t *             capture_ctx,
     593             :                           fd_stake_delegations_t const * stake_delegations,
     594           2 :                           ulong *                        new_rate_activation_epoch ) {
     595             : 
     596             :   /* First, we need to accumulate the stats for the current amount of
     597             :      effective, activating, and deactivating stake for the current
     598             :      epoch.  Once this is computed, we can add update our stake history
     599             :      sysvar.  Afterward, we can refresh the stake values for the vote
     600             :      accounts for the new epoch. */
     601             : 
     602           2 :   fd_stake_history_t stake_history[1];
     603           2 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
     604           0 :     FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
     605           0 :   }
     606             : 
     607           2 :   fd_epoch_stake_history_entry_pair_t new_elem = {
     608           2 :     .epoch = fd_bank_epoch_get( bank ),
     609           2 :     .entry = {
     610           2 :       .effective    = 0UL,
     611           2 :       .activating   = 0UL,
     612           2 :       .deactivating = 0UL
     613           2 :     }
     614           2 :   };
     615             : 
     616           2 :   fd_stake_delegations_iter_t iter_[1];
     617           2 :   for( fd_stake_delegations_iter_t * iter = fd_stake_delegations_iter_init( iter_, stake_delegations );
     618           4 :        !fd_stake_delegations_iter_done( iter );
     619           2 :        fd_stake_delegations_iter_next( iter ) ) {
     620           2 :     fd_stake_delegation_t const * stake_delegation = fd_stake_delegations_iter_ele( iter );
     621             : 
     622           2 :     fd_stake_history_entry_t new_entry = fd_stakes_activating_and_deactivating(
     623           2 :         stake_delegation,
     624           2 :         fd_bank_epoch_get( bank ),
     625           2 :         stake_history,
     626           2 :         new_rate_activation_epoch );
     627           2 :     new_elem.entry.effective    += new_entry.effective;
     628           2 :     new_elem.entry.activating   += new_entry.activating;
     629           2 :     new_elem.entry.deactivating += new_entry.deactivating;
     630           2 :   }
     631           2 :   fd_sysvar_stake_history_update( bank, accdb, xid, capture_ctx, &new_elem );
     632             : 
     633           2 :   if( FD_UNLIKELY( !fd_sysvar_stake_history_read( accdb, xid, stake_history ) ) ) {
     634           0 :     FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
     635           0 :   }
     636             : 
     637             :   /* Now increment the epoch and recompute the stakes for the vote
     638             :      accounts for the new epoch value. */
     639             : 
     640           2 :   fd_bank_epoch_set( bank, fd_bank_epoch_get( bank ) + 1UL );
     641             : 
     642           2 :   fd_refresh_vote_accounts( bank,
     643           2 :                             accdb,
     644           2 :                             xid,
     645           2 :                             runtime_stack,
     646           2 :                             stake_delegations,
     647           2 :                             stake_history,
     648           2 :                             new_rate_activation_epoch );
     649             : 
     650           2 : }
     651             : 
     652             : void
     653             : fd_stakes_update_stake_delegation( fd_pubkey_t const *       pubkey,
     654             :                                    fd_account_meta_t const * meta,
     655           0 :                                    fd_bank_t *               bank ) {
     656             : 
     657           0 :   fd_stake_delegations_t * stake_delegations = fd_bank_stake_delegations_modify( bank );
     658             : 
     659           0 :   if( meta->lamports==0UL ) {
     660           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->data->stake_delegations_fork_id, pubkey );
     661           0 :     return;
     662           0 :   }
     663             : 
     664           0 :   fd_stake_state_v2_t stake_state;
     665           0 :   int err = fd_stakes_get_state( meta, &stake_state );
     666           0 :   if( FD_UNLIKELY( err!=0 ) ) {
     667           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->data->stake_delegations_fork_id, pubkey );
     668           0 :     return;
     669           0 :   }
     670             : 
     671           0 :   if( FD_UNLIKELY( !fd_stake_state_v2_is_stake( &stake_state ) ) ) {
     672           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->data->stake_delegations_fork_id, pubkey );
     673           0 :     return;
     674           0 :   }
     675             : 
     676           0 :   if( FD_UNLIKELY( fd_stake_state_v2_is_uninitialized( &stake_state ) ) ) {
     677           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->data->stake_delegations_fork_id, pubkey );
     678           0 :     return;
     679           0 :   }
     680             : 
     681           0 :   if( FD_UNLIKELY( stake_state.inner.stake.stake.delegation.stake==0UL ) ) {
     682           0 :     fd_stake_delegations_fork_remove( stake_delegations, bank->data->stake_delegations_fork_id, pubkey );
     683           0 :     return;
     684           0 :   }
     685             : 
     686           0 :   fd_stake_delegations_fork_update( stake_delegations, bank->data->stake_delegations_fork_id, pubkey,
     687           0 :                                     &stake_state.inner.stake.stake.delegation.voter_pubkey,
     688           0 :                                     stake_state.inner.stake.stake.delegation.stake,
     689           0 :                                     stake_state.inner.stake.stake.delegation.activation_epoch,
     690           0 :                                     stake_state.inner.stake.stake.delegation.deactivation_epoch,
     691           0 :                                     stake_state.inner.stake.stake.credits_observed,
     692           0 :                                     stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
     693           0 : }

Generated by: LCOV version 1.14