LCOV - code coverage report
Current view: top level - flamenco/alpenglow - fd_alpenglow.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 18 454 4.0 %
Date: 2026-10-22 18:10:43 Functions: 2 19 10.5 %

          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( ulong fork_id,
      16           0 :                                  ulong epoch ) {
      17           0 :   ulong t_1_epoch = (ulong)fd_vote_stakes_fork_epoch( 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 ag_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             :   blst_p1        key_sum;                /* sum of all keys, infinity for a t-1 set */
      54             :   fd_bls_pub_t   bls_keys[ AG_VAT_MAX ]; /* indexed by rank */
      55             :   ulong          stakes  [ AG_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 :   ulong fork_id   = bank->vote_stakes_fork_id;
      72           0 :   int   iter_kind = vote_stakes_iter_kind_for_epoch( fork_id, 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<AG_VAT_MAX );
      92             : 
      93           0 :     if( FD_UNLIKELY( blst_p1_deserialize( set->bls_keys+ele->rank, ele->bls_key_uncompressed )!=BLST_SUCCESS ) ) {
      94           0 :       FD_LOG_WARNING(( "slot %lu: rank %u of epoch %lu has a malformed BLS key", bank->f.slot, ele->rank, epoch ));
      95           0 :       return 0;
      96           0 :     }
      97           0 :     set->stakes[ ele->rank ] = ele->stake;
      98           0 :     max_rank = fd_ulong_max( max_rank, (ulong)ele->rank );
      99           0 :     total   += ele->stake;
     100           0 :     cnt++;
     101           0 :   }
     102           0 :   if( FD_UNLIKELY( !cnt || cnt!=max_rank+1UL ) ) {
     103           0 :     FD_LOG_WARNING(( "slot %lu: epoch %lu has %lu ranked validators, highest rank %lu", bank->f.slot, epoch, cnt, max_rank ));
     104           0 :     return 0;
     105           0 :   }
     106           0 :   memset( &set->key_sum, 0, sizeof(blst_p1) ); /* point at infinity */
     107           0 :   if( FD_LIKELY( iter_kind!=FD_VOTE_STAKES_ITER_T_1 ) ) {
     108           0 :     for( ulong rank=0UL; rank<cnt; rank++ ) blst_p1_add_or_double_affine( &set->key_sum, &set->key_sum, set->bls_keys+rank );
     109           0 :   }
     110           0 :   set->epoch         = iter_kind==FD_VOTE_STAKES_ITER_T_1 ? ULONG_MAX : epoch;
     111           0 :   set->validator_cnt = cnt;
     112           0 :   set->total_stake   = total;
     113           0 :   return 1;
     114           0 : }
     115             : 
     116             : ulong
     117             : fd_alpenglow_cert_payload( fd_block_footer_cert_t const * cert,
     118             :                            uint                           vote_tag,
     119             :                            ushort                         shred_version,
     120           0 :                            uchar                          payload[ static FD_ALPENGLOW_CERT_PAYLOAD_MAX ] ) {
     121           0 :   return vote_signing_ser( vote_tag, cert->slot, vote_tag==VOTE_TAG_NOTAR ? cert->block_id.uc : NULL, shred_version, payload );
     122           0 : }
     123             : 
     124             : int
     125             : fd_alpenglow_cert_verify( fd_bank_t const *              bank,
     126             :                           fd_block_footer_cert_t const * cert,
     127             :                           uint                           vote_tag,
     128             :                           ulong                          quorum_numer,
     129             :                           ushort                         shred_version,
     130             :                           fd_footer_cache_t *            cache,
     131             :                           blst_p2_affine const *         hm,
     132           0 :                           blst_p2_affine *               hm_out ) {
     133           0 :   ulong bank_slot = bank->f.slot;
     134           0 :   ulong cert_slot = cert->slot;
     135             : 
     136           0 :   if( FD_UNLIKELY( !shred_version ) ) {
     137           0 :     FD_LOG_WARNING(( "slot %lu: footer carries certs but the shred version is not known yet; cannot verify them", bank_slot ));
     138           0 :     return -1;
     139           0 :   }
     140             : 
     141             :   /* ~200 KiB, kept across calls: consecutive blocks' certs almost
     142             :      always share a ring epoch; a fork specific t-1 set is not reused */
     143           0 :   static FD_TL validator_set_t set[1];
     144             : 
     145           0 :   if( FD_UNLIKELY( !validator_set_for_slot( set, bank, cert_slot ) ) ) return -1;
     146             : 
     147           0 :   ulong last_rank = fd_bls_set_last( cert->signer_set ); /* ULONG_MAX when empty */
     148           0 :   if( FD_UNLIKELY( cert->nbits>set->validator_cnt || last_rank>=set->validator_cnt ) ) {
     149           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu names %u ranks (highest signer %lu) but its epoch has %lu validators",
     150           0 :                      bank_slot, cert_slot, cert->nbits, last_rank, set->validator_cnt ));
     151           0 :     return -1;
     152           0 :   }
     153             : 
     154           0 :   ulong stake = 0UL;
     155           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 ) ) {
     156           0 :     stake += set->stakes[ rank ];
     157           0 :   }
     158           0 :   blst_p1 pub[1];
     159           0 :   fd_bls_pub_sum( pub, set->bls_keys, set->validator_cnt, cert->signer_set, set->epoch!=ULONG_MAX ? &set->key_sum : NULL );
     160           0 :   if( FD_UNLIKELY( quorum_numer && (uint128)stake*(uint128)AG_QUORUM_THRESHOLD_DENOM<(uint128)set->total_stake*(uint128)quorum_numer ) ) {
     161           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has %lu of %lu stake, below %lu/%lu",
     162           0 :                      bank_slot, cert_slot, stake, set->total_stake, quorum_numer, AG_QUORUM_THRESHOLD_DENOM ));
     163           0 :     return -1;
     164           0 :   }
     165             : 
     166           0 :   fd_bls_sig_t sig_affine[1];
     167           0 :   blst_p2      sig[1];
     168           0 :   if( FD_UNLIKELY( blst_p2_uncompress( sig_affine, cert->sig )!=BLST_SUCCESS || !blst_p2_affine_in_g2( sig_affine ) ) ) {
     169           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has a malformed signature", bank_slot, cert_slot ));
     170           0 :     return -1;
     171           0 :   }
     172           0 :   blst_p2_from_affine( sig, sig_affine );
     173             : 
     174           0 :   blst_p2_affine h[1];
     175           0 :   if( hm ) {
     176           0 :     *h = *hm;
     177           0 :   } else {
     178           0 :     uchar payload[ FD_ALPENGLOW_CERT_PAYLOAD_MAX ];
     179           0 :     ulong payload_sz = fd_alpenglow_cert_payload( cert, vote_tag, shred_version, payload );
     180           0 :     if( cache ) fd_footer_cache_hash_msg( cache, payload, payload_sz, h );
     181           0 :     else        fd_bls_hash_msg( payload, payload_sz, h );
     182           0 :     if( hm_out ) *hm_out = *h;
     183           0 :   }
     184           0 :   if( FD_UNLIKELY( !fd_bls_agg_verify_hashed( h, pub, sig ) ) ) {
     185           0 :     FD_LOG_WARNING(( "slot %lu: footer (is_reward %d) cert for slot %lu failed signature verification", bank_slot, !quorum_numer, cert_slot ));
     186           0 :     return -1;
     187           0 :   }
     188           0 :   return 0;
     189           0 : }
     190             : 
     191             : ulong
     192             : fd_alpenglow_footer_certs( fd_block_footer_t const *      footer,
     193             :                            fd_block_footer_cert_t const * cert  [ static FD_ALPENGLOW_FOOTER_CERT_MAX ],
     194             :                            uint                           tag   [ static FD_ALPENGLOW_FOOTER_CERT_MAX ],
     195           0 :                            ulong                          quorum[ static FD_ALPENGLOW_FOOTER_CERT_MAX ] ) {
     196           0 :   ulong n = 0UL;
     197           0 : # define PUSH(c,t,q) do { cert[ n ] = (c); tag[ n ] = (t); quorum[ n ] = (q); n++; } while(0)
     198           0 :   if( footer->has_fast_final_cert   ) PUSH( &footer->fast_final_cert,   VOTE_TAG_NOTAR, AG_STRONG_QUORUM_THRESHOLD_NUMER );
     199           0 :   if( footer->has_final_cert        ) {
     200           0 :     PUSH( &footer->final_cert, VOTE_TAG_FINAL, AG_QUORUM_THRESHOLD_NUMER );
     201           0 :     PUSH( &footer->notar_cert, VOTE_TAG_NOTAR, AG_QUORUM_THRESHOLD_NUMER );
     202           0 :   }
     203           0 :   if( footer->has_skip_reward_cert  ) PUSH( &footer->skip_reward_cert,  VOTE_TAG_SKIP,  0UL );
     204           0 :   if( footer->has_notar_reward_cert ) PUSH( &footer->notar_reward_cert, VOTE_TAG_NOTAR, 0UL );
     205           0 : # undef PUSH
     206           0 :   return n;
     207           0 : }
     208             : 
     209             : int
     210             : fd_alpenglow_footer_verify( fd_bank_t const *         bank,
     211             :                             fd_block_footer_t const * footer,
     212             :                             ushort                    shred_version,
     213           0 :                             fd_footer_cache_t *       cache ) {
     214           0 :   int has_certs = footer->has_fast_final_cert | footer->has_final_cert | footer->has_skip_reward_cert | footer->has_notar_reward_cert;
     215           0 :   if( FD_LIKELY( !has_certs ) ) return 0;
     216             : 
     217           0 :   fd_block_footer_cert_t const * cert  [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     218           0 :   uint                           tag   [ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     219           0 :   ulong                          quorum[ FD_ALPENGLOW_FOOTER_CERT_MAX ];
     220           0 :   ulong                          cnt = fd_alpenglow_footer_certs( footer, cert, tag, quorum );
     221           0 :   for( ulong i=0UL; i<cnt; i++ ) {
     222           0 :     if( FD_UNLIKELY( fd_alpenglow_cert_verify( bank, cert[ i ], tag[ i ], quorum[ i ], shred_version, cache, NULL, NULL ) ) ) return -1;
     223           0 :   }
     224           0 :   return 0;
     225           0 : }
     226             : 
     227             : /* credits_increment mirrors Agave's alpenglow increment_credits
     228             :    (vote_reward.rs): accumulate credits into the tail epoch_credits
     229             :    entry, starting a new entry on epoch changes and inserting the
     230             :    tower->alpenglow migration marker in the migration epoch.  The
     231             :    deque's capacity is MAX_EPOCH_CREDITS_HISTORY+1 while Agave pushes
     232             :    into an unbounded Vec before trimming, so we drop the head early
     233             :    when full; the final trimmed state is the same. */
     234             : 
     235             : static void
     236             : credits_increment( fd_vote_epoch_credits_t * ec,
     237             :                    ulong                     migration_epoch,
     238             :                    ulong                     epoch,
     239           0 :                    ulong                     credits /* nonzero */ ) {
     240           0 :   if( epoch==migration_epoch ) {
     241           0 :     int have_marker = 0;
     242           0 :     for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     243           0 :          !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     244           0 :          iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     245           0 :       if( fd_vote_epoch_credits_is_alpenglow_marker( deq_fd_vote_epoch_credits_t_iter_ele( ec, iter ) ) ) { have_marker = 1; break; }
     246           0 :     }
     247           0 :     if( !have_marker ) {
     248           0 :       if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     249           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 } );
     250           0 :     }
     251           0 :   }
     252             : 
     253           0 :   ulong cnt = deq_fd_vote_epoch_credits_t_cnt( ec );
     254           0 :   if( FD_UNLIKELY( !cnt ) ) {
     255           0 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){ .epoch=epoch, .credits=credits, .prev_credits=0UL } );
     256           0 :     return;
     257           0 :   }
     258             : 
     259           0 :   fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( ec );
     260             : 
     261           0 :   if( fd_vote_epoch_credits_is_alpenglow_marker( last ) ) {
     262             :     /* If there was a tower entry before the marker, its final credits
     263             :        form this entry's initial credits. */
     264           0 :     ulong final_tower_credits = 0UL;
     265           0 :     if( cnt>=2UL ) {
     266           0 :       ulong idx = 0UL;
     267           0 :       for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     268           0 :            !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     269           0 :            iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     270           0 :         if( idx==cnt-2UL ) { final_tower_credits = deq_fd_vote_epoch_credits_t_iter_ele( ec, iter )->credits; break; }
     271           0 :         idx++;
     272           0 :       }
     273           0 :     }
     274           0 :     if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     275           0 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     276           0 :       .epoch        = epoch,
     277           0 :       .credits      = fd_ulong_sat_add( credits, final_tower_credits ),
     278           0 :       .prev_credits = final_tower_credits } );
     279           0 :     while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     280           0 :     return;
     281           0 :   }
     282             : 
     283           0 :   if( last->epoch==epoch ) {
     284           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     285           0 :     return;
     286           0 :   }
     287             : 
     288           0 :   if( last->credits==last->prev_credits ) {
     289             :     /* Different epochs but the latest epoch earned no credits, reuse
     290             :        the entry. */
     291           0 :     last->epoch   = epoch;
     292           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     293           0 :     return;
     294           0 :   }
     295             : 
     296           0 :   ulong final_credits = last->credits;
     297           0 :   if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     298           0 :   deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     299           0 :     .epoch        = epoch,
     300           0 :     .credits      = fd_ulong_sat_add( credits, final_credits ),
     301           0 :     .prev_credits = final_credits } );
     302           0 :   while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     303           0 : }
     304             : 
     305             : /* vs_maybe_update_votes mirrors VoteState::maybe_update_votes: the
     306             :    votes deque is replaced by a single landed vote on the latest voted
     307             :    slot, and last_timestamp advances to the slot's timestamp if newer. */
     308             : 
     309             : static void
     310             : vs_maybe_update_votes( fd_vote_state_versioned_t * vs,
     311             :                        ulong                       slot,
     312           0 :                        long                        slot_timestamp_ns ) {
     313           0 :   ulong latest = slot;
     314           0 :   ulong const * last_voted = fd_vsv_get_last_voted_slot( vs );
     315           0 :   if( last_voted && *last_voted>latest ) latest = *last_voted;
     316           0 :   ulong const * root = fd_vsv_get_root_slot( vs );
     317           0 :   if( root && *root>latest ) latest = *root;
     318             : 
     319           0 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( vs );
     320           0 :   deq_fd_landed_vote_t_remove_all( votes );
     321           0 :   deq_fd_landed_vote_t_push_tail( votes, (fd_landed_vote_t){ .latency=0, .lockout={ .slot=latest, .confirmation_count=1U } } );
     322             : 
     323           0 :   long timestamp = slot_timestamp_ns/1000000000L;
     324           0 :   if( timestamp>fd_vsv_get_last_timestamp( vs )->timestamp ) {
     325           0 :     fd_vsv_set_last_timestamp( vs, &(fd_vote_block_timestamp_t){ .slot=slot, .timestamp=timestamp } );
     326           0 :   }
     327           0 : }
     328             : 
     329             : /* vote_update describes the mutations to apply to one vote account. */
     330             : 
     331             : struct vote_update {
     332             :   int   update_votes; ulong vote_slot; long vote_ts_ns;
     333             :   int   update_root;  ulong root_slot;
     334             :   ulong credits;      /* 0 = none */
     335             :   ulong migration_epoch;
     336             :   ulong current_epoch;
     337             : };
     338             : typedef struct vote_update vote_update_t;
     339             : 
     340             : static int
     341             : vote_account_open( fd_bank_t *                 bank,
     342             :                    fd_accdb_t *                accdb,
     343             :                    fd_pubkey_t const *         pk,
     344             :                    fd_vote_state_versioned_t * vs,
     345           0 :                    fd_acc_t *                  acc ) {
     346           0 :   *acc = fd_accdb_write_one( accdb, bank->accdb_fork_id, pk->uc );
     347           0 :   if( FD_UNLIKELY( !acc->prior_lamports ||
     348           0 :                    !fd_vsv_is_correct_size_owner_and_init( acc->prior_owner, acc->prior_data, acc->prior_data_len ) ||
     349           0 :                    !fd_vote_state_versioned_deserialize( vs, acc->prior_data, acc->prior_data_len ) ||
     350           0 :                    vs->kind==fd_vote_state_versioned_enum_uninitialized ) ) {
     351           0 :     fd_accdb_unwrite_one( accdb, acc );
     352           0 :     return 0;
     353           0 :   }
     354           0 :   return 1;
     355           0 : }
     356             : 
     357             : static void
     358             : vote_update_apply( fd_vote_state_versioned_t * vs,
     359           0 :                    vote_update_t const *       upd ) {
     360           0 :   if( upd->update_votes ) vs_maybe_update_votes( vs, upd->vote_slot, upd->vote_ts_ns );
     361           0 :   if( upd->update_root ) {
     362           0 :     ulong const * root = fd_vsv_get_root_slot( vs );
     363           0 :     ulong latest_root  = fd_ulong_max( root ? *root : upd->root_slot, upd->root_slot );
     364           0 :     fd_vsv_set_root_slot( vs, &latest_root );
     365           0 :   }
     366           0 :   if( upd->credits ) credits_increment( fd_vsv_get_epoch_credits_mutable( vs ), upd->migration_epoch, upd->current_epoch, upd->credits );
     367           0 : }
     368             : 
     369             : static void
     370             : vote_account_store( fd_bank_t *                       bank,
     371             :                     fd_accdb_t *                      accdb,
     372             :                     fd_capture_ctx_t *                capture_ctx,
     373             :                     fd_acc_t *                        acc,
     374             :                     fd_vote_state_versioned_t const * vs,
     375             :                     fd_footer_cache_memo_t *          memo,
     376           0 :                     fd_vote_queue_t *                 q ) {
     377           0 :   fd_memset( acc->data, 0, acc->data_len );
     378           0 :   if( FD_UNLIKELY( fd_vote_state_versioned_serialize( vs, acc->data, acc->data_len ) ) ) {
     379           0 :     FD_BASE58_ENCODE_32_BYTES( acc->pubkey, pk_b58 );
     380           0 :     FD_LOG_WARNING(( "slot %lu: vote account %s failed to serialize; skipping", bank->f.slot, pk_b58 ));
     381           0 :     fd_accdb_unwrite_one( accdb, acc );
     382           0 :     return;
     383           0 :   }
     384           0 :   acc->executable = 0;
     385             : 
     386           0 :   fd_vote_queue_store( q, acc, memo );
     387           0 :   fd_hashes_capture_account( acc->pubkey, acc->owner, acc->lamports, acc->executable, acc->data, acc->data_len, bank, capture_ctx );
     388             : 
     389           0 :   acc->commit = 1;
     390           0 :   fd_accdb_unwrite_one( accdb, acc );
     391           0 : }
     392             : 
     393             : static void
     394             : vote_account_modify( fd_bank_t *              bank,
     395             :                      fd_accdb_t *             accdb,
     396             :                      fd_capture_ctx_t *       capture_ctx,
     397             :                      fd_pubkey_t const *      pk,
     398             :                      vote_update_t const *    upd,
     399             :                      fd_footer_cache_memo_t * memo,
     400           0 :                      fd_vote_queue_t *        q ) {
     401           0 :   static FD_TL fd_vote_state_versioned_t vs[1];
     402           0 :   fd_acc_t acc;
     403           0 :   if( FD_UNLIKELY( !vote_account_open( bank, accdb, pk, vs, &acc ) ) ) return;
     404           0 :   vote_update_apply( vs, upd );
     405           0 :   vote_account_store( bank, accdb, capture_ctx, &acc, vs, memo, q );
     406           0 : }
     407             : 
     408             : void
     409             : fd_alpenglow_pda( char const *  seed,
     410          33 :                   fd_pubkey_t * out ) {
     411          33 :   fd_pubkey_t const * feature_id = &ids[ offsetof( fd_features_t, alpenglow )>>3 ].id;
     412             : 
     413          33 :   uchar const * seed_   = (uchar const *)seed;
     414          33 :   ulong         seed_sz = strlen( seed );
     415          33 :   uchar         bump;
     416          33 :   uint          custom_err;
     417          33 :   FD_TEST( fd_pubkey_find_program_address( feature_id, 1UL, &seed_, &seed_sz, out, &bump, &custom_err )==FD_PUBKEY_SUCCESS );
     418          33 : }
     419             : 
     420             : ulong
     421             : fd_alpenglow_migration_slot( fd_bank_t *  bank,
     422       64592 :                              fd_accdb_t * accdb ) {
     423       64592 :   if( FD_UNLIKELY( !FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) ) return ULONG_MAX;
     424             : 
     425          42 :   fd_pubkey_t genesis_cert_addr;
     426          42 :   fd_alpenglow_pda( "carlgration", &genesis_cert_addr );
     427             : 
     428          42 :   ulong    migration_slot = ULONG_MAX;
     429          42 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, genesis_cert_addr.uc );
     430          42 :   if( FD_LIKELY( acc.lamports && acc.data_len>=8UL ) ) migration_slot = FD_LOAD( ulong, acc.data );
     431          42 :   fd_accdb_unread_one( accdb, &acc );
     432          42 :   return migration_slot;
     433       64592 : }
     434             : 
     435             : void
     436             : fd_alpenglow_producer_time_bounds( fd_bank_t const * bank,
     437             :                                    fd_accdb_t *      accdb,
     438             :                                    long *            lo,
     439           0 :                                    long *            hi ) {
     440           0 :   fd_pubkey_t alpenclock_addr;
     441           0 :   fd_alpenglow_pda( "alpenclock", &alpenclock_addr );
     442             : 
     443           0 :   long     parent_nanos;
     444           0 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, alpenclock_addr.uc );
     445           0 :   int      has_alpenclock = acc.lamports && acc.data_len>=sizeof(ulong);
     446           0 :   if( FD_LIKELY( has_alpenclock ) ) parent_nanos = (long)FD_LOAD( ulong, acc.data );
     447           0 :   fd_accdb_unread_one( accdb, &acc );
     448           0 :   if( FD_UNLIKELY( !has_alpenclock ) ) {
     449           0 :     fd_sol_sysvar_clock_t clock[1];
     450           0 :     if( FD_UNLIKELY( !fd_sysvar_clock_read( accdb, bank->accdb_fork_id, clock ) ) ) FD_LOG_ERR(( "fd_sysvar_clock_read failed" ));
     451           0 :     parent_nanos = fd_long_sat_mul( clock->unix_timestamp, 1000000000L );
     452           0 :   }
     453             : 
     454           0 :   ulong elapsed = fd_slot_params_slot_range_duration_ns( bank, bank->f.parent_slot+1UL, bank->f.slot+1UL /* inclusive */ );
     455           0 :   *lo = fd_long_sat_add( parent_nanos, 1L );
     456           0 :   *hi = fd_long_sat_add( parent_nanos, (long)fd_ulong_min( fd_ulong_sat_mul( elapsed, 2UL ), (ulong)LONG_MAX ) );
     457           0 : }
     458             : 
     459             : /* slot_timestamp backdates the footer timestamp by the duration of
     460             :    slots (slot, bank_slot], taking into account reduced slot times.
     461             : 
     462             :    https://github.com/anza-xyz/agave/blob/v4.3/runtime/src/block_component_processor/vote_reward.rs#L505 */
     463             : static long
     464             : slot_timestamp( fd_bank_t * bank,
     465             :                 ulong       slot,
     466           0 :                 ulong       footer_time_nanos ) {
     467           0 :   ulong dur = fd_slot_params_slot_range_duration_ns( bank, slot+1UL, bank->f.slot+1UL );
     468           0 :   return fd_long_sat_sub( (long)footer_time_nanos, (long)dur );
     469           0 : }
     470             : 
     471             : static ulong
     472             : memo_rank( fd_bank_t const *   bank,
     473             :            int                 iter_kind,
     474           0 :            fd_pubkey_t const * pubkey ) {
     475           0 :   fd_vote_stakes_ele_t ele[1];
     476           0 :   int found;
     477           0 :   switch( iter_kind ) {
     478           0 :   case FD_VOTE_STAKES_ITER_T_2: found = fd_vote_stakes_query_t_2( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, pubkey, ele ); break;
     479           0 :   case FD_VOTE_STAKES_ITER_T_3: found = fd_vote_stakes_query_t_3( fd_bank_vote_stakes( bank ), bank->vote_stakes_fork_id, pubkey, ele ); break;
     480           0 :   default:                      return ULONG_MAX;
     481           0 :   }
     482           0 :   if( FD_UNLIKELY( !found || ele->rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) return ULONG_MAX;
     483           0 :   FD_TEST( ele->rank<AG_VAT_MAX );
     484           0 :   return (ulong)ele->rank;
     485           0 : }
     486             : 
     487             : int
     488             : fd_alpenglow_rewards_apply( fd_bank_t *               bank,
     489             :                             fd_accdb_t *              accdb,
     490             :                             fd_capture_ctx_t *        capture_ctx,
     491             :                             fd_footer_cache_t *       cache,
     492           0 :                             fd_block_footer_t const * footer ) {
     493             : 
     494           0 :   ulong bank_slot         = bank->f.slot;
     495           0 :   ulong footer_time_nanos = footer->block_producer_time_nanos;
     496           0 :   ulong current_epoch     = fd_slot_to_epoch( &bank->f.epoch_schedule, bank_slot, NULL );
     497           0 :   ulong migration_epoch   = ULONG_MAX;
     498           0 :   ulong leader_credits    = 0UL;
     499             : 
     500             :   /* credits for the attested voters of the reward slot */
     501             : 
     502           0 :   int          has_reward = footer->has_skip_reward_cert || footer->has_notar_reward_cert;
     503           0 :   int          has_final  = footer->has_fast_final_cert  || footer->has_final_cert;
     504           0 :   fd_bls_set_t reward_set[ fd_bls_set_word_cnt ];
     505           0 :   fd_bls_set_t final_set [ fd_bls_set_word_cnt ];
     506           0 :   ulong        reward_slot     = 0UL;
     507           0 :   ulong        max_reward      = 0UL;
     508           0 :   ulong        slots_per_epoch = 0UL;
     509           0 :   ulong        total_stake     = 0UL;
     510           0 :   long         reward_ts_ns    = 0L;
     511           0 :   int          reward_kind     = 0;
     512           0 :   ulong        final_slot      = 0UL;
     513           0 :   ulong        final_epoch     = 0UL;
     514           0 :   long         final_ts_ns     = 0L;
     515           0 :   int          final_kind      = 0;
     516             : 
     517           0 :   if( has_reward ) {
     518           0 :     fd_bls_set_null( reward_set );
     519           0 :     ulong skip_slot = ULONG_MAX, notar_slot = ULONG_MAX;
     520           0 :     if( footer->has_skip_reward_cert ) {
     521           0 :       skip_slot = footer->skip_reward_cert.slot;
     522           0 :       fd_bls_set_union( reward_set, reward_set, footer->skip_reward_cert.signer_set );
     523           0 :     }
     524           0 :     if( footer->has_notar_reward_cert ) {
     525           0 :       notar_slot = footer->notar_reward_cert.slot;
     526           0 :       fd_bls_set_union( reward_set, reward_set, footer->notar_reward_cert.signer_set );
     527           0 :     }
     528           0 :     if( FD_UNLIKELY( skip_slot!=ULONG_MAX && notar_slot!=ULONG_MAX && skip_slot!=notar_slot ) ) {
     529           0 :       FD_LOG_WARNING(( "slot %lu: reward cert slots differ: skip %lu, notar %lu", bank_slot, skip_slot, notar_slot ));
     530           0 :       return -1;
     531           0 :     }
     532           0 :     reward_slot = fd_ulong_min( skip_slot, notar_slot );
     533             : 
     534           0 :     ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     535           0 :     if( FD_UNLIKELY( migration_slot==ULONG_MAX ) ) {
     536           0 :       FD_LOG_WARNING(( "slot %lu: reward cert but no genesis certificate account", bank_slot ));
     537           0 :       return -1;
     538           0 :     }
     539           0 :     migration_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, migration_slot, NULL );
     540             : 
     541           0 :     if( FD_UNLIKELY( fd_ulong_sat_add( reward_slot, FD_NUM_SLOTS_FOR_REWARD )!=bank_slot || reward_slot<=migration_slot ) ) {
     542           0 :       FD_LOG_WARNING(( "slot %lu: invalid reward cert slot %lu (migration slot %lu)", bank_slot, reward_slot, migration_slot ));
     543           0 :       return -1;
     544           0 :     }
     545             : 
     546           0 :     ulong reward_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, reward_slot, NULL );
     547             : 
     548             :     /* Changed to charge rewards against the processing bank's epoch
     549             :        inflation budget, not the reward slot's epoch.  Handles rewards
     550             :        going over budget when inflation rate decreases.
     551             :        https://github.com/anza-xyz/agave/blob/386cf57c45e135d8a3a8b7d16877eb896f695c64/runtime/src/block_component_processor/vote_reward.rs#L194 */
     552           0 :     fd_epoch_inflation_account_state_t inflation[1];
     553           0 :     fd_epoch_inflation_state_t const * inflation_state = NULL;
     554           0 :     if( FD_LIKELY( fd_epoch_inflation_account_read( bank, accdb, inflation ) ) ) {
     555           0 :       if(      inflation->current.epoch==current_epoch                     ) inflation_state = &inflation->current;
     556           0 :       else if( inflation->has_prev && inflation->prev.epoch==current_epoch ) inflation_state = &inflation->prev;
     557           0 :     }
     558           0 :     if( FD_UNLIKELY( !inflation_state ) ) {
     559           0 :       FD_LOG_WARNING(( "slot %lu: no epoch inflation state for epoch %lu", bank_slot, current_epoch ));
     560           0 :       return -1;
     561           0 :     }
     562           0 :     max_reward      = inflation_state->max_possible_validator_reward;
     563           0 :     slots_per_epoch = inflation_state->slots_per_epoch;
     564             : 
     565           0 :     total_stake = fd_vote_stakes_total_stake( fd_bank_vote_stakes( bank ), reward_epoch );
     566           0 :     if( FD_UNLIKELY( !total_stake ) ) {
     567           0 :       FD_LOG_WARNING(( "slot %lu: no epoch stakes for reward epoch %lu", bank_slot, reward_epoch ));
     568           0 :       return -1;
     569           0 :     }
     570             : 
     571           0 :     reward_ts_ns = slot_timestamp( bank, reward_slot, footer_time_nanos );
     572           0 :     reward_kind  = vote_stakes_iter_kind_for_epoch( bank->vote_stakes_fork_id, reward_epoch );
     573           0 :     if( FD_UNLIKELY( !reward_kind ) ) {
     574           0 :       FD_LOG_WARNING(( "slot %lu: reward epoch %lu is not t-1 through t-5", bank_slot, reward_epoch ));
     575           0 :       return -1;
     576           0 :     }
     577           0 :   }
     578             : 
     579             :   /* finalization cert: root/votes/timestamp for the signers */
     580             : 
     581           0 :   if( has_final ) {
     582           0 :     if( footer->has_fast_final_cert ) {
     583           0 :       final_slot = footer->fast_final_cert.slot;
     584           0 :       fd_bls_set_copy( final_set, footer->fast_final_cert.signer_set );
     585           0 :     } else {
     586           0 :       final_slot = footer->final_cert.slot;
     587           0 :       fd_bls_set_union( final_set, footer->final_cert.signer_set, footer->notar_cert.signer_set );
     588           0 :     }
     589           0 :     final_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, final_slot, NULL );
     590           0 :     final_ts_ns = slot_timestamp( bank, final_slot, footer_time_nanos );
     591           0 :     final_kind  = vote_stakes_iter_kind_for_epoch( bank->vote_stakes_fork_id, final_epoch );
     592           0 :     if( FD_UNLIKELY( !final_kind ) ) {
     593           0 :       FD_LOG_WARNING(( "slot %lu: finalization epoch %lu is not t-1 through t-5", bank_slot, final_epoch ));
     594           0 :       return -1;
     595           0 :     }
     596           0 :   }
     597             : 
     598             :   /* Same epoch set: one walk, reward then final update, one store per
     599             :      signer (Agave update_accounts).  Else reward walk, then final. */
     600             : 
     601           0 :   uchar __attribute__((aligned(FD_VOTE_QUEUE_ALIGN))) q_mem[ FD_VOTE_QUEUE_FOOTPRINT ];
     602           0 :   fd_vote_queue_t * q = fd_vote_queue_init( q_mem );
     603             : 
     604           0 :   fd_pubkey_t const * leader_vote_pubkey = fd_epoch_leaders_get_vote( fd_bank_epoch_leaders_query( bank, current_epoch ), bank_slot );
     605           0 :   FD_TEST( leader_vote_pubkey );
     606             : 
     607           0 :   int   memo_kind   = vote_stakes_iter_kind_for_epoch( bank->vote_stakes_fork_id, current_epoch );
     608           0 :   ulong leader_rank = memo_rank( bank, memo_kind, leader_vote_pubkey );
     609             : 
     610           0 :   int fused = has_reward && final_kind==reward_kind;
     611           0 :   for( ulong pass=0UL; pass<2UL; pass++ ) {
     612           0 :     int rwd = !pass && has_reward;
     613           0 :     int fin = has_final && ( pass ? !fused : fused );
     614           0 :     if( !rwd && !fin ) continue;
     615           0 :     int iter_kind = rwd ? reward_kind : final_kind;
     616             : 
     617           0 :     int have_ranked_vote = 0;
     618           0 :     fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
     619           0 :     uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     620           0 :     for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter_mem );
     621           0 :          !fd_vote_stakes_iter_done( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter );
     622           0 :          fd_vote_stakes_iter_next( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter ) ) {
     623           0 :       fd_vote_stakes_ele_t ele[1];
     624           0 :       fd_vote_stakes_iter_ele( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter, ele );
     625           0 :       if( FD_UNLIKELY( ele->rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
     626           0 :       FD_TEST( ele->rank<AG_VAT_MAX );
     627           0 :       have_ranked_vote = 1;
     628           0 :       ulong r      = (ulong)ele->rank;
     629           0 :       int   in_rwd = rwd && fd_bls_set_test( reward_set, r );
     630           0 :       int   in_fin = fin && fd_bls_set_test( final_set,  r );
     631           0 :       if( !in_rwd && !in_fin ) continue;
     632             : 
     633           0 :       static FD_TL fd_vote_state_versioned_t vs[1];
     634           0 :       fd_acc_t acc;
     635             : 
     636             :       /* Skip this node if the vote account cannot be deserialized
     637             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L378-L380 */
     638           0 :       if( FD_UNLIKELY( !vote_account_open( bank, accdb, &ele->pubkey, vs, &acc ) ) ) continue;
     639             : 
     640           0 :       if( in_rwd ) {
     641             :         /* per-slot, stake-fractional reward; split half validator, half
     642             :            (rounded up) leader */
     643           0 :         uint128 numerator   = (uint128)max_reward*(uint128)ele->stake;
     644           0 :         uint128 denominator = (uint128)slots_per_epoch*(uint128)total_stake;
     645           0 :         ulong   reward      = denominator ? (ulong)( numerator/denominator ) : 0UL;
     646           0 :         ulong   validator_reward = reward/2UL;
     647             : 
     648             :         /* Only accumulate the leader credits if the vote account could
     649             :            be successfully read.
     650             : 
     651             :            https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L249 */
     652           0 :         leader_credits = fd_ulong_sat_add( leader_credits, reward-validator_reward );
     653           0 :         vote_update_apply( vs, &(vote_update_t){
     654           0 :           .update_votes    = 1, .vote_slot = reward_slot, .vote_ts_ns = reward_ts_ns,
     655           0 :           .credits         = validator_reward,
     656           0 :           .migration_epoch = migration_epoch,
     657           0 :           .current_epoch   = current_epoch,
     658           0 :         } );
     659           0 :       }
     660           0 :       if( in_fin ) {
     661           0 :         vote_update_apply( vs, &(vote_update_t){
     662           0 :           .update_root  = 1, .root_slot = final_slot,
     663           0 :           .update_votes = 1, .vote_slot = final_slot, .vote_ts_ns = final_ts_ns,
     664           0 :         } );
     665           0 :       }
     666           0 :       ulong m = iter_kind==memo_kind ? r : memo_rank( bank, memo_kind, &ele->pubkey );
     667           0 :       vote_account_store( bank, accdb, capture_ctx, &acc, vs, m!=ULONG_MAX ? cache->memo+m : NULL, q );
     668           0 :     }
     669           0 :     if( FD_UNLIKELY( !have_ranked_vote ) ) {
     670           0 :       if( rwd ) FD_LOG_WARNING(( "slot %lu: no ranked validators for reward slot %lu",    bank_slot, reward_slot ));
     671           0 :       else      FD_LOG_WARNING(( "slot %lu: no ranked validators for finalized slot %lu", bank_slot, final_slot  ));
     672           0 :       fd_vote_queue_fini( q );
     673           0 :       return -1;
     674           0 :     }
     675           0 :   }
     676             : 
     677             :   /* leader rewards */
     678             : 
     679           0 :   if( leader_credits ) {
     680           0 :     vote_update_t upd = {
     681           0 :       .credits         = leader_credits,
     682           0 :       .migration_epoch = migration_epoch,
     683           0 :       .current_epoch   = current_epoch,
     684           0 :     };
     685           0 :     vote_account_modify( bank, accdb, capture_ctx, leader_vote_pubkey, &upd, leader_rank!=ULONG_MAX ? cache->memo+leader_rank : NULL, q );
     686           0 :   }
     687             : 
     688           0 :   fd_lthash_add( fd_bank_lthash_locking_modify( bank ), fd_vote_queue_fini( q ) );
     689           0 :   fd_bank_lthash_end_locking_modify( bank );
     690             : 
     691           0 :   return 0;
     692           0 : }

Generated by: LCOV version 1.14