LCOV - code coverage report
Current view: top level - flamenco/alpenglow - fd_alpenglow.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 217 401 54.1 %
Date: 2026-09-17 04:28:31 Functions: 10 14 71.4 %

          Line data    Source code
       1             : #include "fd_alpenglow.h"
       2             : #include "../rewards/fd_epoch_inflation_account.h"
       3             : 
       4             : #include "../runtime/fd_accdb_svm.h"
       5             : #include "../runtime/fd_pubkey_utils.h"
       6             : #include "../runtime/program/vote/fd_vote_state_versioned.h"
       7             : #include "../runtime/program/vote/fd_vote_codec_tmpl.h"
       8             : #include "../runtime/sysvar/fd_sysvar_epoch_schedule.h"
       9             : 
      10             : FD_STATIC_ASSERT( MAX_EPOCH_CREDITS_HISTORY==64UL, epoch_credits_bound );
      11             : 
      12             : static int
      13             : vote_stakes_iter_kind_for_epoch( ulong fork_id,
      14          12 :                                  ulong epoch ) {
      15          12 :   ulong fork_epoch = (ulong)fd_vote_stakes_fork_epoch( fork_id );
      16          12 :   if( FD_LIKELY( epoch==fork_epoch ) ) return FD_VOTE_STAKES_ITER_T_2;
      17           6 :   if( FD_LIKELY( fork_epoch && epoch==fork_epoch-1UL ) ) return FD_VOTE_STAKES_ITER_T_3;
      18           0 :   return 0;
      19           6 : }
      20             : 
      21             : /* Footer cert verification */
      22             : 
      23             : /* TBD: this is a copy of the votor's ag_vote_signing_ser so the runtime
      24             :    does not link fd_choreo; keep the two in sync. */
      25             : 
      26           0 : #define VOTE_TAG_NOTAR (1U) /* WireConsensusMessageKind::NotarVote    */
      27             : #define VOTE_TAG_FINAL (2U) /* WireConsensusMessageKind::FinalizeVote */
      28             : #define VOTE_TAG_SKIP  (3U) /* WireConsensusMessageKind::SkipVote     */
      29             : 
      30             : #define VOTE_SIGNING_SER_MAX ( sizeof(uchar) + sizeof(ulong) + sizeof(fd_hash_t) + sizeof(ushort) )
      31             : 
      32             : static ulong
      33             : vote_signing_ser( uint          tag,
      34             :                   ulong         slot,
      35             :                   uchar const * block_hash,
      36             :                   ushort        shred_version,
      37           0 :                   uchar         buf[ static VOTE_SIGNING_SER_MAX ] ) {
      38           0 :   ulong off = 0UL;
      39           0 :   buf[ off ] = (uchar)tag;                                                          off += sizeof(uchar);
      40           0 :   FD_STORE( ulong, buf+off, slot );                                                 off += sizeof(ulong);
      41           0 :   if( FD_LIKELY( block_hash ) ) { memcpy( buf+off, block_hash, sizeof(fd_hash_t) ); off += sizeof(fd_hash_t); }
      42           0 :   FD_STORE( ushort, buf+off, shred_version );                                       off += sizeof(ushort);
      43           0 :   return off;
      44           0 : }
      45             : 
      46             : struct validator_set {
      47             :   ulong          epoch;
      48             :   ulong          validator_cnt;          /* 0 if not built */
      49             :   ulong          total_stake;
      50             :   blst_p1_affine bls_keys[ AG_VAT_MAX ]; /* indexed by rank */
      51             :   ulong          stakes  [ AG_VAT_MAX ]; /* indexed by rank */
      52             : };
      53             : typedef struct validator_set validator_set_t;
      54             : 
      55             : /* validator_set_for_slot makes set hold the validators of slot's epoch,
      56             :    rebuilding it only when the epoch differs from the one it holds.
      57             :    Returns 1 on success, 0 if the bank holds no ranked validators for
      58             :    the epoch. */
      59             : 
      60             : static int
      61             : validator_set_for_slot( validator_set_t * set,
      62             :                         fd_bank_t const * bank,
      63           0 :                         ulong             slot ) {
      64           0 :   ulong epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, slot, NULL );
      65           0 :   if( FD_LIKELY( set->validator_cnt && set->epoch==epoch ) ) return 1;
      66             : 
      67           0 :   ulong fork_id   = bank->vote_stakes_fork_id;
      68           0 :   int   iter_kind = vote_stakes_iter_kind_for_epoch( fork_id, epoch );
      69           0 :   if( FD_UNLIKELY( !iter_kind ) ) {
      70           0 :     FD_LOG_WARNING(( "slot %lu: cert epoch %lu is not t-2 or t-3", bank->f.slot, epoch ));
      71           0 :     return 0;
      72           0 :   }
      73             : 
      74           0 :   set->validator_cnt = 0UL;
      75           0 :   fd_vote_stakes_t const * vote_stakes = fd_bank_vote_stakes( bank );
      76           0 :   ulong cnt      = 0UL;
      77           0 :   ulong max_rank = 0UL;
      78           0 :   ulong total    = 0UL;
      79           0 :   uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
      80           0 :   for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, iter_kind, iter_mem );
      81           0 :        !fd_vote_stakes_iter_done( vote_stakes, fork_id, iter_kind, iter );
      82           0 :        fd_vote_stakes_iter_next( vote_stakes, fork_id, iter_kind, iter ) ) {
      83           0 :     fd_pubkey_t vote_key;
      84           0 :     ulong       stake;
      85           0 :     ushort      rank;
      86           0 :     uchar       bls_key[ FD_BLS_PUBKEY_UNCOMPRESSED_SZ ];
      87           0 :     fd_vote_stakes_iter_ele( vote_stakes, fork_id, iter_kind, iter, &vote_key, NULL, &stake, NULL, NULL, NULL, NULL, &rank, NULL, bls_key );
      88           0 :     if( FD_UNLIKELY( rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
      89           0 :     FD_TEST( rank<AG_VAT_MAX );
      90             : 
      91           0 :     if( FD_UNLIKELY( blst_p1_deserialize( set->bls_keys+rank, bls_key )!=BLST_SUCCESS ) ) {
      92           0 :       FD_LOG_WARNING(( "slot %lu: rank %u of epoch %lu has a malformed BLS key", bank->f.slot, rank, epoch ));
      93           0 :       return 0;
      94           0 :     }
      95           0 :     set->stakes[ rank ] = stake;
      96           0 :     max_rank = fd_ulong_max( max_rank, (ulong)rank );
      97           0 :     total   += stake;
      98           0 :     cnt++;
      99           0 :   }
     100           0 :   if( FD_UNLIKELY( !cnt || cnt!=max_rank+1UL ) ) {
     101           0 :     FD_LOG_WARNING(( "slot %lu: epoch %lu has %lu ranked validators, highest rank %lu", bank->f.slot, epoch, cnt, max_rank ));
     102           0 :     return 0;
     103           0 :   }
     104           0 :   set->epoch         = epoch;
     105           0 :   set->validator_cnt = cnt;
     106           0 :   set->total_stake   = total;
     107           0 :   return 1;
     108           0 : }
     109             : 
     110             : /* cert_verify checks one footer cert against its epoch's validator set.
     111             :    The aggregate signature verifies over the vote with wire tag vote_tag the
     112             :    signers would have signed, and if quorum_numer is nonzero the signers
     113             :    hold quorum_numer/5 of the epoch's stake.  i.e., for verifying
     114             :    reward certs, quorum_numer should be 0.
     115             : 
     116             :    Returns 1 if the cert verifies, 0 otherwise. */
     117             : 
     118             : static int
     119             : cert_verify( validator_set_t *              set,
     120             :              fd_bank_t const *              bank,
     121             :              fd_block_footer_cert_t const * cert,
     122             :              uint                           vote_tag,
     123             :              ulong                          quorum_numer,
     124           0 :              ushort                         shred_version ) {
     125           0 :   ulong bank_slot = bank->f.slot;
     126           0 :   ulong cert_slot = cert->slot;
     127             : 
     128           0 :   if( FD_UNLIKELY( !validator_set_for_slot( set, bank, cert_slot ) ) ) return 0;
     129             : 
     130           0 :   ulong last_rank = fd_bls_set_last( cert->signer_set ); /* ULONG_MAX when empty */
     131           0 :   if( FD_UNLIKELY( cert->nbits>set->validator_cnt || last_rank>=set->validator_cnt ) ) {
     132           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu names %u ranks (highest signer %lu) but its epoch has %lu validators",
     133           0 :                      bank_slot, cert_slot, cert->nbits, last_rank, set->validator_cnt ));
     134           0 :     return 0;
     135           0 :   }
     136             : 
     137           0 :   fd_bls_pub_t pub[1]; memset( pub, 0, sizeof(fd_bls_pub_t) );
     138           0 :   ulong        stake = 0UL;
     139           0 :   for( ulong rank=0UL; rank<=last_rank; rank++ ) {
     140           0 :     if( !fd_bls_set_test( cert->signer_set, rank ) ) continue;
     141           0 :     blst_p1_add_or_double_affine( pub, pub, set->bls_keys+rank );
     142           0 :     stake += set->stakes[ rank ];
     143           0 :   }
     144           0 :   if( FD_UNLIKELY( quorum_numer && (uint128)stake*(uint128)AG_QUORUM_THRESHOLD_DENOM<(uint128)set->total_stake*(uint128)quorum_numer ) ) {
     145           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has %lu of %lu stake, below %lu/%lu",
     146           0 :                      bank_slot, cert_slot, stake, set->total_stake, quorum_numer, AG_QUORUM_THRESHOLD_DENOM ));
     147           0 :     return 0;
     148           0 :   }
     149             : 
     150           0 :   blst_p2_affine sig_affine[1];
     151           0 :   fd_bls_sig_t   sig[1];
     152           0 :   if( FD_UNLIKELY( blst_p2_uncompress( sig_affine, cert->sig )!=BLST_SUCCESS || !blst_p2_affine_in_g2( sig_affine ) ) ) {
     153           0 :     FD_LOG_WARNING(( "slot %lu: footer cert for slot %lu has a malformed signature", bank_slot, cert_slot ));
     154           0 :     return 0;
     155           0 :   }
     156           0 :   blst_p2_from_affine( sig, sig_affine );
     157             : 
     158           0 :   uchar payload[ VOTE_SIGNING_SER_MAX ];
     159           0 :   ulong payload_sz = vote_signing_ser( vote_tag, cert_slot, vote_tag==VOTE_TAG_NOTAR ? cert->block_id.uc : NULL, shred_version, payload );
     160           0 :   if( FD_UNLIKELY( !fd_bls_agg_verify( payload, payload_sz, pub, sig ) ) ) {
     161           0 :     FD_LOG_WARNING(( "slot %lu: footer (is_reward %d) cert for slot %lu failed signature verification", bank_slot, !quorum_numer, cert_slot ));
     162           0 :     return 0;
     163           0 :   }
     164           0 :   return 1;
     165           0 : }
     166             : 
     167             : int
     168             : fd_alpenglow_footer_verify( fd_bank_t const *         bank,
     169             :                             fd_block_footer_t const * footer,
     170           0 :                             ushort                    shred_version ) {
     171           0 :   int has_certs = footer->has_fast_final_cert | footer->has_final_cert | footer->has_skip_reward_cert | footer->has_notar_reward_cert;
     172           0 :   if( FD_LIKELY( !has_certs ) ) return 0;
     173           0 :   if( FD_UNLIKELY( !shred_version ) ) {
     174           0 :     FD_LOG_WARNING(( "slot %lu: footer carries certs but the shred version is not known yet; cannot verify them", bank->f.slot ));
     175           0 :     return -1;
     176           0 :   }
     177             : 
     178             :   /* ~200 KiB, kept across calls: consecutive blocks' certs almost
     179             :      always share an epoch */
     180           0 :   static FD_TL validator_set_t set[1];
     181             : 
     182           0 :   if( footer->has_fast_final_cert ) {
     183           0 :     if( FD_UNLIKELY( !cert_verify( set, bank, &footer->fast_final_cert,   VOTE_TAG_NOTAR, AG_STRONG_QUORUM_THRESHOLD_NUMER, shred_version ) ) ) return -1;
     184           0 :   }
     185           0 :   if( footer->has_final_cert ) {
     186           0 :     if( FD_UNLIKELY( !cert_verify( set, bank, &footer->final_cert,        VOTE_TAG_FINAL, AG_QUORUM_THRESHOLD_NUMER, shred_version ) ) ) return -1;
     187           0 :     if( FD_UNLIKELY( !cert_verify( set, bank, &footer->notar_cert,        VOTE_TAG_NOTAR, AG_QUORUM_THRESHOLD_NUMER, shred_version ) ) ) return -1;
     188           0 :   }
     189           0 :   if( footer->has_skip_reward_cert ) {
     190           0 :     if( FD_UNLIKELY( !cert_verify( set, bank, &footer->skip_reward_cert,  VOTE_TAG_SKIP,  0UL, shred_version ) ) ) return -1;
     191           0 :   }
     192           0 :   if( footer->has_notar_reward_cert ) {
     193           0 :     if( FD_UNLIKELY( !cert_verify( set, bank, &footer->notar_reward_cert, VOTE_TAG_NOTAR, 0UL, shred_version ) ) ) return -1;
     194           0 :   }
     195           0 :   return 0;
     196           0 : }
     197             : 
     198             : /* credits_increment mirrors Agave's alpenglow increment_credits
     199             :    (vote_reward.rs): accumulate credits into the tail epoch_credits
     200             :    entry, starting a new entry on epoch changes and inserting the
     201             :    tower->alpenglow migration marker in the migration epoch.  The
     202             :    deque's capacity is MAX_EPOCH_CREDITS_HISTORY+1 while Agave pushes
     203             :    into an unbounded Vec before trimming, so we drop the head early
     204             :    when full; the final trimmed state is the same. */
     205             : 
     206             : static void
     207             : credits_increment( fd_vote_epoch_credits_t * ec,
     208             :                    ulong                     migration_epoch,
     209             :                    ulong                     epoch,
     210           6 :                    ulong                     credits /* nonzero */ ) {
     211           6 :   if( epoch==migration_epoch ) {
     212           6 :     int have_marker = 0;
     213           6 :     for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     214           6 :          !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     215           6 :          iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     216           3 :       if( fd_vote_epoch_credits_is_alpenglow_marker( deq_fd_vote_epoch_credits_t_iter_ele( ec, iter ) ) ) { have_marker = 1; break; }
     217           3 :     }
     218           6 :     if( !have_marker ) {
     219           3 :       if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     220           3 :       deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){ .epoch=ULONG_MAX, .credits=ULONG_MAX, .prev_credits=ULONG_MAX } );
     221           3 :     }
     222           6 :   }
     223             : 
     224           6 :   ulong cnt = deq_fd_vote_epoch_credits_t_cnt( ec );
     225           6 :   if( FD_UNLIKELY( !cnt ) ) {
     226           0 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){ .epoch=epoch, .credits=credits, .prev_credits=0UL } );
     227           0 :     return;
     228           0 :   }
     229             : 
     230           6 :   fd_vote_epoch_credits_t * last = deq_fd_vote_epoch_credits_t_peek_tail( ec );
     231             : 
     232           6 :   if( fd_vote_epoch_credits_is_alpenglow_marker( last ) ) {
     233             :     /* If there was a tower entry before the marker, its final credits
     234             :        form this entry's initial credits. */
     235           3 :     ulong final_tower_credits = 0UL;
     236           3 :     if( cnt>=2UL ) {
     237           0 :       ulong idx = 0UL;
     238           0 :       for( deq_fd_vote_epoch_credits_t_iter_t iter = deq_fd_vote_epoch_credits_t_iter_init( ec );
     239           0 :            !deq_fd_vote_epoch_credits_t_iter_done( ec, iter );
     240           0 :            iter = deq_fd_vote_epoch_credits_t_iter_next( ec, iter ) ) {
     241           0 :         if( idx==cnt-2UL ) { final_tower_credits = deq_fd_vote_epoch_credits_t_iter_ele( ec, iter )->credits; break; }
     242           0 :         idx++;
     243           0 :       }
     244           0 :     }
     245           3 :     if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     246           3 :     deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     247           3 :       .epoch        = epoch,
     248           3 :       .credits      = fd_ulong_sat_add( credits, final_tower_credits ),
     249           3 :       .prev_credits = final_tower_credits } );
     250           3 :     while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     251           3 :     return;
     252           3 :   }
     253             : 
     254           3 :   if( last->epoch==epoch ) {
     255           3 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     256           3 :     return;
     257           3 :   }
     258             : 
     259           0 :   if( last->credits==last->prev_credits ) {
     260             :     /* Different epochs but the latest epoch earned no credits, reuse
     261             :        the entry. */
     262           0 :     last->epoch   = epoch;
     263           0 :     last->credits = fd_ulong_sat_add( last->credits, credits );
     264           0 :     return;
     265           0 :   }
     266             : 
     267           0 :   ulong final_credits = last->credits;
     268           0 :   if( FD_UNLIKELY( deq_fd_vote_epoch_credits_t_full( ec ) ) ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     269           0 :   deq_fd_vote_epoch_credits_t_push_tail( ec, (fd_vote_epoch_credits_t){
     270           0 :     .epoch        = epoch,
     271           0 :     .credits      = fd_ulong_sat_add( credits, final_credits ),
     272           0 :     .prev_credits = final_credits } );
     273           0 :   while( deq_fd_vote_epoch_credits_t_cnt( ec )>MAX_EPOCH_CREDITS_HISTORY ) deq_fd_vote_epoch_credits_t_pop_head( ec );
     274           0 : }
     275             : 
     276             : /* vs_maybe_update_votes mirrors VoteState::maybe_update_votes: the
     277             :    votes deque is replaced by a single landed vote on the latest voted
     278             :    slot, and last_timestamp advances to the slot's timestamp if newer. */
     279             : 
     280             : static void
     281             : vs_maybe_update_votes( fd_vote_state_versioned_t * vs,
     282             :                        ulong                       slot,
     283          12 :                        long                        slot_timestamp_ns ) {
     284          12 :   ulong latest = slot;
     285          12 :   ulong const * last_voted = fd_vsv_get_last_voted_slot( vs );
     286          12 :   if( last_voted && *last_voted>latest ) latest = *last_voted;
     287          12 :   ulong const * root = fd_vsv_get_root_slot( vs );
     288          12 :   if( root && *root>latest ) latest = *root;
     289             : 
     290          12 :   fd_landed_vote_t * votes = fd_vsv_get_votes_mutable( vs );
     291          12 :   deq_fd_landed_vote_t_remove_all( votes );
     292          12 :   deq_fd_landed_vote_t_push_tail( votes, (fd_landed_vote_t){ .latency=0, .lockout={ .slot=latest, .confirmation_count=1U } } );
     293             : 
     294          12 :   long timestamp = slot_timestamp_ns/1000000000L;
     295          12 :   if( timestamp>fd_vsv_get_last_timestamp( vs )->timestamp ) {
     296           0 :     fd_vsv_set_last_timestamp( vs, &(fd_vote_block_timestamp_t){ .slot=slot, .timestamp=timestamp } );
     297           0 :   }
     298          12 : }
     299             : 
     300             : /* vote_update describes the mutations to apply to one vote account. */
     301             : 
     302             : struct vote_update {
     303             :   int   update_votes; ulong vote_slot; long vote_ts_ns;
     304             :   int   update_root;  ulong root_slot;
     305             :   ulong credits;      /* 0 = none */
     306             :   ulong migration_epoch;
     307             :   ulong current_epoch;
     308             : };
     309             : typedef struct vote_update vote_update_t;
     310             : 
     311             : static int
     312             : vote_account_read( fd_bank_t *                 bank,
     313             :                    fd_accdb_t *                accdb,
     314             :                    fd_pubkey_t const *         pk,
     315             :                    fd_vote_state_versioned_t * vs,
     316             :                    ulong *                     out_data_len,
     317          15 :                    fd_pubkey_t *               out_owner ) {
     318          15 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, pk->uc );
     319          15 :   if( FD_UNLIKELY( !acc.lamports || !fd_vsv_is_correct_size_owner_and_init( acc.owner, acc.data, acc.data_len ) ) ) {
     320           0 :     fd_accdb_unread_one( accdb, &acc );
     321           0 :     return 0;
     322           0 :   }
     323          15 :   if( FD_UNLIKELY( fd_vsv_deserialize( &acc, vs ) ) ) {
     324           0 :     fd_accdb_unread_one( accdb, &acc );
     325           0 :     return 0;
     326           0 :   }
     327          15 :   *out_data_len = acc.data_len;
     328          15 :   fd_memcpy( out_owner->uc, acc.owner, sizeof(fd_pubkey_t) );
     329          15 :   fd_accdb_unread_one( accdb, &acc );
     330          15 :   return 1;
     331          15 : }
     332             : 
     333             : static void
     334             : vote_account_write( fd_bank_t *                 bank,
     335             :                     fd_accdb_t *                accdb,
     336             :                     fd_capture_ctx_t *          capture_ctx,
     337             :                     fd_pubkey_t const *         pk,
     338             :                     fd_pubkey_t const *         owner,
     339             :                     ulong                       data_len,
     340             :                     fd_vote_state_versioned_t * vs,
     341          15 :                     vote_update_t const *       upd ) {
     342          15 :   static FD_TL uchar buf[ 8192UL ];
     343             : 
     344          15 :   if( upd->update_votes ) vs_maybe_update_votes( vs, upd->vote_slot, upd->vote_ts_ns );
     345          15 :   if( upd->update_root ) {
     346           6 :     ulong const * root = fd_vsv_get_root_slot( vs );
     347           6 :     ulong latest_root  = fd_ulong_max( root ? *root : upd->root_slot, upd->root_slot );
     348           6 :     fd_vsv_set_root_slot( vs, &latest_root );
     349           6 :   }
     350          15 :   if( upd->credits ) credits_increment( fd_vsv_get_epoch_credits_mutable( vs ), upd->migration_epoch, upd->current_epoch, upd->credits );
     351             : 
     352          15 :   fd_memset( buf, 0, data_len );
     353          15 :   if( FD_UNLIKELY( fd_vote_state_versioned_serialize( vs, buf, data_len ) ) ) {
     354           0 :     FD_BASE58_ENCODE_32_BYTES( pk->uc, pk_b58 );
     355           0 :     FD_LOG_WARNING(( "slot %lu: vote account %s failed to serialize; skipping", bank->f.slot, pk_b58 ));
     356           0 :     return;
     357           0 :   }
     358          15 :   fd_accdb_svm_write( bank, accdb, capture_ctx, pk, owner, buf, data_len, 0UL, 0, 1 );
     359          15 : }
     360             : 
     361             : static void
     362             : vote_account_modify( fd_bank_t *           bank,
     363             :                      fd_accdb_t *          accdb,
     364             :                      fd_capture_ctx_t *    capture_ctx,
     365             :                      fd_pubkey_t const *   pk,
     366           9 :                      vote_update_t const * upd ) {
     367           9 :   static FD_TL fd_vote_state_versioned_t vs[1];
     368           9 :   ulong       data_len;
     369           9 :   fd_pubkey_t owner;
     370           9 :   if( FD_UNLIKELY( !vote_account_read( bank, accdb, pk, vs, &data_len, &owner ) ) ) return;
     371           9 :   vote_account_write( bank, accdb, capture_ctx, pk, &owner, data_len, vs, upd );
     372           9 : }
     373             : 
     374             : void
     375             : fd_alpenglow_pda( char const *  seed,
     376          84 :                   fd_pubkey_t * out ) {
     377          84 :   fd_pubkey_t const * feature_id = &ids[ offsetof( fd_features_t, alpenglow )>>3 ].id;
     378             : 
     379          84 :   uchar const * seed_   = (uchar const *)seed;
     380          84 :   ulong         seed_sz = strlen( seed );
     381          84 :   uchar         bump;
     382          84 :   uint          custom_err;
     383          84 :   FD_TEST( fd_pubkey_find_program_address( feature_id, 1UL, &seed_, &seed_sz, out, &bump, &custom_err )==FD_PUBKEY_SUCCESS );
     384          84 : }
     385             : 
     386             : ulong
     387             : fd_alpenglow_migration_slot( fd_bank_t *  bank,
     388        5898 :                              fd_accdb_t * accdb ) {
     389        5898 :   if( FD_UNLIKELY( !FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) ) return ULONG_MAX;
     390             : 
     391          84 :   fd_pubkey_t genesis_cert_addr;
     392          84 :   fd_alpenglow_pda( "carlgration", &genesis_cert_addr );
     393             : 
     394          84 :   ulong    migration_slot = ULONG_MAX;
     395          84 :   fd_acc_t acc = fd_accdb_read_one( accdb, bank->accdb_fork_id, genesis_cert_addr.uc );
     396          84 :   if( FD_LIKELY( acc.lamports && acc.data_len>=8UL ) ) migration_slot = FD_LOAD( ulong, acc.data );
     397          84 :   fd_accdb_unread_one( accdb, &acc );
     398          84 :   return migration_slot;
     399        5898 : }
     400             : 
     401             : /* slot_timestamp backdates the footer timestamp by the duration of
     402             :    slots (slot, bank_slot], taking into account reduced slot times.
     403             : 
     404             :    https://github.com/anza-xyz/agave/blob/v4.3/runtime/src/block_component_processor/vote_reward.rs#L505 */
     405             : static long
     406             : slot_timestamp( fd_bank_t * bank,
     407             :                 ulong       slot,
     408          12 :                 ulong       footer_time_nanos ) {
     409          12 :   ulong dur = fd_slot_params_slot_range_duration_ns( bank, slot+1UL, bank->f.slot+1UL );
     410          12 :   return fd_long_sat_sub( (long)footer_time_nanos, (long)dur );
     411          12 : }
     412             : 
     413             : int
     414             : fd_alpenglow_rewards_apply( fd_bank_t *               bank,
     415             :                             fd_accdb_t *              accdb,
     416             :                             fd_capture_ctx_t *        capture_ctx,
     417          12 :                             fd_block_footer_t const * footer ) {
     418             : 
     419          12 :   ulong bank_slot         = bank->f.slot;
     420          12 :   ulong footer_time_nanos = footer->block_producer_time_nanos;
     421          12 :   ulong current_epoch     = fd_slot_to_epoch( &bank->f.epoch_schedule, bank_slot, NULL );
     422          12 :   ulong migration_epoch   = ULONG_MAX;
     423          12 :   ulong leader_credits    = 0UL;
     424             : 
     425             :   /* credits for the attested voters of the reward slot */
     426             : 
     427          12 :   if( footer->has_skip_reward_cert || footer->has_notar_reward_cert ) {
     428           6 :     fd_bls_set_t reward_set[ fd_bls_set_word_cnt ];
     429           6 :     fd_bls_set_null( reward_set );
     430           6 :     ulong skip_slot = ULONG_MAX, notar_slot = ULONG_MAX;
     431           6 :     if( footer->has_skip_reward_cert ) {
     432           6 :       skip_slot = footer->skip_reward_cert.slot;
     433           6 :       fd_bls_set_union( reward_set, reward_set, footer->skip_reward_cert.signer_set );
     434           6 :     }
     435           6 :     if( footer->has_notar_reward_cert ) {
     436           0 :       notar_slot = footer->notar_reward_cert.slot;
     437           0 :       fd_bls_set_union( reward_set, reward_set, footer->notar_reward_cert.signer_set );
     438           0 :     }
     439           6 :     if( FD_UNLIKELY( skip_slot!=ULONG_MAX && notar_slot!=ULONG_MAX && skip_slot!=notar_slot ) ) {
     440           0 :       FD_LOG_WARNING(( "slot %lu: reward cert slots differ: skip %lu, notar %lu", bank_slot, skip_slot, notar_slot ));
     441           0 :       return -1;
     442           0 :     }
     443           6 :     ulong reward_slot = fd_ulong_min( skip_slot, notar_slot );
     444             : 
     445           6 :     ulong migration_slot = fd_alpenglow_migration_slot( bank, accdb );
     446           6 :     if( FD_UNLIKELY( migration_slot==ULONG_MAX ) ) {
     447           0 :       FD_LOG_WARNING(( "slot %lu: reward cert but no genesis certificate account", bank_slot ));
     448           0 :       return -1;
     449           0 :     }
     450           6 :     migration_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, migration_slot, NULL );
     451             : 
     452           6 :     if( FD_UNLIKELY( fd_ulong_sat_add( reward_slot, FD_NUM_SLOTS_FOR_REWARD )!=bank_slot || reward_slot<=migration_slot ) ) {
     453           0 :       FD_LOG_WARNING(( "slot %lu: invalid reward cert slot %lu (migration slot %lu)", bank_slot, reward_slot, migration_slot ));
     454           0 :       return -1;
     455           0 :     }
     456             : 
     457           6 :     ulong reward_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, reward_slot, NULL );
     458             : 
     459             :     /* Changed to charge rewards against the processing bank's epoch
     460             :        inflation budget, not the reward slot's epoch.  Handles rewards
     461             :        going over budget when inflation rate decreases.
     462             :        https://github.com/anza-xyz/agave/blob/386cf57c45e135d8a3a8b7d16877eb896f695c64/runtime/src/block_component_processor/vote_reward.rs#L194 */
     463           6 :     fd_epoch_inflation_account_state_t inflation[1];
     464           6 :     fd_epoch_inflation_state_t const * inflation_state = NULL;
     465           6 :     if( FD_LIKELY( fd_epoch_inflation_account_read( bank, accdb, inflation ) ) ) {
     466           6 :       if(      inflation->current.epoch==current_epoch                     ) inflation_state = &inflation->current;
     467           0 :       else if( inflation->has_prev && inflation->prev.epoch==current_epoch ) inflation_state = &inflation->prev;
     468           6 :     }
     469           6 :     if( FD_UNLIKELY( !inflation_state ) ) {
     470           0 :       FD_LOG_WARNING(( "slot %lu: no epoch inflation state for epoch %lu", bank_slot, current_epoch ));
     471           0 :       return -1;
     472           0 :     }
     473           6 :     ulong max_reward      = inflation_state->max_possible_validator_reward;
     474           6 :     ulong slots_per_epoch = inflation_state->slots_per_epoch;
     475             : 
     476           6 :     ulong total_stake = fd_vote_stakes_total_stake( fd_bank_vote_stakes( bank ), reward_epoch );
     477           6 :     if( FD_UNLIKELY( !total_stake ) ) {
     478           0 :       FD_LOG_WARNING(( "slot %lu: no epoch stakes for reward epoch %lu", bank_slot, reward_epoch ));
     479           0 :       return -1;
     480           0 :     }
     481             : 
     482           6 :     long ts_ns = slot_timestamp( bank, reward_slot, footer_time_nanos );
     483           6 :     int have_ranked_vote = 0;
     484           6 :     fd_vote_stakes_t const * vote_stakes = fd_bank_vote_stakes( bank );
     485           6 :     int iter_kind = vote_stakes_iter_kind_for_epoch( bank->vote_stakes_fork_id, reward_epoch );
     486           6 :     if( FD_UNLIKELY( !iter_kind ) ) {
     487           0 :       FD_LOG_WARNING(( "slot %lu: reward epoch %lu is not t-2 or t-3", bank_slot, reward_epoch ));
     488           0 :       return -1;
     489           0 :     }
     490           6 :     uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     491           6 :     for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter_mem );
     492          18 :          !fd_vote_stakes_iter_done( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter );
     493          12 :          fd_vote_stakes_iter_next( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter ) ) {
     494          12 :       fd_pubkey_t vote_key;
     495          12 :       ulong       stake;
     496          12 :       ushort      rank;
     497          12 :       fd_vote_stakes_iter_ele( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter,
     498          12 :                                &vote_key, NULL, &stake, NULL, NULL, NULL, NULL, &rank, NULL, NULL );
     499          12 :       if( FD_UNLIKELY( rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
     500          12 :       FD_TEST( rank<AG_VAT_MAX );
     501          12 :       have_ranked_vote = 1;
     502          12 :       ulong r = (ulong)rank;
     503          12 :       if( !fd_bls_set_test( reward_set, r ) ) continue;
     504             :       /* per-slot, stake-fractional reward; split half validator, half
     505             :          (rounded up) leader */
     506           6 :       uint128 numerator   = (uint128)max_reward*(uint128)stake;
     507           6 :       uint128 denominator = (uint128)slots_per_epoch*(uint128)total_stake;
     508           6 :       ulong   reward      = denominator ? (ulong)( numerator/denominator ) : 0UL;
     509           6 :       ulong   validator_reward = reward/2UL;
     510             : 
     511           6 :       vote_update_t upd = {
     512           6 :         .update_votes    = 1, .vote_slot = reward_slot, .vote_ts_ns = ts_ns,
     513           6 :         .credits         = validator_reward,
     514           6 :         .migration_epoch = migration_epoch,
     515           6 :         .current_epoch   = current_epoch,
     516           6 :       };
     517           6 :       static FD_TL fd_vote_state_versioned_t vs[1];
     518           6 :       ulong       data_len;
     519           6 :       fd_pubkey_t owner;
     520             : 
     521             :       /* Skip this node if the vote account cannot be deserialized
     522             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L378-L380 */
     523           6 :       if( FD_UNLIKELY( !vote_account_read( bank, accdb, &vote_key, vs, &data_len, &owner ) ) ) continue;
     524             : 
     525             :       /* Only accumulate the leader credits if the vote account could
     526             :          be successfully read.
     527             : 
     528             :          https://github.com/anza-xyz/agave/blob/v4.3.0-beta.3/runtime/src/block_component_processor/vote_reward.rs#L249 */
     529           6 :       leader_credits = fd_ulong_sat_add( leader_credits, reward-validator_reward );
     530           6 :       vote_account_write( bank, accdb, capture_ctx, &vote_key, &owner, data_len, vs, &upd );
     531           6 :     }
     532           6 :     if( FD_UNLIKELY( !have_ranked_vote ) ) {
     533           0 :       FD_LOG_WARNING(( "slot %lu: no ranked validators for reward slot %lu", bank_slot, reward_slot ));
     534           0 :       return -1;
     535           0 :     }
     536           6 :   }
     537             : 
     538             :   /* finalization cert: root/votes/timestamp for the signers */
     539             : 
     540          12 :   if( footer->has_fast_final_cert || footer->has_final_cert ) {
     541           6 :     ulong        final_slot;
     542           6 :     fd_bls_set_t final_set[ fd_bls_set_word_cnt ];
     543           6 :     if( footer->has_fast_final_cert ) {
     544           6 :       final_slot = footer->fast_final_cert.slot;
     545           6 :       fd_bls_set_copy( final_set, footer->fast_final_cert.signer_set );
     546           6 :     } else {
     547           0 :       final_slot = footer->final_cert.slot;
     548           0 :       fd_bls_set_union( final_set, footer->final_cert.signer_set, footer->notar_cert.signer_set );
     549           0 :     }
     550             : 
     551           6 :     ulong final_epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, final_slot, NULL );
     552             : 
     553           6 :     long ts_ns = slot_timestamp( bank, final_slot, footer_time_nanos );
     554           6 :     int have_ranked_vote = 0;
     555           6 :     fd_vote_stakes_t const * vote_stakes = fd_bank_vote_stakes( bank );
     556           6 :     int iter_kind = vote_stakes_iter_kind_for_epoch( bank->vote_stakes_fork_id, final_epoch );
     557           6 :     if( FD_UNLIKELY( !iter_kind ) ) {
     558           0 :       FD_LOG_WARNING(( "slot %lu: finalization epoch %lu is not t-2 or t-3", bank_slot, final_epoch ));
     559           0 :       return -1;
     560           0 :     }
     561           6 :     uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
     562           6 :     for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter_mem );
     563          18 :          !fd_vote_stakes_iter_done( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter );
     564          12 :          fd_vote_stakes_iter_next( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter ) ) {
     565          12 :       fd_pubkey_t vote_key;
     566          12 :       ushort      rank;
     567          12 :       fd_vote_stakes_iter_ele( vote_stakes, bank->vote_stakes_fork_id, iter_kind, iter,
     568          12 :                                &vote_key, NULL, NULL, NULL, NULL, NULL, NULL, &rank, NULL, NULL );
     569          12 :       if( FD_UNLIKELY( rank==FD_VOTE_STAKES_ALPENGLOW_RANK_NULL ) ) continue;
     570          12 :       FD_TEST( rank<AG_VAT_MAX );
     571          12 :       have_ranked_vote = 1;
     572          12 :       ulong r = (ulong)rank;
     573          12 :       if( !fd_bls_set_test( final_set, r ) ) continue;
     574           6 :       vote_update_t upd = {
     575           6 :         .update_root  = 1, .root_slot = final_slot,
     576           6 :         .update_votes = 1, .vote_slot = final_slot, .vote_ts_ns = ts_ns,
     577           6 :       };
     578           6 :       vote_account_modify( bank, accdb, capture_ctx, &vote_key, &upd );
     579           6 :     }
     580           6 :     if( FD_UNLIKELY( !have_ranked_vote ) ) {
     581           0 :       FD_LOG_WARNING(( "slot %lu: no ranked validators for finalized slot %lu", bank_slot, final_slot ));
     582           0 :       return -1;
     583           0 :     }
     584           6 :   }
     585             : 
     586             :   /* leader rewards */
     587             : 
     588          12 :   if( leader_credits ) {
     589           3 :     fd_pubkey_t const * leader_vote_pubkey = fd_epoch_leaders_get_vote( fd_bank_epoch_leaders_query( bank, current_epoch ), bank_slot );
     590           3 :     if( FD_UNLIKELY( !leader_vote_pubkey ) ) {
     591           0 :       FD_LOG_WARNING(( "slot %lu: leader vote account unknown; dropping %lu leader reward credits (bank hash will diverge)", bank_slot, leader_credits ));
     592           3 :     } else {
     593           3 :       vote_update_t upd = {
     594           3 :         .credits         = leader_credits,
     595           3 :         .migration_epoch = migration_epoch,
     596           3 :         .current_epoch   = current_epoch,
     597           3 :       };
     598           3 :       vote_account_modify( bank, accdb, capture_ctx, leader_vote_pubkey, &upd );
     599           3 :     }
     600           3 :   }
     601             : 
     602          12 :   return 0;
     603          12 : }

Generated by: LCOV version 1.14