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

Generated by: LCOV version 1.14