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