LCOV - code coverage report
Current view: top level - flamenco/runtime/program/vote - fd_vote_state_versioned.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 303 530 57.2 %
Date: 2026-03-19 18:19:27 Functions: 28 35 80.0 %

          Line data    Source code
       1             : #include "../fd_vote_program.h"
       2             : #include "fd_vote_state_versioned.h"
       3             : #include "fd_vote_common.h"
       4             : #include "fd_vote_lockout.h"
       5             : #include "fd_vote_state_v3.h"
       6             : #include "fd_vote_state_v4.h"
       7             : #include "fd_authorized_voters.h"
       8             : #include "../../fd_runtime.h"
       9             : 
      10             : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L42 */
      11             : #define DEFAULT_PRIOR_VOTERS_OFFSET 114
      12             : 
      13             : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L886 */
      14             : #define VERSION_OFFSET (4UL)
      15             : 
      16             : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L887 */
      17             : #define DEFAULT_PRIOR_VOTERS_END (118)
      18             : 
      19             : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L6 */
      20             : #define DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 (82UL)
      21             : 
      22             : /* https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/vote_state_1_14_11.rs#L60 */
      23             : #define DEFAULT_PRIOR_VOTERS_END_1_14_11 (86UL)
      24             : 
      25             : /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L780-L785 */
      26             : static inline fd_vote_lockout_t *
      27           0 : last_lockout( fd_vote_state_versioned_t * self ) {
      28           0 :   fd_landed_vote_t * votes = NULL;
      29           0 :   switch( self->discriminant ) {
      30           0 :     case fd_vote_state_versioned_enum_v3:
      31           0 :       votes = self->inner.v3.votes;
      32           0 :       break;
      33           0 :     case fd_vote_state_versioned_enum_v4:
      34           0 :       votes = self->inner.v4.votes;
      35           0 :       break;
      36           0 :     default:
      37           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
      38           0 :   }
      39             : 
      40           0 :   if( deq_fd_landed_vote_t_empty( votes ) ) return NULL;
      41           0 :   fd_landed_vote_t * last_vote = deq_fd_landed_vote_t_peek_tail( votes );
      42           0 :   return &last_vote->lockout;
      43           0 : }
      44             : 
      45             : /**********************************************************************/
      46             : /* Getters                                                            */
      47             : /**********************************************************************/
      48             : 
      49             : int
      50             : fd_vsv_get_state( fd_account_meta_t const * meta,
      51        1324 :                   uchar *                   vote_state_mem ) {
      52             : 
      53        1324 :   fd_bincode_decode_ctx_t decode = {
      54        1324 :     .data    = fd_account_data( meta ),
      55        1324 :     .dataend = fd_account_data( meta ) + meta->dlen,
      56        1324 :   };
      57             : 
      58        1324 :   ulong total_sz = 0UL;
      59        1324 :   int err = fd_vote_state_versioned_decode_footprint( &decode, &total_sz );
      60        1324 :   if( FD_UNLIKELY( err ) ) {
      61         448 :     return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
      62         448 :   }
      63             : 
      64         876 :   FD_TEST( total_sz<=FD_VOTE_STATE_VERSIONED_FOOTPRINT );
      65             : 
      66         876 :   fd_vote_state_versioned_decode( vote_state_mem, &decode );
      67             : 
      68         876 :   return FD_EXECUTOR_INSTR_SUCCESS;
      69         876 : }
      70             : 
      71             : int
      72             : fd_vsv_deserialize( fd_account_meta_t const * meta,
      73         300 :                     uchar *                   vote_state_mem ) {
      74         300 :   int rc = fd_vsv_get_state( meta, vote_state_mem );
      75         300 :   if( FD_UNLIKELY( rc ) ) {
      76           0 :     return rc;
      77           0 :   }
      78             : 
      79         300 :   fd_vote_state_versioned_t * versioned = (fd_vote_state_versioned_t *)vote_state_mem;
      80         300 :   if( FD_UNLIKELY( versioned->discriminant==fd_vote_state_versioned_enum_uninitialized ) ) {
      81           0 :     return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
      82           0 :   }
      83             : 
      84         300 :   return FD_EXECUTOR_INSTR_SUCCESS;
      85         300 : }
      86             : 
      87             : fd_pubkey_t const *
      88         160 : fd_vsv_get_authorized_withdrawer( fd_vote_state_versioned_t * self ) {
      89         160 :   switch( self->discriminant ) {
      90           0 :     case fd_vote_state_versioned_enum_v1_14_11:
      91           0 :       return &self->inner.v1_14_11.authorized_withdrawer;
      92         160 :     case fd_vote_state_versioned_enum_v3:
      93         160 :       return &self->inner.v3.authorized_withdrawer;
      94           0 :     case fd_vote_state_versioned_enum_v4:
      95           0 :       return &self->inner.v4.authorized_withdrawer;
      96           0 :     default:
      97           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
      98         160 :   }
      99         160 : }
     100             : 
     101             : uchar
     102          25 : fd_vsv_get_commission( fd_vote_state_versioned_t * self ) {
     103          25 :   switch( self->discriminant ) {
     104          25 :     case fd_vote_state_versioned_enum_v3:
     105          25 :       return self->inner.v3.commission;
     106           0 :     case fd_vote_state_versioned_enum_v4:
     107           0 :       return (uchar)(self->inner.v4.inflation_rewards_commission_bps/100);
     108           0 :     default:
     109           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     110          25 :   }
     111          25 : }
     112             : 
     113             : fd_vote_epoch_credits_t const *
     114           7 : fd_vsv_get_epoch_credits( fd_vote_state_versioned_t * self ) {
     115           7 :   return fd_vsv_get_epoch_credits_mutable( self );
     116           7 : }
     117             : 
     118             : fd_landed_vote_t const *
     119         303 : fd_vsv_get_votes( fd_vote_state_versioned_t * self ) {
     120         303 :   return fd_vsv_get_votes_mutable( self );
     121         303 : }
     122             : 
     123             : ulong const *
     124           0 : fd_vsv_get_last_voted_slot( fd_vote_state_versioned_t * self ) {
     125           0 :   fd_vote_lockout_t * last_lockout_ = last_lockout( self );
     126           0 :   if( FD_UNLIKELY( !last_lockout_ ) ) return NULL;
     127           0 :   return &last_lockout_->slot;
     128           0 : }
     129             : 
     130             : ulong const *
     131         606 : fd_vsv_get_root_slot( fd_vote_state_versioned_t * self ) {
     132         606 :   switch( self->discriminant ) {
     133           7 :     case fd_vote_state_versioned_enum_v3:
     134           7 :       if( !self->inner.v3.has_root_slot ) return NULL;
     135           6 :       return &self->inner.v3.root_slot;
     136         599 :     case fd_vote_state_versioned_enum_v4:
     137         599 :       if( !self->inner.v4.has_root_slot ) return NULL;
     138         599 :       return &self->inner.v4.root_slot;
     139           0 :     default:
     140           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     141         606 :   }
     142         606 : }
     143             : 
     144             : fd_vote_block_timestamp_t const *
     145         303 : fd_vsv_get_last_timestamp( fd_vote_state_versioned_t * self ) {
     146         303 :   switch( self->discriminant ) {
     147           3 :     case fd_vote_state_versioned_enum_v3:
     148           3 :       return &self->inner.v3.last_timestamp;
     149         300 :     case fd_vote_state_versioned_enum_v4:
     150         300 :       return &self->inner.v4.last_timestamp;
     151           0 :     default:
     152           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     153         303 :   }
     154         303 : }
     155             : 
     156             : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L938 */
     157             : int
     158           0 : fd_vsv_has_bls_pubkey( fd_vote_state_versioned_t * self ) {
     159             :   /* Implementation slightly simplified */
     160           0 :   switch( self->discriminant ) {
     161           0 :     case fd_vote_state_versioned_enum_uninitialized:
     162           0 :       return 0;
     163           0 :     case fd_vote_state_versioned_enum_v1_14_11:
     164           0 :       return 0;
     165           0 :     case fd_vote_state_versioned_enum_v3:
     166             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L483 */
     167           0 :       return 0;
     168           0 :     case fd_vote_state_versioned_enum_v4:
     169             :       /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L676 */
     170           0 :       return !!self->inner.v4.has_bls_pubkey_compressed;
     171           0 :     default:
     172           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     173           0 :   }
     174           0 : }
     175             : 
     176             : /* https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/programs/vote/src/vote_state/handler.rs#L823-L828 */
     177             : ulong
     178          14 : fd_vsv_get_pending_delegator_rewards( fd_vote_state_versioned_t * self ) {
     179          14 :   switch( self->discriminant ) {
     180          14 :     case fd_vote_state_versioned_enum_v3:
     181          14 :       return 0UL;
     182           0 :     case fd_vote_state_versioned_enum_v4:
     183           0 :       return self->inner.v4.pending_delegator_rewards;
     184           0 :     default:
     185           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     186          14 :   }
     187          14 : }
     188             : 
     189             : /**********************************************************************/
     190             : /* Mutable getters                                                    */
     191             : /**********************************************************************/
     192             : 
     193             : fd_vote_epoch_credits_t *
     194         310 : fd_vsv_get_epoch_credits_mutable( fd_vote_state_versioned_t * self ) {
     195         310 :   switch( self->discriminant ) {
     196          10 :     case fd_vote_state_versioned_enum_v3:
     197          10 :       return self->inner.v3.epoch_credits;
     198         300 :     case fd_vote_state_versioned_enum_v4:
     199         300 :       return self->inner.v4.epoch_credits;
     200           0 :     default:
     201           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     202         310 :   }
     203         310 : }
     204             : 
     205             : fd_landed_vote_t *
     206         606 : fd_vsv_get_votes_mutable( fd_vote_state_versioned_t * self ) {
     207         606 :   switch( self->discriminant ) {
     208           7 :     case fd_vote_state_versioned_enum_v3:
     209           7 :       return self->inner.v3.votes;
     210         599 :     case fd_vote_state_versioned_enum_v4:
     211         599 :       return self->inner.v4.votes;
     212           0 :     default:
     213           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     214         606 :   }
     215         606 : }
     216             : 
     217             : /**********************************************************************/
     218             : /* Setters                                                            */
     219             : /**********************************************************************/
     220             : 
     221             : int
     222             : fd_vsv_set_state( fd_borrowed_account_t *     self,
     223         362 :                   fd_vote_state_versioned_t * state ) {
     224             :   /* https://github.com/anza-xyz/agave/blob/v2.1.14/sdk/src/transaction_context.rs#L974 */
     225         362 :   uchar * data = NULL;
     226         362 :   ulong   dlen = 0UL;
     227         362 :   int err = fd_borrowed_account_get_data_mut( self, &data, &dlen );
     228         362 :   if( FD_UNLIKELY( err ) ) {
     229          10 :     return err;
     230          10 :   }
     231             : 
     232             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L978
     233         352 :   ulong serialized_size = fd_vote_state_versioned_size( state );
     234         352 :   if( FD_UNLIKELY( serialized_size > dlen ) )
     235           2 :     return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
     236             : 
     237             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/src/transaction_context.rs#L983
     238         350 :   fd_bincode_encode_ctx_t encode =
     239         350 :     { .data    = data,
     240         350 :       .dataend = data + dlen };
     241         350 :   do {
     242         350 :     int err = fd_vote_state_versioned_encode( state, &encode );
     243         350 :     if( FD_UNLIKELY( err ) ) FD_LOG_CRIT(( "fd_vote_state_versioned_encode failed (%d)", err ));
     244         350 :   } while(0);
     245             : 
     246         350 :   return FD_EXECUTOR_INSTR_SUCCESS;
     247         350 : }
     248             : 
     249             : int
     250             : fd_vsv_set_vote_account_state( fd_exec_instr_ctx_t const * ctx,
     251             :                                fd_borrowed_account_t *     vote_account,
     252             :                                fd_vote_state_versioned_t * versioned,
     253         353 :                                uchar *                     vote_lockout_mem ) {
     254         353 :   switch( versioned->discriminant ) {
     255          53 :     case fd_vote_state_versioned_enum_v3:
     256          53 :       return fd_vote_state_v3_set_vote_account_state( ctx, vote_account, versioned, vote_lockout_mem );
     257         300 :     case fd_vote_state_versioned_enum_v4:
     258         300 :       return fd_vote_state_v4_set_vote_account_state( ctx, vote_account, versioned );
     259           0 :     default:
     260           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", versioned->discriminant ));
     261         353 :   }
     262         353 : }
     263             : 
     264             : void
     265             : fd_vsv_set_authorized_withdrawer( fd_vote_state_versioned_t * self,
     266          19 :                                   fd_pubkey_t const *         authorized_withdrawer ) {
     267          19 :   switch( self->discriminant ) {
     268          19 :     case fd_vote_state_versioned_enum_v3: {
     269          19 :       self->inner.v3.authorized_withdrawer = *authorized_withdrawer;
     270          19 :       break;
     271           0 :     }
     272           0 :     case fd_vote_state_versioned_enum_v4: {
     273           0 :       self->inner.v4.authorized_withdrawer = *authorized_withdrawer;
     274           0 :       break;
     275           0 :     }
     276           0 :     default:
     277           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     278          19 :   }
     279          19 : }
     280             : 
     281             : int
     282             : fd_vsv_set_new_authorized_voter( fd_exec_instr_ctx_t *              ctx,
     283             :                                  fd_vote_state_versioned_t *        self,
     284             :                                  fd_pubkey_t const *                authorized_pubkey,
     285             :                                  ulong                              current_epoch,
     286             :                                  ulong                              target_epoch,
     287             :                                  fd_bls_pubkey_compressed_t const * bls_pubkey,
     288             :                                  int                                authorized_withdrawer_signer,
     289             :                                  fd_pubkey_t const *                signers[ FD_TXN_SIG_MAX ],
     290          31 :                                  ulong                              signers_cnt ) {
     291          31 :   switch( self->discriminant ) {
     292          31 :     case fd_vote_state_versioned_enum_v3:
     293          31 :       return fd_vote_state_v3_set_new_authorized_voter(
     294          31 :           ctx,
     295          31 :           &self->inner.v3,
     296          31 :           authorized_pubkey,
     297          31 :           current_epoch,
     298          31 :           target_epoch,
     299          31 :           bls_pubkey,
     300          31 :           authorized_withdrawer_signer,
     301          31 :           signers,
     302          31 :           signers_cnt
     303          31 :       );
     304           0 :     case fd_vote_state_versioned_enum_v4:
     305           0 :       return fd_vote_state_v4_set_new_authorized_voter(
     306           0 :           ctx,
     307           0 :           &self->inner.v4,
     308           0 :           authorized_pubkey,
     309           0 :           current_epoch,
     310           0 :           target_epoch,
     311           0 :           bls_pubkey,
     312           0 :           authorized_withdrawer_signer,
     313           0 :           signers,
     314           0 :           signers_cnt
     315           0 :       );
     316           0 :     default:
     317           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     318          31 :   }
     319          31 : }
     320             : 
     321             : void
     322             : fd_vsv_set_node_pubkey( fd_vote_state_versioned_t * self,
     323          11 :                         fd_pubkey_t const *         node_pubkey ) {
     324          11 :   switch( self->discriminant ) {
     325          11 :     case fd_vote_state_versioned_enum_v3:
     326          11 :       self->inner.v3.node_pubkey = *node_pubkey;
     327          11 :       break;
     328           0 :     case fd_vote_state_versioned_enum_v4:
     329           0 :       self->inner.v4.node_pubkey = *node_pubkey;
     330           0 :       break;
     331           0 :     default:
     332           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     333          11 :   }
     334          11 : }
     335             : 
     336             : void
     337             : fd_vsv_set_block_revenue_collector( fd_vote_state_versioned_t * self,
     338          11 :                                     fd_pubkey_t const *         block_revenue_collector ) {
     339          11 :   switch( self->discriminant ) {
     340           0 :     case fd_vote_state_versioned_enum_v4:
     341           0 :       self->inner.v4.block_revenue_collector = *block_revenue_collector;
     342           0 :       break;
     343          11 :     case fd_vote_state_versioned_enum_v3:
     344             :       /* No-op for v3 */
     345          11 :       break;
     346           0 :     default:
     347           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     348          11 :   }
     349          11 : }
     350             : 
     351             : void
     352             : fd_vsv_set_commission( fd_vote_state_versioned_t * self,
     353          10 :                        uchar                       commission ) {
     354          10 :   switch( self->discriminant ) {
     355          10 :     case fd_vote_state_versioned_enum_v3:
     356          10 :       self->inner.v3.commission = commission;
     357          10 :       break;
     358           0 :     case fd_vote_state_versioned_enum_v4:
     359           0 :       self->inner.v4.inflation_rewards_commission_bps = (ushort)( commission*100 );
     360           0 :       break;
     361           0 :     default:
     362           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     363          10 :   }
     364          10 : }
     365             : 
     366             : void
     367         303 : fd_vsv_set_root_slot( fd_vote_state_versioned_t * self, ulong * root_slot ) {
     368         303 :   switch( self->discriminant ) {
     369           3 :     case fd_vote_state_versioned_enum_v3:
     370           3 :       self->inner.v3.has_root_slot = (root_slot!=NULL);
     371           3 :       if( FD_LIKELY( root_slot ) ) {
     372           3 :         self->inner.v3.root_slot = *root_slot;
     373           3 :       }
     374           3 :       break;
     375         300 :     case fd_vote_state_versioned_enum_v4:
     376         300 :       self->inner.v4.has_root_slot = (root_slot!=NULL);
     377         300 :       if( FD_LIKELY( root_slot ) ) {
     378         300 :         self->inner.v4.root_slot = *root_slot;
     379         300 :       }
     380         300 :       break;
     381           0 :     default:
     382           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     383         303 :   }
     384         303 : }
     385             : 
     386             : static void
     387             : fd_vsv_set_last_timestamp( fd_vote_state_versioned_t *       self,
     388         303 :                            fd_vote_block_timestamp_t const * last_timestamp ) {
     389         303 :   switch( self->discriminant ) {
     390           3 :     case fd_vote_state_versioned_enum_v3:
     391           3 :       self->inner.v3.last_timestamp = *last_timestamp;
     392           3 :       break;
     393         300 :     case fd_vote_state_versioned_enum_v4:
     394         300 :       self->inner.v4.last_timestamp = *last_timestamp;
     395         300 :       break;
     396           0 :     default:
     397           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     398         303 :   }
     399         303 : }
     400             : 
     401             : /**********************************************************************/
     402             : /* General functions                                                  */
     403             : /**********************************************************************/
     404             : 
     405             : // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L855
     406             : static void
     407           0 : double_lockouts( fd_vote_state_versioned_t * self ) {
     408           0 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
     409             : 
     410             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L856
     411           0 :   ulong stack_depth = deq_fd_landed_vote_t_cnt( votes );
     412           0 :   ulong i           = 0;
     413             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L857
     414           0 :   for( deq_fd_landed_vote_t_iter_t iter = deq_fd_landed_vote_t_iter_init( votes );
     415           0 :        !deq_fd_landed_vote_t_iter_done( votes, iter );
     416           0 :        iter = deq_fd_landed_vote_t_iter_next( votes, iter ) ) {
     417           0 :     fd_landed_vote_t * v = deq_fd_landed_vote_t_iter_ele( votes, iter );
     418             :     // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L860
     419           0 :     if( stack_depth >
     420           0 :         fd_ulong_checked_add_expect(
     421           0 :             i,
     422           0 :             (ulong)v->lockout.confirmation_count,
     423           0 :             "`confirmation_count` and tower_size should be bounded by `MAX_LOCKOUT_HISTORY`" ) )
     424           0 :       {
     425             :         // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L864
     426           0 :         fd_vote_lockout_increase_confirmation_count( &v->lockout, 1 );
     427           0 :       }
     428           0 :     i++;
     429           0 :   }
     430           0 : }
     431             : 
     432             : void
     433             : fd_vsv_increment_credits( fd_vote_state_versioned_t * self,
     434             :                           ulong                       epoch,
     435         303 :                           ulong                       credits ) {
     436         303 :   fd_vote_epoch_credits_t * epoch_credits = fd_vsv_get_epoch_credits_mutable( self );
     437             : 
     438             :   /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L305 */
     439         303 :   if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_empty( epoch_credits ) ) ) {
     440             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L286-L288 */
     441           0 :     deq_fd_vote_epoch_credits_t_push_tail_wrap(
     442           0 :         epoch_credits,
     443           0 :         ( fd_vote_epoch_credits_t ){ .epoch = epoch, .credits = 0, .prev_credits = 0 } );
     444         303 :   } else if( FD_LIKELY( epoch !=
     445         303 :                         deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch ) ) {
     446             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L290 */
     447           5 :     fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits );
     448             : 
     449           5 :     ulong credits      = last->credits;
     450           5 :     ulong prev_credits = last->prev_credits;
     451             : 
     452             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L292-L299 */
     453           5 :     if( FD_LIKELY( credits!=prev_credits ) ) {
     454           2 :       if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>=MAX_EPOCH_CREDITS_HISTORY ) ) {
     455             :         /* Although Agave performs a `.remove(0)` AFTER the call to
     456             :           `.push()`, there is an edge case where the epoch credits is
     457             :           full, making the call to `_push_tail()` unsafe. Since Agave's
     458             :           structures are dynamically allocated, it is safe for them to
     459             :           simply call `.push()` and then popping afterwards. We have to
     460             :           reverse the order of operations to maintain correct behavior
     461             :           and avoid overflowing the deque.
     462             :           https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L303 */
     463           0 :         deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
     464           0 :       }
     465             : 
     466             :       /* This will not fail because we already popped if we're at
     467             :          capacity, since the epoch_credits deque is allocated with a
     468             :          minimum capacity of MAX_EPOCH_CREDITS_HISTORY. */
     469           2 :       deq_fd_vote_epoch_credits_t_push_tail(
     470           2 :           epoch_credits,
     471           2 :           ( fd_vote_epoch_credits_t ){
     472           2 :               .epoch = epoch, .credits = credits, .prev_credits = credits } );
     473           3 :     } else {
     474             :       /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v3.0.0/vote-interface/src/state/vote_state_v3.rs#L297-L298 */
     475           3 :       deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->epoch = epoch;
     476             : 
     477             :       /* Here we can perform the same deque size check and pop if
     478             :          we're beyond the maximum epoch credits len. */
     479           3 :       if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_cnt( epoch_credits )>MAX_EPOCH_CREDITS_HISTORY ) ) {
     480           0 :         deq_fd_vote_epoch_credits_t_pop_head( epoch_credits );
     481           0 :       }
     482           3 :     }
     483           5 :   }
     484             : 
     485             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L663
     486         303 :   deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits = fd_ulong_sat_add(
     487         303 :       deq_fd_vote_epoch_credits_t_peek_tail( epoch_credits )->credits, credits );
     488         303 : }
     489             : 
     490             : int
     491             : fd_vsv_process_timestamp( fd_exec_instr_ctx_t *       ctx,
     492             :                           fd_vote_state_versioned_t * self,
     493             :                           ulong                       slot,
     494         303 :                           long                        timestamp ) {
     495             :   /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L160 */
     496         303 :   fd_vote_block_timestamp_t const * last_timestamp = fd_vsv_get_last_timestamp( self );
     497         303 :   if( FD_UNLIKELY(
     498         303 :           ( slot<last_timestamp->slot || timestamp<last_timestamp->timestamp ) ||
     499         303 :           ( slot==last_timestamp->slot &&
     500         303 :             ( slot!=last_timestamp->slot || timestamp!=last_timestamp->timestamp ) &&
     501         303 :             last_timestamp->slot!=0UL ) ) ) {
     502           0 :     ctx->txn_out->err.custom_err = FD_VOTE_ERR_TIMESTAMP_TOO_OLD;
     503           0 :     return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
     504           0 :   }
     505             : 
     506             :   /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L168 */
     507         303 :   fd_vote_block_timestamp_t new_timestamp = {
     508         303 :     .slot = slot,
     509         303 :     .timestamp = timestamp,
     510         303 :   };
     511         303 :   fd_vsv_set_last_timestamp( self, &new_timestamp );
     512         303 :   return FD_EXECUTOR_INSTR_SUCCESS;
     513         303 : }
     514             : 
     515             : void
     516           0 : fd_vsv_pop_expired_votes( fd_vote_state_versioned_t * self, ulong next_vote_slot ) {
     517           0 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
     518             : 
     519           0 :   while( !deq_fd_landed_vote_t_empty( votes ) ) {
     520           0 :     fd_landed_vote_t * vote = deq_fd_landed_vote_t_peek_tail( votes );
     521           0 :     if( !( fd_vote_lockout_is_locked_out_at_slot( &vote->lockout, next_vote_slot ) ) ) {
     522           0 :       deq_fd_landed_vote_t_pop_tail( votes );
     523           0 :     } else {
     524           0 :       break;
     525           0 :     }
     526           0 :   }
     527           0 : }
     528             : 
     529             : void
     530             : fd_vsv_process_next_vote_slot( fd_vote_state_versioned_t * self,
     531             :                                ulong                       next_vote_slot,
     532             :                                ulong                       epoch,
     533           0 :                                ulong                       current_slot ) {
     534           0 :   ulong const * last_voted_slot_ = fd_vsv_get_last_voted_slot( self );
     535           0 :   if( FD_UNLIKELY( last_voted_slot_ && next_vote_slot <= *last_voted_slot_ ) ) return;
     536             : 
     537           0 :   fd_vsv_pop_expired_votes( self, next_vote_slot );
     538             : 
     539           0 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( self );
     540             : 
     541           0 :   fd_landed_vote_t landed_vote = {
     542           0 :     .latency = fd_vote_compute_vote_latency( next_vote_slot, current_slot ),
     543           0 :     .lockout = ( fd_vote_lockout_t ){ .slot = next_vote_slot }
     544           0 :   };
     545             : 
     546             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L623
     547           0 :   if( FD_UNLIKELY( deq_fd_landed_vote_t_cnt( votes ) == MAX_LOCKOUT_HISTORY ) ) {
     548           0 :     ulong            credits     = fd_vote_credits_for_vote_at_index( votes, 0 );
     549           0 :     fd_landed_vote_t landed_vote = deq_fd_landed_vote_t_pop_head( votes );
     550           0 :     fd_vsv_set_root_slot( self, &landed_vote.lockout.slot );
     551             : 
     552           0 :     fd_vsv_increment_credits( self, epoch, credits );
     553           0 :   }
     554             : 
     555             :   // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L634
     556           0 :   deq_fd_landed_vote_t_push_tail_wrap( votes, landed_vote );
     557           0 :   double_lockouts( self );
     558           0 : }
     559             : 
     560             : int
     561             : fd_vsv_try_convert_to_v3( fd_vote_state_versioned_t * self,
     562         171 :                           uchar *                     landed_votes_mem ) {
     563         171 :   switch( self->discriminant ) {
     564             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L47-L73 */
     565           0 :     case fd_vote_state_versioned_enum_uninitialized: {
     566           0 :       return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
     567           0 :     }
     568             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L75-L91 */
     569          70 :     case fd_vote_state_versioned_enum_v1_14_11: {
     570          70 :       fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11;
     571             : 
     572             :       /* Temporary to hold v3 */
     573          70 :       fd_vote_state_v3_t v3 = {
     574          70 :         .node_pubkey            = state->node_pubkey,
     575          70 :         .authorized_withdrawer  = state->authorized_withdrawer,
     576          70 :         .commission             = state->commission,
     577          70 :         .votes                  = fd_vote_lockout_landed_votes_from_lockouts( state->votes, landed_votes_mem ),
     578          70 :         .has_root_slot          = state->has_root_slot,
     579          70 :         .root_slot              = state->root_slot,
     580          70 :         .authorized_voters      = state->authorized_voters,
     581          70 :         .prior_voters           = state->prior_voters,
     582          70 :         .epoch_credits          = state->epoch_credits,
     583          70 :         .last_timestamp         = state->last_timestamp
     584          70 :       };
     585             : 
     586             :       /* Emplace new vote state into target */
     587          70 :       self->discriminant = fd_vote_state_versioned_enum_v3;
     588          70 :       self->inner.v3 = v3;
     589             : 
     590          70 :       return FD_EXECUTOR_INSTR_SUCCESS;
     591           0 :     }
     592             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L93 */
     593         101 :     case fd_vote_state_versioned_enum_v3:
     594         101 :       return FD_EXECUTOR_INSTR_SUCCESS;
     595             :     /* https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_versions.rs#L96 */
     596           0 :     case fd_vote_state_versioned_enum_v4:
     597           0 :       return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
     598           0 :     default:
     599           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     600         171 :   }
     601         171 : }
     602             : 
     603             : int
     604             : fd_vsv_try_convert_to_v4( fd_vote_state_versioned_t * self,
     605             :                           fd_pubkey_t const *         vote_pubkey,
     606         299 :                           uchar *                     landed_votes_mem ) {
     607         299 :   switch( self->discriminant ) {
     608             :     /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L971-L974 */
     609           0 :     case fd_vote_state_versioned_enum_uninitialized: {
     610           0 :       return FD_EXECUTOR_INSTR_ERR_UNINITIALIZED_ACCOUNT;
     611           0 :     }
     612             :     /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L975-L989 */
     613           0 :     case fd_vote_state_versioned_enum_v1_14_11: {
     614           0 :       fd_vote_state_1_14_11_t * state = &self->inner.v1_14_11;
     615           0 :       fd_vote_state_v4_t v4 = {
     616           0 :         .node_pubkey                      = state->node_pubkey,
     617           0 :         .authorized_withdrawer            = state->authorized_withdrawer,
     618           0 :         .inflation_rewards_collector      = *vote_pubkey,
     619           0 :         .block_revenue_collector          = state->node_pubkey,
     620           0 :         .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
     621           0 :         .block_revenue_commission_bps     = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
     622           0 :         .pending_delegator_rewards        = 0,
     623           0 :         .has_bls_pubkey_compressed        = 0,
     624           0 :         .votes                            = fd_vote_lockout_landed_votes_from_lockouts( state->votes, landed_votes_mem ),
     625           0 :         .has_root_slot                    = state->has_root_slot,
     626           0 :         .root_slot                        = state->root_slot,
     627           0 :         .authorized_voters                = state->authorized_voters,
     628           0 :         .epoch_credits                    = state->epoch_credits,
     629           0 :         .last_timestamp                   = state->last_timestamp
     630           0 :       };
     631             : 
     632             :       /* Emplace new vote state into target */
     633           0 :       self->discriminant = fd_vote_state_versioned_enum_v4;
     634           0 :       self->inner.v4     = v4;
     635             : 
     636           0 :       return FD_EXECUTOR_INSTR_SUCCESS;
     637           0 :     }
     638             :     /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L990-L1004 */
     639           1 :     case fd_vote_state_versioned_enum_v3: {
     640           1 :       fd_vote_state_v3_t * state = &self->inner.v3;
     641           1 :       fd_vote_state_v4_t v4 = {
     642           1 :         .node_pubkey                      = state->node_pubkey,
     643           1 :         .authorized_withdrawer            = state->authorized_withdrawer,
     644           1 :         .inflation_rewards_collector      = *vote_pubkey,
     645           1 :         .block_revenue_collector          = state->node_pubkey,
     646           1 :         .inflation_rewards_commission_bps = fd_ushort_sat_mul( state->commission, 100 ),
     647           1 :         .block_revenue_commission_bps     = DEFAULT_BLOCK_REVENUE_COMMISSION_BPS,
     648           1 :         .pending_delegator_rewards        = 0,
     649           1 :         .has_bls_pubkey_compressed        = 0,
     650           1 :         .votes                            = state->votes,
     651           1 :         .has_root_slot                    = state->has_root_slot,
     652           1 :         .root_slot                        = state->root_slot,
     653           1 :         .authorized_voters                = state->authorized_voters,
     654           1 :         .epoch_credits                    = state->epoch_credits,
     655           1 :         .last_timestamp                   = state->last_timestamp
     656           1 :       };
     657             : 
     658             :       /* Emplace new vote state into target */
     659           1 :       self->discriminant = fd_vote_state_versioned_enum_v4;
     660           1 :       self->inner.v4     = v4;
     661             : 
     662           1 :       return FD_EXECUTOR_INSTR_SUCCESS;
     663           0 :     }
     664             :     /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L1005 */
     665         298 :     case fd_vote_state_versioned_enum_v4:
     666         298 :       return FD_EXECUTOR_INSTR_SUCCESS;
     667           0 :     default:
     668           0 :       FD_LOG_CRIT(( "unsupported vote state version: %u", self->discriminant ));
     669         299 :   }
     670         299 : }
     671             : 
     672             : int
     673             : fd_vsv_deinitialize_vote_account_state( fd_exec_instr_ctx_t *   ctx,
     674             :                                         fd_borrowed_account_t * vote_account,
     675             :                                         int                     target_version,
     676           7 :                                         uchar *                 vote_lockout_mem ) {
     677           7 :   switch( target_version ) {
     678           7 :     case VOTE_STATE_TARGET_VERSION_V3: {
     679             :       /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L878 */
     680           7 :       fd_vote_state_versioned_t versioned;
     681           7 :       fd_vote_state_versioned_new_disc( &versioned, fd_vote_state_versioned_enum_v3 );
     682           7 :       versioned.inner.v3.prior_voters.idx      = 31;
     683           7 :       versioned.inner.v3.prior_voters.is_empty = 1;
     684           7 :       return fd_vote_state_v3_set_vote_account_state(
     685           7 :           ctx,
     686           7 :           vote_account,
     687           7 :           &versioned,
     688           7 :           vote_lockout_mem
     689           7 :       );
     690           0 :     }
     691           0 :     case VOTE_STATE_TARGET_VERSION_V4: {
     692             :       /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/handler.rs#L881-L883 */
     693           0 :       uchar * data;
     694           0 :       ulong   dlen;
     695           0 :       int rc = fd_borrowed_account_get_data_mut( vote_account, &data, &dlen );
     696           0 :       if( FD_UNLIKELY( rc ) ) return rc;
     697           0 :       fd_memset( data, 0, dlen );
     698           0 :       return FD_EXECUTOR_INSTR_SUCCESS;
     699           0 :     }
     700           0 :     default:
     701           0 :       FD_LOG_CRIT(( "unsupported target version" ));
     702           7 :   }
     703           7 : }
     704             : 
     705             : int
     706         309 : fd_vsv_is_uninitialized( fd_vote_state_versioned_t * self ) {
     707         309 :   switch( self->discriminant ) {
     708           2 :     case fd_vote_state_versioned_enum_uninitialized:
     709           2 :       return 1;
     710           0 :     case fd_vote_state_versioned_enum_v1_14_11:
     711           0 :       return fd_authorized_voters_is_empty( &self->inner.v1_14_11.authorized_voters );
     712           9 :     case fd_vote_state_versioned_enum_v3:
     713           9 :       return fd_authorized_voters_is_empty( &self->inner.v3.authorized_voters );
     714         298 :     case fd_vote_state_versioned_enum_v4:
     715         298 :       return 0; // v4 vote states are always initialized
     716           0 :     default:
     717           0 :       FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", self->discriminant ));
     718         309 :   }
     719         309 : }
     720             : 
     721             : int
     722         600 : fd_vsv_is_correct_size_and_initialized( fd_account_meta_t const * meta ) {
     723         600 :   uchar const * data     = fd_account_data( meta );
     724         600 :   ulong         data_len = meta->dlen;
     725         600 :   uint const *  disc_ptr = (uint const *)data; // NOT SAFE TO ACCESS YET!
     726             : 
     727             :   /* VoteStateV4::is_correct_size_and_initialized
     728             :      https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v4.rs#L207-L210 */
     729         600 :   if( FD_LIKELY( data_len==FD_VOTE_STATE_V4_SZ && *disc_ptr==fd_vote_state_versioned_enum_v4 ) ) {
     730         600 :     return 1;
     731         600 :   }
     732             : 
     733             :   /* VoteStateV3::is_correct_size_and_initialized
     734             :      https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_v3.rs#L509-L514 */
     735           0 :   if( FD_LIKELY( data_len==FD_VOTE_STATE_V3_SZ &&
     736           0 :                  !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET ) ) ) {
     737           0 :     return 1;
     738           0 :   }
     739             : 
     740             :   /* VoteState1_14_11::is_correct_size_and_initialized
     741             :      https://github.com/anza-xyz/solana-sdk/blob/vote-interface%40v4.0.4/vote-interface/src/state/vote_state_1_14_11.rs#L63-L69 */
     742           0 :   if( FD_LIKELY( data_len==FD_VOTE_STATE_V2_SZ &&
     743           0 :                  !fd_mem_iszero( data+VERSION_OFFSET, DEFAULT_PRIOR_VOTERS_OFFSET_1_14_11 ) ) ) {
     744           0 :     return 1;
     745           0 :   }
     746             : 
     747           0 :   return 0;
     748           0 : }
     749             : 
     750             : fd_vote_block_timestamp_t
     751             : fd_vsv_get_vote_block_timestamp( uchar const * data,
     752         598 :                                  ulong         data_len ) {
     753             : 
     754             :   /* TODO: A smarter/safer xray should be used here instead of the
     755             :      fd_types xray functions + an offset. */
     756         598 :   fd_bincode_decode_ctx_t ctx = {
     757         598 :     .data    = data,
     758         598 :     .dataend = data + data_len,
     759         598 :   };
     760             : 
     761         598 :   FD_TEST( data_len>=16UL );
     762             : 
     763             :   /* The vote block timestamp are always the last 16 bytes of the vote
     764             :      account data. */
     765             : 
     766         598 :   fd_vote_state_versioned_seek_end( &ctx );
     767         598 :   uchar * data_ptr = (uchar *)ctx.data;
     768         598 :   return *(fd_vote_block_timestamp_t *)(data_ptr-16UL);
     769         598 : }
     770             : 
     771             : fd_pubkey_t
     772           0 : fd_vsv_get_node_account( uchar const * data ) {
     773           0 :   fd_pubkey_t pubkey;
     774           0 :   memcpy( &pubkey, data + sizeof(uint), sizeof(fd_pubkey_t) );
     775           0 :   return pubkey;
     776           0 : }

Generated by: LCOV version 1.14