LCOV - code coverage report
Current view: top level - flamenco/stakes - fd_stakes.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 280 498 56.2 %
Date: 2026-03-30 06:35:28 Functions: 12 16 75.0 %

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

Generated by: LCOV version 1.14