LCOV - code coverage report
Current view: top level - flamenco/alpenglow - fd_alpenglow.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 27 579 4.7 %
Date: 2026-11-11 12:40:43 Functions: 3 43 7.0 %

          Line data    Source code
       1             : #include "fd_alpenglow.h"
       2             : #include "fd_vote_queue.h"
       3             : #include "../rewards/fd_epoch_inflation_account.h"
       4             : 
       5             : #include "../runtime/fd_hashes.h"
       6             : #include "../runtime/fd_pubkey_utils.h"
       7             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       8             : #include "../runtime/program/vote/fd_vote_codec_tmpl.h"
       9             : #include "../runtime/sysvar/fd_sysvar_clock.h"
      10             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
      11             : 
      12             : FD_STATIC_ASSERT( MAX_EPOCH_CREDITS_HISTORY==64UL, epoch_credits_bound );
      13             : 
      14             : static int
      15             : vote_stakes_iter_kind_for_epoch( fd_bank_t const * bank,
      16           0 :                                  ulong             epoch ) {
      17           0 :   ulong t_1_epoch = fd_vote_stakes_fork_epoch( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id )+1UL;
      18           0 :   if( FD_UNLIKELY( epoch>t_1_epoch || t_1_epoch-epoch>(ulong)(FD_VOTE_STAKES_ITER_T_5-FD_VOTE_STAKES_ITER_T_1) ) ) return 0;
      19           0 :   return FD_VOTE_STAKES_ITER_T_1+(int)(t_1_epoch-epoch);
      20           0 : }
      21             : 
      22             : /* Footer cert verification */
      23             : 
      24             : /* TBD: this is a copy of the votor's fd_vote_signing_ser so the runtime
      25             :    does not link fd_choreo; keep the two in sync. */
      26             : 
      27           0 : #define VOTE_TAG_NOTAR (1U) /* WireConsensusMessageKind::NotarVote    */
      28             : #define VOTE_TAG_FINAL (2U) /* WireConsensusMessageKind::FinalizeVote */
      29             : #define VOTE_TAG_SKIP  (3U) /* WireConsensusMessageKind::SkipVote     */
      30             : 
      31             : #define VOTE_SIGNING_SER_MAX ( sizeof(uchar) + sizeof(ulong) + sizeof(fd_hash_t) + sizeof(ushort) )
      32             : FD_STATIC_ASSERT( VOTE_SIGNING_SER_MAX<=FD_FOOTER_CACHE_MSG_MAX,       footer_cache_msg_max  );
      33             : FD_STATIC_ASSERT( VOTE_SIGNING_SER_MAX==FD_ALPENGLOW_CERT_PAYLOAD_MAX, cert_payload_max      );
      34             : 
      35             : static ulong
      36             : vote_signing_ser( uint          tag,
      37             :                   ulong         slot,
      38             :                   uchar const * block_hash,
      39             :                   ushort        shred_version,
      40           0 :                   uchar         buf[ static VOTE_SIGNING_SER_MAX ] ) {
      41           0 :   ulong off = 0UL;
      42           0 :   buf[ off ] = (uchar)tag;                                                          off += sizeof(uchar);
      43           0 :   FD_STORE( ulong, buf+off, slot );                                                 off += sizeof(ulong);
      44           0 :   if( FD_LIKELY( block_hash ) ) { memcpy( buf+off, block_hash, sizeof(fd_hash_t) ); off += sizeof(fd_hash_t); }
      45           0 :   FD_STORE( ushort, buf+off, shred_version );                                       off += sizeof(ushort);
      46           0 :   return off;
      47           0 : }
      48             : 
      49             : struct validator_set {
      50             :   ulong          epoch;
      51             :   ulong          validator_cnt;                /* 0 if not built */
      52             :   ulong          total_stake;
      53             :   fd_bls_pub_t   key_sum;                      /* sum of all keys, unused for a t-1 set */
      54             :   fd_bls_pub_t   bls_keys[ FD_VOTOR_VAT_MAX ]; /* indexed by rank */
      55             :   ulong          stakes  [ FD_VOTOR_VAT_MAX ]; /* indexed by rank */
      56             : };
      57             : typedef struct validator_set validator_set_t;
      58             : 
      59             : /* validator_set_for_slot makes set hold the validators of slot's epoch,
      60             :    rebuilding it only when the epoch differs from the one it holds.  A
      61             :    set built from the t-1 tier is never reused and gets no key sum: t-1
      62             :    is fork specific, so two banks in the same epoch can hold different
      63             :    t-1 sets.  Returns 1 on success, 0 if the fork cannot address the
      64             :    epoch or the bank holds no ranked validators for it. */
      65             : 
      66             : static int
      67             : validator_set_for_slot( validator_set_t * set,
      68             :                         fd_bank_t const * bank,
      69           0 :                         ulong             slot ) {
      70           0 :   ulong  epoch     = fd_slot_to_epoch( &bank->f.epoch_schedule, slot, NULL );
      71           0 :   ushort fork_id   = bank->vote_stakes_fork_id;
      72           0 :   int    iter_kind = vote_stakes_iter_kind_for_epoch( bank, epoch );
      73           0 :   if( FD_UNLIKELY( !iter_kind ) ) {
      74           0 :     FD_LOG_WARNING(( "slot %lu: cert epoch %lu is not t-1 through t-5", bank->f.slot, epoch ));
      75           0 :     return 0;
      76           0 :   }
      77           0 :   if( FD_LIKELY( iter_kind!=FD_VOTE_STAKES_ITER_T_1 && set->validator_cnt && set->epoch==epoch ) ) return 1;
      78             : 
      79           0 :   set->validator_cnt = 0UL;
      80           0 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
      81           0 :   ulong cnt      = 0UL;
      82           0 :   ulong max_rank = 0UL;
      83           0 :   ulong total    = 0UL;
      84           0 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
      85           0 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
      86           0 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
      87           0 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
      88           0 :     fd_vote_stakes_ele_t ele[1];
      89           0 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, ele );
      90           0 :     if( FD_UNLIKELY( ele->rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
      91           0 :     FD_TEST( ele->rank<FD_VOTOR_VAT_MAX );
      92             : 
      93             :     /* finalize subgroup-checked each ranked key while producing this
      94             :        uncompressed encoding, so do not repeat that expensive check here. */
      95           0 :     if( FD_UNLIKELY( fd_bls_pub_de_unchecked( set->bls_keys+ele->rank, ele->bls_key_uncompressed ) ) ) {
      96           0 :       FD_LOG_WARNING(( "slot %lu: rank %u of epoch %lu has a malformed BLS key", bank->f.slot, ele->rank, epoch ));
      97           0 :       return 0;
      98           0 :     }
      99           0 :     set->stakes[ ele->rank ] = ele->stake;
     100           0 :     max_rank = fd_ulong_max( max_rank, (ulong)ele->rank );
     101           0 :     total   += ele->stake;
     102           0 :     cnt++;
     103           0 :   }
     104           0 :   if( FD_UNLIKELY( !cnt || cnt!=max_rank+1UL ) ) {
     105           0 :     FD_LOG_WARNING(( "slot %lu: epoch %lu has %lu ranked validators, highest rank %lu", bank->f.slot, epoch, cnt, max_rank ));
     106           0 :     return 0;
     107           0 :   }
     108           0 :   if( FD_LIKELY( iter_kind!=FD_VOTE_STAKES_ITER_T_1 ) ) {
     109           0 :     fd_bls_set_t all[ fd_bls_set_word_cnt ];
     110           0 :     fd_bls_pub_sum( &set->key_sum, set->bls_keys, fd_bls_set_range( all, 0UL, cnt ) );
     111           0 :   }
     112           0 :   set->epoch         = iter_kind==FD_VOTE_STAKES_ITER_T_1 ? ULONG_MAX : epoch;
     113           0 :   set->validator_cnt = cnt;
     114           0 :   set->total_stake   = total;
     115           0 :   return 1;
     116           0 : }
     117             : 
     118             : ulong
     119             : fd_alpenglow_cert_payload( fd_block_footer_cert_t const * cert,
     120             :                            uint                           vote_tag,
     121             :                            ushort                         shred_version,
     122           0 :                            uchar                          payload[ static FD_ALPENGLOW_CERT_PAYLOAD_MAX ] ) {
     123           0 :   return vote_signing_ser( vote_tag, cert->slot, vote_tag==VOTE_TAG_NOTAR ? cert->block_id.uc : NULL, shred_version, payload );
     124           0 : }
     125             : 
     126             : int
     127             : fd_alpenglow_cert_verify( fd_bank_t const *              bank,
     128             :                           fd_block_footer_cert_t const * cert,
     129             :                           uint                           vote_tag,
     130             :                           ulong                          quorum_numer,
     131             :                           ushort                         shred_version,
     132             :                           fd_footer_cache_t *            cache,
     133             :                           fd_bls_hash_t const *         hm,
     134           0 :                           fd_bls_hash_t *               hm_out ) {
     135           0 :   ulong bank_slot = bank->f.slot;
     136           0 :   ulong cert_slot = cert->slot;
     137             : 
     138           0 :   if( FD_UNLIKELY( !shred_version ) ) {
     139           0 :     FD_LOG_WARNING(( "slot %lu: footer carries certs but the shred version is not known yet; cannot verify them", bank_slot ));
     140           0 :     return -1;
     141           0 :   }
     142             : 
     143             :   /* ~200 KiB, kept across calls: consecutive blocks' certs almost
     144             :      always share a ring epoch; a fork specific t-1 set is not reused */
     145           0 :   static FD_TL validator_set_t set[1];
     146             : 
     147           0 :   if( FD_UNLIKELY( !validator_set_for_slot( set, bank, cert_slot ) ) ) return -1;
     148             : 
     149           0 :   ulong last_rank = fd_bls_set_last( cert->signer_set ); /* ULONG_MAX when empty */
     150           0 :   if( FD_UNLIKELY( cert->nbits>set->validator_cnt || last_rank>=set->validator_cnt ) ) {
     151           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu names %u ranks (highest signer %lu) but its epoch has %lu validators",
     152           0 :                      bank_slot, cert_slot, cert->nbits, last_rank, set->validator_cnt ));
     153           0 :     return -1;
     154           0 :   }
     155             : 
     156           0 :   ulong stake = 0UL;
     157           0 :   for( ulong rank=fd_bls_set_const_iter_init( cert->signer_set ); !fd_bls_set_const_iter_done( rank ); rank=fd_bls_set_const_iter_next( cert->signer_set, rank ) ) {
     158           0 :     stake += set->stakes[ rank ];
     159           0 :   }
     160           0 :   fd_bls_pub_t pub[1];
     161           0 :   fd_bls_pub_sum_cached( pub, set->bls_keys, set->validator_cnt, cert->signer_set,
     162           0 :                          set->epoch!=ULONG_MAX ? &set->key_sum : NULL );
     163           0 :   if( FD_UNLIKELY( quorum_numer && (uint128)stake*(uint128)FD_VOTOR_QUORUM_THRESHOLD_DENOM<(uint128)set->total_stake*(uint128)quorum_numer ) ) {
     164           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has %lu of %lu stake, below %lu/%lu",
     165           0 :                      bank_slot, cert_slot, stake, set->total_stake, quorum_numer, FD_VOTOR_QUORUM_THRESHOLD_DENOM ));
     166           0 :     return -1;
     167           0 :   }
     168             : 
     169           0 :   fd_bls_sig_t sig[1];
     170           0 :   if( FD_UNLIKELY( fd_bls_sig_de_compressed( sig, cert->sig ) ) ) { /* uncompress + g2 subgroup check */
     171           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has a malformed signature", bank_slot, cert_slot ));
     172           0 :     return -1;
     173           0 :   }
     174             : 
     175           0 :   fd_bls_hash_t h[1];
     176           0 :   if( hm ) {
     177           0 :     *h = *hm;
     178           0 :   } else {
     179           0 :     uchar payload[ FD_ALPENGLOW_CERT_PAYLOAD_MAX ];
     180           0 :     ulong payload_sz = fd_alpenglow_cert_payload( cert, vote_tag, shred_version, payload );
     181           0 :     if( cache ) fd_footer_cache_hash_msg( cache, payload, payload_sz, h );
     182           0 :     else        fd_bls_hash_msg( payload, payload_sz, h );
     183           0 :     if( hm_out ) *hm_out = *h;
     184           0 :   }
     185           0 :   if( FD_UNLIKELY( !fd_bls_agg_verify_hashed( h, pub, sig ) ) ) {
     186           0 :     FD_LOG_WARNING(( "slot %lu: footer (is_reward %d) cert for slot %lu failed signature verification", bank_slot, !quorum_numer, cert_slot ));
     187           0 :     return -1;
     188           0 :   }
     189           0 :   return 0;
     190           0 : }
     191             : 
     192             : ulong
     193             : fd_alpenglow_footer_certs( fd_block_footer_t const *      footer,
     194             :                            fd_block_footer_cert_t const * cert  [ static FD_ALPENGLOW_FOOTER_CERT_MAX ],
     195             :                            uint                           tag   [ static FD_ALPENGLOW_FOOTER_CERT_MAX ],
     196         992 :                            ulong                          quorum[ static FD_ALPENGLOW_FOOTER_CERT_MAX ] ) {
     197         992 :   ulong n = 0UL;
     198         992 : # define PUSH(c,t,q) do { cert[ n ] = (c); tag[ n ] = (t); quorum[ n ] = (q); n++; } while(0)
     199         992 :   if( footer->has_fast_final_cert   ) PUSH( &footer->fast_final_cert,   VOTE_TAG_NOTAR, FD_VOTOR_STRONG_QUORUM_THRESHOLD_NUMER );
     200         992 :   if( footer->has_final_cert        ) {
     201           0 :     PUSH( &footer->final_cert, VOTE_TAG_FINAL, FD_VOTOR_QUORUM_THRESHOLD_NUMER );
     202           0 :     PUSH( &footer->notar_cert, VOTE_TAG_NOTAR, FD_VOTOR_QUORUM_THRESHOLD_NUMER );
     203           0 :   }
     204         992 :   if( footer->has_skip_reward_cert  ) PUSH( &footer->skip_reward_cert,  VOTE_TAG_SKIP,  0UL );
     205         992 :   if( footer->has_notar_reward_cert ) PUSH( &footer->notar_reward_cert, VOTE_TAG_NOTAR, 0UL );
     206         992 : # undef PUSH
     207         992 :   return n;
     208         992 : }
     209             : 
     210             : int
     211             : fd_alpenglow_footer_verify( fd_bank_t const *         bank,
     212             :                             fd_block_footer_t const * footer,
     213             :                             ushort                    shred_version,
     214           0 :                             fd_footer_cache_t *       cache ) {
     215           0 :   int has_certs = footer->has_fast_final_cert | footer->has_final_cert | footer->has_skip_reward_cert | footer->has_notar_reward_cert;
     216           0 :   if( FD_LIKELY( !has_certs ) ) return 0;
     217             : 
     218           0 :   fd_block_footer_cert_t const * cert  [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     219           0 :   uint                           tag   [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     220           0 :   ulong                          quorum[ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     221           0 :   ulong                          cnt = fd_alpenglow_footer_certs( footer, cert, tag, quorum );
     222           0 :   for( ulong i=0UL; i<cnt; i++ ) {
     223           0 :     if( FD_UNLIKELY( fd_alpenglow_cert_verify( bank, cert[ i ], tag[ i ], quorum[ i ], shred_version, cache, NULL, NULL ) ) ) return -1;
     224           0 :   }
     225           0 :   return 0;
     226           0 : }
     227             : 
     228             : /* credits_increment mirrors Agave's alpenglow increment_credits
     229             :    (vote_reward.rs): accumulate credits into the tail epoch_credits
     230             :    entry, starting a new entry on epoch changes and inserting the
     231             :    tower->alpenglow migration marker in the migration epoch.  The
     232             :    deque's capacity is MAX_EPOCH_CREDITS_HISTORY+1 while Agave pushes
     233             :    into an unbounded Vec before trimming, so we drop the head early
     234             :    when full; the final trimmed state is the same. */
     235             : 
     236             : static void
     237             : credits_increment( fd_vote_epoch_credits_t * ec,
     238             :                    ulong                     migration_epoch,
     239             :                    ulong                     epoch,
     240           0 :                    ulong                     credits /* nonzero */ ) {
     241           0 :   if( epoch==migration_epoch ) {
     242           0 :     int have_marker = 0;
     243           0 :     for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     244           0 :          !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     245           0 :          iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     246           0 :       if( fd_vote_epoch_credits_is_alpenglow_marker( deq_fd_vote_epoch_credits_t_iter_ele( ec, iter ) ) ) { have_marker = 1; break; }
     247           0 :     }
     248           0 :     if( !have_marker ) {
     249           0 :       if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     250           0 :       deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){ .epoch=ULONG_MAX, .credits=ULONG_MAX, .prev_credits=ULONG_MAX } );
     251           0 :     }
     252           0 :   }
     253             : 
     254           0 :   ulong cnt = deq_fd_vote_epoch_credits_t_cnt( ec );
     255           0 :   if( FD_UNLIKELY( !cnt ) ) {
     256           0 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){ .epoch=epoch, .credits=credits, .prev_credits=0UL } );
     257           0 :     return;
     258           0 :   }
     259             : 
     260           0 :   fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( ec );
     261             : 
     262           0 :   if( fd_vote_epoch_credits_is_alpenglow_marker( last ) ) {
     263             :     /* If there was a tower entry before the marker, its final credits
     264             :        form this entry's initial credits. */
     265           0 :     ulong final_tower_credits = 0UL;
     266           0 :     if( cnt>=2UL ) {
     267           0 :       ulong idx = 0UL;
     268           0 :       for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     269           0 :            !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     270           0 :            iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     271           0 :         if( idx==cnt-2UL ) { final_tower_credits = deq_fd_vote_epoch_credits_t_iter_ele( ec, iter )->credits; break; }
     272           0 :         idx++;
     273           0 :       }
     274           0 :     }
     275           0 :     if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     276           0 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     277           0 :       .epoch        = epoch,
     278           0 :       .credits      = fd_ulong_sat_add( credits, final_tower_credits ),
     279           0 :       .prev_credits = final_tower_credits } );
     280           0 :     while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     281           0 :     return;
     282           0 :   }
     283             : 
     284           0 :   if( last->epoch==epoch ) {
     285           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     286           0 :     return;
     287           0 :   }
     288             : 
     289           0 :   if( last->credits==last->prev_credits ) {
     290             :     /* Different epochs but the latest epoch earned no credits, reuse
     291             :        the entry. */
     292           0 :     last->epoch   = epoch;
     293           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     294           0 :     return;
     295           0 :   }
     296             : 
     297           0 :   ulong final_credits = last->credits;
     298           0 :   if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     299           0 :   deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     300           0 :     .epoch        = epoch,
     301           0 :     .credits      = fd_ulong_sat_add( credits, final_credits ),
     302           0 :     .prev_credits = final_credits } );
     303           0 :   while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     304           0 : }
     305             : 
     306             : static void
     307             : credits_push_v5( fd_vote_state_v5_t *            v5,
     308           0 :                  fd_vote_epoch_credits_t const * ec ) {
     309           0 :   ulong cnt = v5->accumulated_lamports_len;
     310           0 :   if( FD_UNLIKELY( cnt==FD_VOTE_STATE_V5_ACCUMULATED_LAMPORTS_MAX ) ) {
     311           0 :     memmove( v5->accumulated_lamports, v5->accumulated_lamports+1UL, (cnt-1UL)*sizeof(fd_vote_epoch_credits_t) );
     312           0 :     cnt--;
     313           0 :   }
     314           0 :   v5->accumulated_lamports[ cnt ] = *ec;
     315           0 :   v5->accumulated_lamports_len    = (uint)( cnt+1UL );
     316           0 : }
     317             : 
     318             : static void
     319             : credits_increment_v5( fd_vote_state_v5_t * v5,
     320             :                       ulong                migration_epoch,
     321             :                       ulong                epoch,
     322           0 :                       ulong                credits ) {
     323           0 :   if( epoch==migration_epoch ) {
     324           0 :     int have_marker = 0;
     325           0 :     for( ulong i=0UL; i<v5->accumulated_lamports_len; i++ ) have_marker |= fd_vote_epoch_credits_is_alpenglow_marker( &v5->accumulated_lamports[ i ] );
     326           0 :     if( !have_marker ) credits_push_v5( v5, &(fd_vote_epoch_credits_t){ .epoch=ULONG_MAX, .credits=ULONG_MAX, .prev_credits=ULONG_MAX } );
     327           0 :   }
     328             : 
     329           0 :   ulong cnt = v5->accumulated_lamports_len;
     330           0 :   if( FD_UNLIKELY( !cnt ) ) {
     331           0 :     credits_push_v5( v5, &(fd_vote_epoch_credits_t){ .epoch=epoch, .credits=credits, .prev_credits=0UL } );
     332           0 :     return;
     333           0 :   }
     334             : 
     335           0 :   fd_vote_epoch_credits_t * last = &v5->accumulated_lamports[ cnt-1UL ];
     336             : 
     337           0 :   if( fd_vote_epoch_credits_is_alpenglow_marker( last ) ) {
     338           0 :     ulong final_tower_credits = cnt>=2UL ? v5->accumulated_lamports[ cnt-2UL ].credits : 0UL;
     339           0 :     credits_push_v5( v5, &(fd_vote_epoch_credits_t){
     340           0 :       .epoch        = epoch,
     341           0 :       .credits      = fd_ulong_sat_add( credits, final_tower_credits ),
     342           0 :       .prev_credits = final_tower_credits } );
     343           0 :     return;
     344           0 :   }
     345             : 
     346           0 :   if( last->epoch==epoch ) {
     347           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     348           0 :     return;
     349           0 :   }
     350             : 
     351           0 :   if( last->credits==last->prev_credits ) {
     352           0 :     last->epoch   = epoch;
     353           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     354           0 :     return;
     355           0 :   }
     356             : 
     357           0 :   ulong final_credits = last->credits;
     358           0 :   credits_push_v5( v5, &(fd_vote_epoch_credits_t){
     359           0 :     .epoch        = epoch,
     360           0 :     .credits      = fd_ulong_sat_add( credits, final_credits ),
     361           0 :     .prev_credits = final_credits } );
     362           0 : }
     363             : 
     364             : /* vs_maybe_update_votes mirrors VoteState::maybe_update_votes: the
     365             :    votes deque is replaced by a single landed vote on the latest voted
     366             :    slot, and last_timestamp advances to the slot's timestamp if newer. */
     367             : 
     368             : static void
     369             : vs_maybe_update_votes( fd_vote_state_versioned_t * vs,
     370             :                        ulong                       slot,
     371           0 :                        long                        slot_timestamp_ns ) {
     372           0 :   ulong latest = slot;
     373           0 :   ulong const * last_voted = fd_vsv_get_last_voted_slot( vs );
     374           0 :   if( last_voted && *last_voted>latest ) latest = *last_voted;
     375           0 :   ulong const * root = fd_vsv_get_root_slot( vs );
     376           0 :   if( root && *root>latest ) latest = *root;
     377             : 
     378           0 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( vs );
     379           0 :   deq_fd_landed_vote_t_remove_all( votes );
     380           0 :   deq_fd_landed_vote_t_push_tail( votes, (fd_landed_vote_t){ .latency=0, .lockout={ .slot=latest, .confirmation_count=1U } } );
     381             : 
     382           0 :   long timestamp = slot_timestamp_ns/1000000000L;
     383           0 :   if( timestamp>fd_vsv_get_last_timestamp( vs )->timestamp ) {
     384           0 :     fd_vsv_set_last_timestamp( vs, &(fd_vote_block_timestamp_t){ .slot=slot, .timestamp=timestamp } );
     385           0 :   }
     386           0 : }
     387             : 
     388             : /* vote_update describes the mutations to apply to one vote account. */
     389             : 
     390             : struct vote_update {
     391             :   int   update_votes; ulong vote_slot; long vote_ts_ns;
     392             :   int   update_root;  ulong root_slot;
     393             :   ulong credits;      /* 0 = none */
     394             :   ulong migration_epoch;
     395             :   ulong current_epoch;
     396             : };
     397             : typedef struct vote_update vote_update_t;
     398             : 
     399             : static int
     400             : vote_account_open( fd_bank_t *                 bank,
     401             :                    fd_accdb_t *                accdb,
     402             :                    fd_pubkey_t const *         pk,
     403             :                    fd_vote_state_versioned_t * vs,
     404           0 :                    fd_acc_t *                  acc ) {
     405             :   /* A signer's vote account can be closed and recreated with any data,
     406             :      so reject accounts that can't be vote accounts before copying their
     407             :      data. */
     408           0 :   ulong data_len = fd_accdb_data_len( accdb, bank->accdb_fork_id, pk->uc );
     409           0 :   if( FD_UNLIKELY( data_len!=FD_VOTE_STATE_V2_SZ && data_len!=FD_VOTE_STATE_V3_SZ &&
     410           0 :                    data_len!=FD_VOTE_STATE_V4_SZ && data_len!=FD_VOTE_STATE_V5_SZ ) ) return 0;
     411             : 
     412           0 :   *acc = fd_accdb_write_one( accdb, bank->accdb_fork_id, pk->uc );
     413           0 :   if( acc->prior_lamports &&
     414           0 :       fd_vsv_is_correct_size_owner_and_init( acc->prior_owner, acc->prior_data, acc->prior_data_len ) &&
     415           0 :       fd_vote_account_is_valid_v5( acc->data, acc->data_len ) ) {
     416             :     /* Vote State V5 accounts are updated in-place so no need to
     417             :        deserialize. */
     418           0 :     return 1;
     419           0 :   }
     420           0 :   if( FD_UNLIKELY( !acc->prior_lamports ||
     421           0 :                    !fd_vsv_is_correct_size_owner_and_init( acc->prior_owner, acc->prior_data, acc->prior_data_len ) ||
     422           0 :                    !fd_vote_state_versioned_deserialize( vs, acc->prior_data, acc->prior_data_len ) ||
     423           0 :                    vs->kind==fd_vote_state_versioned_enum_uninitialized ) ) {
     424           0 :     fd_accdb_unwrite_one( accdb, acc );
     425           0 :     return 0;
     426           0 :   }
     427           0 :   return 1;
     428           0 : }
     429             : 
     430             : static int
     431           0 : vote_account_is_v5( fd_acc_t const * acc ) {
     432           0 :   return FD_LOAD( uint, acc->data )==fd_vote_state_versioned_enum_v5;
     433           0 : }
     434             : 
     435             : static void
     436             : vote_update_apply_v5( fd_vote_state_v5_t *  v5,
     437           0 :                       vote_update_t const * upd ) {
     438           0 :   if( upd->update_votes ) {
     439           0 :     v5->latest_vote = v5->latest_vote==ULONG_MAX ? upd->vote_slot : fd_ulong_max( upd->vote_slot, v5->latest_vote );
     440           0 :     if( v5->root_slot!=ULONG_MAX ) v5->latest_vote = fd_ulong_max( v5->latest_vote, v5->root_slot );
     441             : 
     442           0 :     long timestamp = upd->vote_ts_ns/1000000000L;
     443           0 :     if( timestamp>v5->last_timestamp.timestamp ) {
     444           0 :       v5->last_timestamp = (fd_vote_block_timestamp_t){ .slot=upd->vote_slot, .timestamp=timestamp };
     445           0 :     }
     446           0 :   }
     447           0 :   if( upd->update_root ) {
     448           0 :     v5->root_slot = v5->root_slot==ULONG_MAX ? upd->root_slot : fd_ulong_max( v5->root_slot, upd->root_slot );
     449           0 :   }
     450           0 :   if( upd->credits ) credits_increment_v5( v5, upd->migration_epoch, upd->current_epoch, upd->credits );
     451           0 : }
     452             : 
     453             : static void
     454             : vote_update_apply( fd_vote_state_versioned_t * vs,
     455             :                    fd_acc_t *                  acc,
     456           0 :                    vote_update_t const *       upd ) {
     457           0 :   if( vote_account_is_v5( acc ) ) {
     458           0 :     vote_update_apply_v5( fd_vote_account_v5( acc->data ), upd );
     459           0 :     return;
     460           0 :   }
     461           0 :   if( upd->update_votes ) vs_maybe_update_votes( vs, upd->vote_slot, upd->vote_ts_ns );
     462           0 :   if( upd->update_root ) {
     463           0 :     ulong const * root = fd_vsv_get_root_slot( vs );
     464           0 :     ulong latest_root  = fd_ulong_max( root ? *root : upd->root_slot, upd->root_slot );
     465           0 :     fd_vsv_set_root_slot( vs, &latest_root );
     466           0 :   }
     467           0 :   if( upd->credits ) credits_increment( fd_vsv_get_epoch_credits_mutable( vs ), upd->migration_epoch, upd->current_epoch, upd->credits );
     468           0 : }
     469             : 
     470             : static void
     471             : vote_account_store( fd_bank_t *                       bank,
     472             :                     fd_accdb_t *                      accdb,
     473             :                     fd_capture_ctx_t *                capture_ctx,
     474             :                     fd_acc_t *                        acc,
     475             :                     fd_vote_state_versioned_t const * vs,
     476             :                     fd_footer_cache_memo_t *          memo,
     477           0 :                     fd_vote_queue_t *                 q ) {
     478             :   /* We do not need to serialize Vote State V5 accounts as they are
     479             :      updated in-place. */
     480           0 :   if( !vote_account_is_v5( acc ) ) {
     481           0 :     fd_memset( acc->data, 0, acc->data_len );
     482           0 :     if( FD_UNLIKELY( fd_vote_state_versioned_serialize( vs, acc->data, acc->data_len ) ) ) {
     483           0 :       FD_BASE58_ENCODE_32_BYTES( acc->pubkey, pk_b58 );
     484           0 :       FD_LOG_WARNING(( "slot %lu: vote account %s failed to serialize; skipping", bank->f.slot, pk_b58 ));
     485           0 :       fd_accdb_unwrite_one( accdb, acc );
     486           0 :       return;
     487           0 :     }
     488           0 :   }
     489           0 :   acc->executable = 0;
     490             : 
     491           0 :   fd_vote_queue_store( q, acc, memo );
     492           0 :   fd_hashes_capture_account( acc->pubkey, acc->owner, acc->lamports, acc->executable, acc->data, acc->data_len, bank, capture_ctx );
     493             : 
     494           0 :   acc->commit = 1;
     495           0 :   fd_accdb_unwrite_one( accdb, acc );
     496           0 : }
     497             : 
     498             : void
     499             : fd_alpenglow_pda( char const *  seed,
     500       64028 :                   fd_pubkey_t * out ) {
     501       64028 :   fd_pubkey_t const * feature_id = &ids[ offsetof( fd_features_t, alpenglow )>>3 ].id;
     502             : 
     503       64028 :   uchar const * seed_   = (uchar const *)seed;
     504       64028 :   ulong         seed_sz = strlen( seed );
     505       64028 :   uchar         bump;
     506       64028 :   uint          custom_err;
     507       64028 :   FD_TEST( fd_pubkey_find_program_address( feature_id, 1UL, &seed_, &seed_sz, out, &bump, &custom_err )==FD_PUBKEY_SUCCESS );
     508       64028 : }
     509             : 
     510             : ulong
     511             : fd_alpenglow_migration_slot( fd_bank_t *  bank,
     512       64031 :                              fd_accdb_t * accdb ) {
     513       64031 :   fd_pubkey_t genesis_cert_addr;
     514       64031 :   fd_alpenglow_pda( "carlgration", &genesis_cert_addr );
     515             : 
     516       64031 :   ulong    migration_slot = ULONG_MAX;
     517       64031 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, genesis_cert_addr.uc );
     518       64031 :   if( FD_LIKELY( acc.lamports && acc.data_len>=8UL ) ) migration_slot = FD_LOAD( ulong, acc.data );
     519       64031 :   fd_accdb_unread_one( accdb, &acc );
     520       64031 :   return migration_slot;
     521       64031 : }
     522             : 
     523             : void
     524             : fd_alpenglow_producer_time_bounds( fd_bank_t const * bank,
     525             :                                    fd_accdb_t *      accdb,
     526             :                                    long *            lo,
     527           0 :                                    long *            hi ) {
     528           0 :   fd_pubkey_t alpenclock_addr;
     529           0 :   fd_alpenglow_pda( "alpenclock", &alpenclock_addr );
     530             : 
     531           0 :   long     parent_nanos;
     532           0 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, alpenclock_addr.uc );
     533           0 :   int      has_alpenclock = acc.lamports && acc.data_len>=sizeof(ulong);
     534           0 :   if( FD_LIKELY( has_alpenclock ) ) parent_nanos = (long)FD_LOAD( ulong, acc.data );
     535           0 :   fd_accdb_unread_one( accdb, &acc );
     536           0 :   if( FD_UNLIKELY( !has_alpenclock ) ) {
     537           0 :     fd_sol_sysvar_clock_t clock[1];
     538           0 :     if( FD_UNLIKELY( !fd_sysvar_clock_read( accdb, bank->accdb_fork_id, clock ) ) ) FD_LOG_ERR(( "fd_sysvar_clock_read failed" ));
     539           0 :     parent_nanos = fd_long_sat_mul( clock->unix_timestamp, 1000000000L );
     540           0 :   }
     541             : 
     542           0 :   ulong elapsed = fd_slot_params_slot_range_duration_ns( bank, bank->f.parent_slot+1UL, bank->f.slot+1UL /* inclusive */ );
     543           0 :   *lo = fd_long_sat_add( parent_nanos, 1L );
     544           0 :   *hi = fd_long_sat_add( parent_nanos, (long)fd_ulong_min( fd_ulong_sat_mul( elapsed, 2UL ), (ulong)LONG_MAX ) );
     545           0 : }
     546             : 
     547             : /* slot_timestamp backdates the footer timestamp by the duration of
     548             :    slots (slot, bank_slot], taking into account reduced slot times.
     549             : 
     550             :    https://github.com/anza-xyz/agave/blob/v4.3/runtime/src/block_component_processor/vote_reward.rs#L505 */
     551             : static long
     552             : slot_timestamp( fd_bank_t * bank,
     553             :                 ulong       slot,
     554           0 :                 ulong       footer_time_nanos ) {
     555           0 :   ulong dur = fd_slot_params_slot_range_duration_ns( bank, slot+1UL, bank->f.slot+1UL );
     556           0 :   return fd_long_sat_sub( (long)footer_time_nanos, (long)dur );
     557           0 : }
     558             : 
     559             : /* ranked_ele looks a validator up by pubkey in the vote stakes snapshot
     560             :    that iter_kind names and copies its entry to ele.  Returns 1 if the
     561             :    validator is there and ranked, 0 otherwise. */
     562             : 
     563             : static int
     564             : ranked_ele( fd_bank_t const *      bank,
     565             :             int                    iter_kind,
     566             :             fd_pubkey_t const *    pubkey,
     567           0 :             fd_vote_stakes_ele_t * ele ) {
     568           0 :   fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     569           0 :   ushort             fork_id     = bank->vote_stakes_fork_id;
     570           0 :   int found = 0;
     571           0 :   switch( iter_kind ) {
     572           0 :   case FD_VOTE_STAKES_ITER_T_2: found = fd_vote_stakes_query_t_2( vote_stakes, fork_id, pubkey, ele ); break;
     573           0 :   case FD_VOTE_STAKES_ITER_T_3: found = fd_vote_stakes_query_t_3( vote_stakes, fork_id, pubkey, ele ); break;
     574           0 :   default: {
     575             :     /* The older snapshots have no query.  Only a finalization cert for
     576             :        a parent several epochs back names one, so walking is fine. */
     577           0 :     uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     578           0 :     for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
     579           0 :          !found && !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
     580           0 :          fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
     581           0 :       fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, ele );
     582           0 :       found = fd_pubkey_eq( &ele->pubkey, pubkey );
     583           0 :     }
     584           0 :     break;
     585           0 :   }
     586           0 :   }
     587           0 :   if( FD_UNLIKELY( !found || ele->rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) return 0;
     588           0 :   FD_TEST( ele->rank<FD_VOTOR_VAT_MAX );
     589           0 :   return 1;
     590           0 : }
     591             : 
     592             : static ulong
     593             : memo_rank( fd_bank_t const *   bank,
     594             :            int                 iter_kind,
     595           0 :            fd_pubkey_t const * pubkey ) {
     596           0 :   fd_vote_stakes_ele_t ele[1];
     597           0 :   return ranked_ele( bank, iter_kind, pubkey, ele ) ? (ulong)ele->rank : ULONG_MAX;
     598           0 : }
     599             : 
     600             : /* signer_reward splits a signer's per-slot, stake-fractional reward
     601             :    half to the signer, half (rounded up) to the leader. */
     602             : 
     603             : static void
     604             : signer_reward( fd_alpenglow_rewards_plan_t const * plan,
     605             :                ulong                               stake,
     606             :                ulong *                             validator_reward,
     607           0 :                ulong *                             leader_share ) {
     608           0 :   uint128 numerator   = (uint128)plan->max_reward*(uint128)stake;
     609           0 :   uint128 denominator = (uint128)plan->slots_per_epoch*(uint128)plan->total_stake;
     610           0 :   ulong   reward      = denominator ? (ulong)( numerator/denominator ) : 0UL;
     611           0 :   *validator_reward = reward/2UL;
     612           0 :   *leader_share     = reward-*validator_reward;
     613           0 : }
     614             : 
     615             : int
     616             : fd_alpenglow_rewards_plan( fd_bank_t *                   bank,
     617             :                            fd_accdb_t *                  accdb,
     618             :                            fd_block_footer_t const *     footer,
     619           0 :                            fd_alpenglow_rewards_plan_t * plan ) {
     620             : 
     621           0 :   ulong bank_slot         = bank->f.slot;
     622           0 :   ulong footer_time_nanos = footer->block_producer_time_nanos;
     623             : 
     624           0 :   memset( plan, 0, sizeof(fd_alpenglow_rewards_plan_t) );
     625           0 :   plan->current_epoch   = fd_slot_to_epoch( &bank->f.epoch_schedule, bank_slot, NULL );
     626           0 :   plan->migration_epoch = ULONG_MAX;
     627           0 :   plan->has_reward      = footer->has_skip_reward_cert || footer->has_notar_reward_cert;
     628           0 :   plan->has_final       = footer->has_fast_final_cert  || footer->has_final_cert;
     629           0 :   plan->memo_kind       = vote_stakes_iter_kind_for_epoch( bank, plan->current_epoch );
     630           0 :   plan->slice_seed      = bank->f.prev_bank_hash.ul[ 0 ];
     631           0 :   plan->use_memo        = 1;
     632             : 
     633           0 :   fd_pubkey_t const * leader_vote_pubkey = fd_epoch_leaders_get_vote( fd_bank_epoch_leaders_query( bank, plan->current_epoch ), bank_slot );
     634           0 :   FD_TEST( leader_vote_pubkey );
     635           0 :   plan->leader_vote_pubkey = *leader_vote_pubkey;
     636             : 
     637             :   /* credits for the attested voters of the reward slot */
     638             : 
     639           0 :   if( plan->has_reward ) {
     640           0 :     fd_bls_set_null( plan->reward_set );
     641           0 :     ulong skip_slot = ULONG_MAX, notar_slot = ULONG_MAX;
     642           0 :     if( footer->has_skip_reward_cert ) {
     643           0 :       skip_slot = footer->skip_reward_cert.slot;
     644           0 :       fd_bls_set_union( plan->reward_set, plan->reward_set, footer->skip_reward_cert.signer_set );
     645           0 :     }
     646           0 :     if( footer->has_notar_reward_cert ) {
     647           0 :       notar_slot = footer->notar_reward_cert.slot;
     648           0 :       fd_bls_set_union( plan->reward_set, plan->reward_set, footer->notar_reward_cert.signer_set );
     649           0 :     }
     650           0 :     if( FD_UNLIKELY( skip_slot!=ULONG_MAX && notar_slot!=ULONG_MAX && skip_slot!=notar_slot ) ) {
     651           0 :       FD_LOG_WARNING(( "slot %lu: reward cert slots differ: skip %lu, notar %lu", bank_slot, skip_slot, notar_slot ));
     652           0 :       return -1;
     653           0 :     }
     654           0 :     plan->reward_slot = fd_ulong_min( skip_slot, notar_slot );
     655             : 
     656           0 :     ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     657           0 :     if( FD_UNLIKELY( migration_slot==ULONG_MAX ) ) {
     658           0 :       FD_LOG_WARNING(( "slot %lu: reward cert but no genesis certificate account", bank_slot ));
     659           0 :       return -1;
     660           0 :     }
     661           0 :     plan->migration_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, migration_slot, NULL );
     662             : 
     663           0 :     if( FD_UNLIKELY( fd_ulong_sat_add( plan->reward_slot, FD_NUM_SLOTS_FOR_REWARD )!=bank_slot || plan->reward_slot<=migration_slot ) ) {
     664           0 :       FD_LOG_WARNING(( "slot %lu: invalid reward cert slot %lu (migration slot %lu)", bank_slot, plan->reward_slot, migration_slot ));
     665           0 :       return -1;
     666           0 :     }
     667             : 
     668           0 :     ulong reward_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, plan->reward_slot, NULL );
     669             : 
     670             :     /* Changed to charge rewards against the processing bank's epoch
     671             :        inflation budget, not the reward slot's epoch.  Handles rewards
     672             :        going over budget when inflation rate decreases.
     673             :        https://github.com/anza-xyz/agave/blob/386cf57c45e135d8a3a8b7d16877eb896f695c64/runtime/src/block_component_processor/vote_reward.rs#L194 */
     674           0 :     fd_epoch_inflation_account_state_t inflation[1];
     675           0 :     fd_epoch_inflation_state_t const * inflation_state = NULL;
     676           0 :     if( FD_LIKELY( fd_epoch_inflation_account_read( bank, accdb, inflation ) ) ) {
     677           0 :       if(      inflation->current.epoch==plan->current_epoch                     ) inflation_state = &inflation->current;
     678           0 :       else if( inflation->has_prev && inflation->prev.epoch==plan->current_epoch ) inflation_state = &inflation->prev;
     679           0 :     }
     680           0 :     if( FD_UNLIKELY( !inflation_state ) ) {
     681           0 :       FD_LOG_WARNING(( "slot %lu: no epoch inflation state for epoch %lu", bank_slot, plan->current_epoch ));
     682           0 :       return -1;
     683           0 :     }
     684           0 :     plan->max_reward      = inflation_state->max_possible_validator_reward;
     685           0 :     plan->slots_per_epoch = inflation_state->slots_per_epoch;
     686             : 
     687           0 :     plan->total_stake = fd_vote_stakes_total_stake( fd_bank_vote_stakes( bank ), reward_epoch );
     688           0 :     if( FD_UNLIKELY( !plan->total_stake ) ) {
     689           0 :       FD_LOG_WARNING(( "slot %lu: no epoch stakes for reward epoch %lu", bank_slot, reward_epoch ));
     690           0 :       return -1;
     691           0 :     }
     692             : 
     693           0 :     plan->reward_ts_ns = slot_timestamp( bank, plan->reward_slot, footer_time_nanos );
     694           0 :     plan->reward_kind  = vote_stakes_iter_kind_for_epoch( bank, reward_epoch );
     695           0 :     if( FD_UNLIKELY( !plan->reward_kind ) ) {
     696           0 :       FD_LOG_WARNING(( "slot %lu: reward epoch %lu is not t-1 through t-5", bank_slot, reward_epoch ));
     697           0 :       return -1;
     698           0 :     }
     699           0 :   }
     700             : 
     701             :   /* finalization cert: root/votes/timestamp for the signers */
     702             : 
     703           0 :   if( plan->has_final ) {
     704           0 :     if( footer->has_fast_final_cert ) {
     705           0 :       plan->final_slot = footer->fast_final_cert.slot;
     706           0 :       fd_bls_set_copy( plan->final_set, footer->fast_final_cert.signer_set );
     707           0 :     } else {
     708           0 :       plan->final_slot = footer->final_cert.slot;
     709           0 :       fd_bls_set_union( plan->final_set, footer->final_cert.signer_set, footer->notar_cert.signer_set );
     710           0 :     }
     711           0 :     ulong final_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, plan->final_slot, NULL );
     712           0 :     plan->final_ts_ns = slot_timestamp( bank, plan->final_slot, footer_time_nanos );
     713           0 :     plan->final_kind  = vote_stakes_iter_kind_for_epoch( bank, final_epoch );
     714           0 :     if( FD_UNLIKELY( !plan->final_kind ) ) {
     715           0 :       FD_LOG_WARNING(( "slot %lu: finalization epoch %lu is not t-1 through t-5", bank_slot, final_epoch ));
     716           0 :       return -1;
     717           0 :     }
     718           0 :   }
     719             : 
     720           0 :   return 0;
     721           0 : }
     722             : 
     723             : int
     724             : fd_alpenglow_rewards_apply_slice( fd_bank_t *                         bank,
     725             :                                   fd_accdb_t *                        accdb,
     726             :                                   fd_capture_ctx_t *                  capture_ctx,
     727             :                                   fd_footer_cache_t *                 cache,
     728             :                                   fd_alpenglow_rewards_plan_t const * plan,
     729             :                                   ulong                               slice_idx,
     730             :                                   ulong                               slice_cnt,
     731             :                                   fd_lthash_value_t *                 delta,
     732           0 :                                   ulong *                             leader_credits ) {
     733             : 
     734           0 :   ulong bank_slot = bank->f.slot;
     735           0 :   if( FD_UNLIKELY( !plan->use_memo ) ) cache = NULL;
     736             : 
     737           0 :   uchar __attribute__((aligned(FD_VOTE_QUEUE_ALIGN))) q_mem[ FD_VOTE_QUEUE_FOOTPRINT ];
     738           0 :   fd_vote_queue_t * q = fd_vote_queue_init( q_mem );
     739             : 
     740             :   /* When both certs draw on the same epoch set, one walk applies the
     741             :      reward and then the final update to each signer with a single
     742             :      store, as Agave's update_accounts does.  Otherwise the reward walk
     743             :      runs first and the final walk after it. */
     744             : 
     745           0 :   int fused = plan->has_reward && plan->has_final && plan->final_kind==plan->reward_kind;
     746           0 :   for( ulong pass=0UL; pass<2UL; pass++ ) {
     747           0 :     int rwd = !pass && plan->has_reward;
     748           0 :     int fin = plan->has_final && ( pass ? !fused : fused );
     749           0 :     if( !rwd && !fin ) continue;
     750           0 :     int iter_kind = rwd ? plan->reward_kind : plan->final_kind;
     751             : 
     752           0 :     int have_ranked_vote = 0;
     753           0 :     fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     754           0 :     uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     755           0 :     for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter_mem );
     756           0 :          !fd_vote_stakes_iter_done( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter );
     757           0 :          fd_vote_stakes_iter_next( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter ) ) {
     758           0 :       fd_vote_stakes_ele_t ele[1];
     759           0 :       fd_vote_stakes_iter_ele( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter, ele );
     760           0 :       if( FD_UNLIKELY( ele->rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
     761           0 :       FD_TEST( ele->rank<FD_VOTOR_VAT_MAX );
     762           0 :       have_ranked_vote = 1;
     763           0 :       ulong r      = (ulong)ele->rank;
     764           0 :       int   in_rwd = rwd && fd_bls_set_test( plan->reward_set, r );
     765           0 :       int   in_fin = fin && fd_bls_set_test( plan->final_set,  r );
     766           0 :       if( !in_rwd && !in_fin ) continue;
     767             :       /* The leader's account takes every slice's credits, so it waits
     768             :          for all of them.  See fd_alpenglow_rewards_apply_leader(). */
     769           0 :       if( FD_UNLIKELY( fd_pubkey_eq( &ele->pubkey, &plan->leader_vote_pubkey ) ) ) continue;
     770           0 :       if( slice_cnt>1UL && fd_ulong_hash( ele->pubkey.ul[ 0 ]^plan->slice_seed )%slice_cnt!=slice_idx ) continue;
     771             : 
     772           0 :       static FD_TL fd_vote_state_versioned_t vs[1];
     773           0 :       fd_acc_t acc;
     774             : 
     775             :       /* Skip this node if the vote account cannot be deserialized
     776             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L378-L380 */
     777           0 :       if( FD_UNLIKELY( !vote_account_open( bank, accdb, &ele->pubkey, vs, &acc ) ) ) continue;
     778             : 
     779           0 :       if( in_rwd ) {
     780           0 :         ulong validator_reward, leader_share;
     781           0 :         signer_reward( plan, ele->stake, &validator_reward, &leader_share );
     782             : 
     783             :         /* Only accumulate the leader credits if the vote account could
     784             :            be successfully read.
     785             : 
     786             :            https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L249 */
     787           0 :         *leader_credits = fd_ulong_sat_add( *leader_credits, leader_share );
     788           0 :         vote_update_apply( vs, &acc, &(vote_update_t){
     789           0 :           .update_votes    = 1, .vote_slot = plan->reward_slot, .vote_ts_ns = plan->reward_ts_ns,
     790           0 :           .credits         = validator_reward,
     791           0 :           .migration_epoch = plan->migration_epoch,
     792           0 :           .current_epoch   = plan->current_epoch,
     793           0 :         } );
     794           0 :       }
     795           0 :       if( in_fin ) {
     796           0 :         vote_update_apply( vs, &acc, &(vote_update_t){
     797           0 :           .update_root  = 1, .root_slot = plan->final_slot,
     798           0 :           .update_votes = 1, .vote_slot = plan->final_slot, .vote_ts_ns = plan->final_ts_ns,
     799           0 :         } );
     800           0 :       }
     801           0 :       ulong m = iter_kind==plan->memo_kind ? r : memo_rank( bank, plan->memo_kind, &ele->pubkey );
     802           0 :       vote_account_store( bank, accdb, capture_ctx, &acc, vs, cache && m!=ULONG_MAX ? cache->memo+m : NULL, q );
     803           0 :     }
     804           0 :     if( FD_UNLIKELY( !have_ranked_vote ) ) {
     805           0 :       if( rwd ) FD_LOG_WARNING(( "slot %lu: no ranked validators for reward slot %lu",    bank_slot, plan->reward_slot ));
     806           0 :       else      FD_LOG_WARNING(( "slot %lu: no ranked validators for finalized slot %lu", bank_slot, plan->final_slot  ));
     807           0 :       fd_vote_queue_fini( q );
     808           0 :       return -1;
     809           0 :     }
     810           0 :   }
     811             : 
     812           0 :   fd_lthash_add( delta, fd_vote_queue_fini( q ) );
     813           0 :   return 0;
     814           0 : }
     815             : 
     816             : void
     817             : fd_alpenglow_rewards_apply_leader( fd_bank_t *                         bank,
     818             :                                    fd_accdb_t *                        accdb,
     819             :                                    fd_capture_ctx_t *                  capture_ctx,
     820             :                                    fd_footer_cache_t *                 cache,
     821             :                                    fd_alpenglow_rewards_plan_t const * plan,
     822             :                                    ulong                               leader_credits,
     823           0 :                                    fd_lthash_value_t *                 delta ) {
     824           0 :   if( FD_UNLIKELY( !plan->use_memo ) ) cache = NULL;
     825             : 
     826             :   /* The leader's own signer updates come first, as a slice would apply
     827             :      them.  Then its share of every reward, its own included. */
     828             : 
     829           0 :   vote_update_t        upd[ 3 ];
     830           0 :   ulong                upd_cnt = 0UL;
     831           0 :   fd_vote_stakes_ele_t ele[1];
     832           0 :   if( plan->has_reward && ranked_ele( bank, plan->reward_kind, &plan->leader_vote_pubkey, ele ) && fd_bls_set_test( plan->reward_set, ele->rank ) ) {
     833           0 :     ulong validator_reward, leader_share;
     834           0 :     signer_reward( plan, ele->stake, &validator_reward, &leader_share );
     835           0 :     leader_credits = fd_ulong_sat_add( leader_credits, leader_share );
     836           0 :     upd[ upd_cnt++ ] = (vote_update_t){
     837           0 :       .update_votes    = 1, .vote_slot = plan->reward_slot, .vote_ts_ns = plan->reward_ts_ns,
     838           0 :       .credits         = validator_reward,
     839           0 :       .migration_epoch = plan->migration_epoch,
     840           0 :       .current_epoch   = plan->current_epoch,
     841           0 :     };
     842           0 :   }
     843           0 :   if( plan->has_final && ranked_ele( bank, plan->final_kind, &plan->leader_vote_pubkey, ele ) && fd_bls_set_test( plan->final_set, ele->rank ) ) {
     844           0 :     upd[ upd_cnt++ ] = (vote_update_t){
     845           0 :       .update_root  = 1, .root_slot = plan->final_slot,
     846           0 :       .update_votes = 1, .vote_slot = plan->final_slot, .vote_ts_ns = plan->final_ts_ns,
     847           0 :     };
     848           0 :   }
     849           0 :   if( leader_credits ) {
     850           0 :     upd[ upd_cnt++ ] = (vote_update_t){
     851           0 :       .credits         = leader_credits,
     852           0 :       .migration_epoch = plan->migration_epoch,
     853           0 :       .current_epoch   = plan->current_epoch,
     854           0 :     };
     855           0 :   }
     856           0 :   if( !upd_cnt ) return;
     857             : 
     858           0 :   static FD_TL fd_vote_state_versioned_t vs[1];
     859           0 :   fd_acc_t acc;
     860           0 :   if( FD_UNLIKELY( !vote_account_open( bank, accdb, &plan->leader_vote_pubkey, vs, &acc ) ) ) return;
     861           0 :   for( ulong i=0UL; i<upd_cnt; i++ ) vote_update_apply( vs, &acc, upd+i );
     862             : 
     863           0 :   uchar __attribute__((aligned(FD_VOTE_QUEUE_ALIGN))) q_mem[ FD_VOTE_QUEUE_FOOTPRINT ];
     864           0 :   fd_vote_queue_t * q = fd_vote_queue_init( q_mem );
     865           0 :   ulong m = memo_rank( bank, plan->memo_kind, &plan->leader_vote_pubkey );
     866           0 :   vote_account_store( bank, accdb, capture_ctx, &acc, vs, cache && m!=ULONG_MAX ? cache->memo+m : NULL, q );
     867           0 :   fd_lthash_add( delta, fd_vote_queue_fini( q ) );
     868           0 : }
     869             : 
     870             : int
     871             : fd_alpenglow_rewards_apply( fd_bank_t *               bank,
     872             :                             fd_accdb_t *              accdb,
     873             :                             fd_capture_ctx_t *        capture_ctx,
     874             :                             fd_footer_cache_t *       cache,
     875           0 :                             fd_block_footer_t const * footer ) {
     876             : 
     877           0 :   fd_alpenglow_rewards_plan_t plan[1];
     878           0 :   if( FD_UNLIKELY( fd_alpenglow_rewards_plan( bank, accdb, footer, plan ) ) ) return -1;
     879             : 
     880           0 :   fd_lthash_value_t delta[1];
     881           0 :   fd_lthash_zero( delta );
     882           0 :   ulong leader_credits = 0UL;
     883           0 :   if( FD_UNLIKELY( fd_alpenglow_rewards_apply_slice( bank, accdb, capture_ctx, cache, plan, 0UL, 1UL, delta, &leader_credits ) ) ) return -1;
     884           0 :   fd_alpenglow_rewards_apply_leader( bank, accdb, capture_ctx, cache, plan, leader_credits, delta );
     885             : 
     886           0 :   fd_lthash_add( fd_bank_lthash_locking_modify( bank ), delta );
     887           0 :   fd_bank_lthash_end_locking_modify( bank );
     888             : 
     889           0 :   return 0;
     890           0 : }

Generated by: LCOV version 1.14