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 : }
|