diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 3e3c077bf06..23af9c7d02e 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -2221,7 +2221,6 @@ | votor_​vote_​rx
{vote_​rx_​result="duplicate"} | counter | Result of processing an inbound vote (per vote) (Voter had already cast this vote) | | votor_​vote_​rx
{vote_​rx_​result="slashable"} | counter | Result of processing an inbound vote (per vote) (Vote constitutes a slashable offence) | | votor_​vote_​rx
{vote_​rx_​result="banned"} | counter | Result of processing an inbound vote (per vote) (Sender is banned for a failed signature verification) | -| votor_​vote_​rx
{vote_​rx_​result="failed_​verify"} | counter | Result of processing an inbound vote (per vote) (Vote's own signature failed the check and the vote was dropped) | | votor_​cert_​rx
{cert_​rx_​result="success"} | counter | Result of processing an inbound cert (per cert) (Cert was handed to the pool and accepted) | | votor_​cert_​rx
{cert_​rx_​result="bad_​size"} | counter | Result of processing an inbound cert (per cert) (Cert was truncated or had trailing bytes) | | votor_​cert_​rx
{cert_​rx_​result="bad_​encoding"} | counter | Result of processing an inbound cert (per cert) (Cert had an invalid version, tag, bitmap, or signature encoding) | diff --git a/src/ballet/bls/Local.mk b/src/ballet/bls/Local.mk index a3e2a07b8c6..ead518a144f 100644 --- a/src/ballet/bls/Local.mk +++ b/src/ballet/bls/Local.mk @@ -1,4 +1,4 @@ -$(call add-hdrs,fd_bls12_381.h fd_bls.h) +$(call add-hdrs,fd_bls12_381.h fd_bls.h fd_bls_err.h) $(call add-objs,fd_bls12_381 fd_bls,fd_ballet) $(call make-unit-test,test_bls12_381,test_bls12_381,fd_ballet fd_util,$(BLST_LIBS)) $(call run-unit-test,test_bls12_381) diff --git a/src/ballet/bls/fd_bls.c b/src/ballet/bls/fd_bls.c index 1aec29cb4cd..6b73e090438 100644 --- a/src/ballet/bls/fd_bls.c +++ b/src/ballet/bls/fd_bls.c @@ -28,18 +28,18 @@ fd_bls_sec_derive( fd_bls_sec_t * sk, } void -fd_bls_sig_ser( uchar out[ static FD_BLS_SIG_SZ ], - fd_bls_sig_t const * sig ) { +fd_bls_sig_ser( fd_bls_sig_t const * sig, + uchar buf[ static FD_BLS_SIG_SZ ] ) { blst_p2_affine a[1]; blst_p2_to_affine( a, sig ); - blst_p2_affine_serialize( out, a ); + blst_p2_affine_serialize( buf, a ); } int fd_bls_sig_de( fd_bls_sig_t * sig, - uchar const in[ static FD_BLS_SIG_SZ ] ) { + uchar const buf[ static FD_BLS_SIG_SZ ] ) { blst_p2_affine a[1]; - if( FD_UNLIKELY( blst_p2_deserialize( a, in )!=BLST_SUCCESS ) ) return -1; + if( FD_UNLIKELY( blst_p2_deserialize( a, buf )!=BLST_SUCCESS ) ) return -1; if( FD_UNLIKELY( !blst_p2_affine_in_g2( a ) ) ) return -1; blst_p2_from_affine( sig, a ); return 0; @@ -65,14 +65,29 @@ pub_from_bytes( blst_p1_affine * out, int fd_bls_pub_de( fd_bls_pub_t * pub, - uchar const * in, - ulong in_sz ) { + uchar const * buf, + ulong buf_sz ) { blst_p1_affine a[1]; - if( FD_UNLIKELY( !pub_from_bytes( a, in, in_sz ) ) ) return -1; + if( FD_UNLIKELY( !pub_from_bytes( a, buf, buf_sz ) ) ) return -1; blst_p1_from_affine( pub, a ); return 0; } +fd_bls_agg_t * +fd_bls_agg_construct( fd_bls_agg_t * agg, + fd_bls_pub_t const * pub, + fd_bls_sig_t const * sig, + fd_bls_set_t const * set ) { + fd_bls_set_copy( agg->set, set ); + for( ulong rank = fd_bls_set_const_iter_init( set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( set, rank ) ) { + blst_p1_add_or_double( &agg->pub, &agg->pub, pub+rank ); + blst_p2_add_or_double( &agg->sig, &agg->sig, sig+rank ); + } + return agg; +} + int fd_bls_agg_verify( uchar const * msg, ulong msg_sz, @@ -146,3 +161,22 @@ fd_bls_agg_verify_bisect( fd_bls_agg_t const * agg, } return fd_bls_agg_verify_linear( &key, msg, msg_sz, pub, sig, bad ); } + +int +fd_bls_agg_verify_subtract( fd_bls_agg_t * agg, + uchar const * msg, + ulong msg_sz, + fd_bls_pub_t const * pub, + fd_bls_sig_t const * sig, + fd_bls_set_t * bad ) { + if( FD_LIKELY( fd_bls_agg_verify( msg, msg_sz, &agg->pub, &agg->sig ) ) ) { fd_bls_set_null( bad ); return FD_BLS_SUCCESS; } + fd_bls_agg_verify_bisect( agg, msg, msg_sz, pub, sig, bad ); + fd_bls_agg_t sub = { 0 }; + fd_bls_agg_construct( &sub, pub, sig, bad ); + blst_p1_cneg( &sub.pub, 1 ); blst_p1_add_or_double( &agg->pub, &agg->pub, &sub.pub ); + blst_p2_cneg( &sub.sig, 1 ); blst_p2_add_or_double( &agg->sig, &agg->sig, &sub.sig ); + fd_bls_set_subtract( agg->set, agg->set, bad ); + if( FD_UNLIKELY( fd_bls_set_is_null( agg->set ) ) ) return FD_BLS_ERR_EMPTY; + if( FD_UNLIKELY( blst_p1_is_inf( &agg->pub ) ) ) return FD_BLS_ERR_INFINITY; + return FD_BLS_SUCCESS; +} diff --git a/src/ballet/bls/fd_bls.h b/src/ballet/bls/fd_bls.h index f7b4412915d..61de0a3e0dc 100644 --- a/src/ballet/bls/fd_bls.h +++ b/src/ballet/bls/fd_bls.h @@ -3,6 +3,7 @@ #include "../../util/fd_util.h" #include "../../third_party/blst/bindings/blst.h" +#include "fd_bls_err.h" #define FD_BLS_SEC_SZ (32UL) #define FD_BLS_PUB_SZ (96UL) @@ -31,46 +32,39 @@ typedef struct fd_bls_agg fd_bls_agg_t; FD_PROTOTYPES_BEGIN -/* SecretKey::to_pk */ - void fd_bls_sec_to_pub( fd_bls_sec_t const * sec, fd_bls_pub_t * pub ); -/* solana_bls_signatures::SecretKey::derive */ - void fd_bls_sec_derive( fd_bls_sec_t * sec, uchar const * ikm, ulong ikm_sz ); -/* SecretKey::sign_bytes */ - void fd_bls_sec_sign( fd_bls_sec_t const * sec, uchar const * msg, ulong msg_sz, fd_bls_sig_t * sig ); -/* fd_bls_sig_ser writes the canonical uncompressed encoding of sig - (FD_BLS_SIG_SZ bytes) to out. fd_bls_sig_de parses such an encoding - into sig, returning 0 on success and -1 if the bytes are not a valid - G2 point. Translates between raw bytes and the canonical encoding. */ - void -fd_bls_sig_ser( uchar out[ static FD_BLS_SIG_SZ ], - fd_bls_sig_t const * sig ); +fd_bls_sig_ser( fd_bls_sig_t const * sig, + uchar buf[ static FD_BLS_SIG_SZ ] ); int fd_bls_sig_de( fd_bls_sig_t * sig, - uchar const in[ static FD_BLS_SIG_SZ ] ); - -/* PublicKey::try_from_bytes */ + uchar const buf[ static FD_BLS_SIG_SZ ] ); int fd_bls_pub_de( fd_bls_pub_t * pub, - uchar const * in, - ulong in_sz ); + uchar const * buf, + ulong buf_sz ); + +fd_bls_agg_t * +fd_bls_agg_construct( fd_bls_agg_t * agg, + fd_bls_pub_t const * pub, + fd_bls_sig_t const * sig, + fd_bls_set_t const * set ); int fd_bls_agg_verify( uchar const * msg, @@ -94,6 +88,14 @@ fd_bls_agg_verify_bisect( fd_bls_agg_t const * agg, fd_bls_sig_t const * sig, fd_bls_set_t * bad ); +int +fd_bls_agg_verify_subtract( fd_bls_agg_t * agg, + uchar const * msg, + ulong msg_sz, + fd_bls_pub_t const * pub, + fd_bls_sig_t const * sig, + fd_bls_set_t * bad ); + FD_PROTOTYPES_END #endif diff --git a/src/ballet/bls/fd_bls12_381.h b/src/ballet/bls/fd_bls12_381.h index ec4be0942d2..6d4643cc8a5 100644 --- a/src/ballet/bls/fd_bls12_381.h +++ b/src/ballet/bls/fd_bls12_381.h @@ -2,8 +2,7 @@ #define HEADER_fd_src_ballet_bls_fd_bls12_381_h #include "../fd_ballet_base.h" - -#define FD_BLS_SUCCESS (0) +#include "fd_bls_err.h" /* Max size for the pairing function */ #define FD_BLS12_381_PAIRING_BATCH_SZ (8UL) diff --git a/src/ballet/bls/fd_bls_err.h b/src/ballet/bls/fd_bls_err.h new file mode 100644 index 00000000000..33d88213971 --- /dev/null +++ b/src/ballet/bls/fd_bls_err.h @@ -0,0 +1,8 @@ +#ifndef HEADER_fd_src_ballet_bls_fd_bls_err_h +#define HEADER_fd_src_ballet_bls_fd_bls_err_h + +#define FD_BLS_SUCCESS (0) +#define FD_BLS_ERR_EMPTY (-1) +#define FD_BLS_ERR_INFINITY (-2) + +#endif /* HEADER_fd_src_ballet_bls_fd_bls_err_h */ diff --git a/src/choreo/votor/ag_pool.c b/src/choreo/votor/ag_pool.c index 4d6d097afd0..a82c3f9a785 100644 --- a/src/choreo/votor/ag_pool.c +++ b/src/choreo/votor/ag_pool.c @@ -103,7 +103,7 @@ ag_pool_align( void ) { ulong ag_pool_footprint( ulong slot_max ) { - if( FD_UNLIKELY( slot_maxfinality_tracker, slot ); + self->parent_ready_tracker->root = slot; +} + +void +ag_pool_fini( ag_pool_t * self ) { + ag_finality_tracker_fini( self->finality_tracker ); + self->parent_ready_tracker->root = ULONG_MAX; +} + FD_FN_CONST char const * ag_pool_strerror( int err ) { switch( err ) { @@ -277,7 +290,6 @@ ag_pool_strerror( int err ) { case AG_POOL_ERR_DUPLICATE: return "duplicate vote or cert"; case AG_POOL_ERR_SLASHABLE: return "vote constitutes a slashable offence"; case AG_POOL_ERR_CERT_VERIFY: return "cert failed the signature or threshold check"; - case AG_POOL_ERR_VOTE_VERIFY: return "vote(s) failed the signature check"; default: return "unknown"; } } @@ -314,7 +326,8 @@ handle_finalization( ag_pool_t * self, pool_events_push( self->pool_events, event ); } ulong first_unpruned_slot = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ); - for( ulong slot = self->parent_ready_tracker->root; slotparent_ready_tracker->root, AG_REWARD_SLOT_DELTA ); slotslot_states->map, &slot, NULL, self->slot_states->pool ); if( FD_LIKELY( ele ) ) slot_state_pool_ele_release( self->slot_states->pool, ele ); } @@ -434,19 +447,6 @@ ag_pool_advance_epoch( ag_pool_t * self, } } -void -ag_pool_init( ag_pool_t * self, - ulong slot ) { - ag_finality_tracker_init( self->finality_tracker, slot ); - self->parent_ready_tracker->root = slot; -} - -void -ag_pool_fini( ag_pool_t * self ) { - ag_finality_tracker_fini( self->finality_tracker ); - self->parent_ready_tracker->root = ULONG_MAX; -} - int ag_pool_add_cert( ag_pool_t * self, ag_cert_t const * cert, @@ -454,7 +454,7 @@ ag_pool_add_cert( ag_pool_t * self, ulong slot = ag_cert_slot( cert ); fd_bls_set_null( bad ); - ulong slot_far_in_future = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ) + self->slot_max; + ulong slot_far_in_future = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ) + self->slot_max - AG_REWARD_SLOT_DELTA; if( FD_UNLIKELY( slotfinality_tracker ) || slot>=slot_far_in_future ) ) return AG_POOL_ERR_SLOT_OUT_OF_BOUNDS; ag_slot_state_t * state = slot_state( self, slot ); @@ -483,8 +483,10 @@ ag_pool_add_vote( ag_pool_t * self, ulong slot = ag_vote_slot( vote ); fd_bls_set_null( bad ); - ulong slot_far_in_future = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ) + self->slot_max; - if( FD_UNLIKELY( slotfinality_tracker ) || slot>=slot_far_in_future ) ) { + ulong first_unpruned_slot = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ); + ulong retained_slot = fd_ulong_sat_sub( first_unpruned_slot, AG_REWARD_SLOT_DELTA ); + ulong slot_far_in_future = first_unpruned_slot + self->slot_max - AG_REWARD_SLOT_DELTA; + if( FD_UNLIKELY( slot=slot_far_in_future ) ) { return AG_POOL_ERR_SLOT_OUT_OF_BOUNDS; } @@ -501,12 +503,12 @@ ag_pool_add_vote( ag_pool_t * self, ag_event_cert_t cert_events [ AG_SLOT_STATE_OUT_CERT_MAX ]; ulong cert_event_cnt; ag_event_pool_t pool_events [ AG_SLOT_STATE_OUT_EVENT_MAX ]; ulong pool_event_cnt; ag_event_repair_t repair_events[ AG_SLOT_STATE_OUT_REPAIR_MAX ]; ulong repair_event_cnt; - int ok = ag_slot_state_add_vote( slot_state_, vote, voter_stake, cert_events, &cert_event_cnt, pool_events, &pool_event_cnt, repair_events, &repair_event_cnt, bad ); + ag_slot_state_add_vote( slot_state_, vote, voter_stake, cert_events, &cert_event_cnt, pool_events, &pool_event_cnt, repair_events, &repair_event_cnt, bad ); for( ulong i=0UL; iseq++; pool_events_push ( self->pool_events, pool_events [i] ); } for( ulong i=0UL; iseq++; repair_events_push( self->repair_events, repair_events[i] ); } - return ok ? AG_POOL_SUCCESS : AG_POOL_ERR_VOTE_VERIFY; + return AG_POOL_SUCCESS; } ag_slot_state_t const * @@ -528,7 +530,7 @@ ag_pool_add_block( ag_pool_t * self, ulong parent_slot = parent_id->slot; uchar const * parent_hash = parent_id->hash; - ulong slot_far_in_future = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ) + self->slot_max; + ulong slot_far_in_future = ag_finality_tracker_first_unpruned_slot( self->finality_tracker ) + self->slot_max - AG_REWARD_SLOT_DELTA; if( FD_UNLIKELY( slotfinality_tracker ) || slot>=slot_far_in_future ) ) return AG_POOL_ERR_SLOT_OUT_OF_BOUNDS; ag_finalization_event_t finalization_event = finalization_event_default( self ); diff --git a/src/choreo/votor/ag_pool.h b/src/choreo/votor/ag_pool.h index a98741e60db..9cfe24e77c0 100644 --- a/src/choreo/votor/ag_pool.h +++ b/src/choreo/votor/ag_pool.h @@ -13,15 +13,11 @@ #define AG_POOL_ERR_DUPLICATE (-2) #define AG_POOL_ERR_SLASHABLE (-3) #define AG_POOL_ERR_CERT_VERIFY (-4) -#define AG_POOL_ERR_VOTE_VERIFY (-5) typedef struct ag_pool ag_pool_t; FD_PROTOTYPES_BEGIN -FD_FN_CONST char const * -ag_pool_strerror( int err ); - FD_FN_CONST ulong ag_pool_align( void ); @@ -49,6 +45,9 @@ ag_pool_init( ag_pool_t * self, void ag_pool_fini( ag_pool_t * self ); +FD_FN_CONST char const * +ag_pool_strerror( int err ); + void ag_pool_advance_epoch( ag_pool_t * self, ag_epoch_info_t const * epoch_info, diff --git a/src/choreo/votor/ag_slot_state.c b/src/choreo/votor/ag_slot_state.c index 906e0fa6d73..7de3ece59e4 100644 --- a/src/choreo/votor/ag_slot_state.c +++ b/src/choreo/votor/ag_slot_state.c @@ -155,7 +155,10 @@ subtract_votes( ag_slot_state_t * self, if( FD_UNLIKELY( kind==AG_VOTE_KIND_NOTAR ) ) { voted_stake->top_notar = 0UL; for( ulong i=0UL; inotar_cnt; i++ ) { - voted_stake->top_notar = fd_ulong_max( voted_stake->top_notar, voted_stake->notar[i].stake ); /* FIXME slow */ + if( FD_LIKELY( voted_stake->notar[i].stake>voted_stake->top_notar ) ) { /* FIXME slow */ + voted_stake->top_notar = voted_stake->notar[i].stake; + memcpy( voted_stake->top_notar_hash, voted_stake->notar[i].hash, sizeof(ag_block_hash_t) ); + } } } return !emptied && !blst_p1_is_inf( &agg->pub ); @@ -308,7 +311,10 @@ count_notar_stake( ag_slot_state_t * self, ulong notar_stake = voted_stake_for_hash->stake; voted_stake->notar_or_skip += stake; - voted_stake->top_notar = fd_ulong_max( notar_stake, voted_stake->top_notar ); + if( FD_LIKELY( notar_stake>voted_stake->top_notar ) ) { + voted_stake->top_notar = notar_stake; + memcpy( voted_stake->top_notar_hash, block_hash, sizeof(ag_block_hash_t) ); + } if( FD_UNLIKELY( !block_hash_set_contains( &self->sent_safe_to_notar, block_hash ) ) ) { switch( check_safe_to_notar( self, block_hash, bad ) ) { @@ -323,7 +329,9 @@ count_notar_stake( ag_slot_state_t * self, default: break; } } - if( FD_UNLIKELY( check_safe_to_skip( self, bad ) ) ) out_pool_events[ (*out_pool_event_cnt)++ ] = (ag_event_pool_t){ .kind = AG_EVENT_POOL_SAFE_TO_SKIP, .safe_to_skip = slot }; + if( FD_UNLIKELY( check_safe_to_skip( self, bad ) ) ) { + out_pool_events[ (*out_pool_event_cnt)++ ] = (ag_event_pool_t){ .kind = AG_EVENT_POOL_SAFE_TO_SKIP, .safe_to_skip = slot }; + } if( FD_UNLIKELY( !fd_bls_set_is_null( bad ) ) ) { voted_stake_for_hash = NULL; for( ulong i=0UL; inotar_cnt; i++ ) { @@ -490,7 +498,9 @@ count_skip_stake( ag_slot_state_t * self, } } - if( FD_UNLIKELY( check_safe_to_skip( self, bad ) ) ) out_pool_events[ (*out_pool_event_cnt)++ ] = (ag_event_pool_t){ .kind = AG_EVENT_POOL_SAFE_TO_SKIP, .safe_to_skip = slot }; + if( FD_UNLIKELY( check_safe_to_skip( self, bad ) ) ) { + out_pool_events[ (*out_pool_event_cnt)++ ] = (ag_event_pool_t){ .kind = AG_EVENT_POOL_SAFE_TO_SKIP, .safe_to_skip = slot }; + } if( FD_UNLIKELY( !fd_bls_set_is_null( bad ) && !fd_bls_set_test( fallback ? voted_stake->skip_fallback_agg.set : voted_stake->skip_agg.set, rank ) ) ) return 0; ulong total_skip_stake = voted_stake->skip + voted_stake->skip_fallback; diff --git a/src/choreo/votor/ag_slot_state.h b/src/choreo/votor/ag_slot_state.h index c7f564327f5..5567e365a76 100644 --- a/src/choreo/votor/ag_slot_state.h +++ b/src/choreo/votor/ag_slot_state.h @@ -68,6 +68,7 @@ struct ag_slot_voted_stake { fd_bls_agg_t finalize_agg; ulong notar_or_skip; ulong top_notar; + ag_block_hash_t top_notar_hash; }; typedef struct ag_slot_voted_stake ag_slot_voted_stake_t; diff --git a/src/choreo/votor/ag_votor.c b/src/choreo/votor/ag_votor.c index 640d4628e5e..55b059c2df5 100644 --- a/src/choreo/votor/ag_votor.c +++ b/src/choreo/votor/ag_votor.c @@ -270,11 +270,13 @@ void ag_votor_init( ag_votor_t * self, ulong slot, long now, + ushort shred_version, fd_bls_sign_fn sign_fn, void * sign_ctx ) { FD_TEST( sign_fn ); self->now = now; self->root = slot; + self->shred_version = shred_version; self->bls_sign_fn = sign_fn; self->bls_sign_ctx = sign_ctx; self->highest_final_cert_slot = slot; @@ -296,30 +298,6 @@ ag_votor_fini( ag_votor_t * self ) { self->highest_final_cert_slot = ULONG_MAX; } -void -ag_votor_advance_epoch( ag_votor_t * self, - ulong epoch_rank, - ulong epoch_slot ) { - if( FD_UNLIKELY( self->curr_epoch_slot==ULONG_MAX ) ) { - self->curr_epoch_rank = epoch_rank; - self->curr_epoch_slot = epoch_slot; - } else if( FD_UNLIKELY( self->next_epoch_slot==ULONG_MAX ) ) { - self->next_epoch_rank = epoch_rank; - self->next_epoch_slot = epoch_slot; - } else { - self->curr_epoch_rank = self->next_epoch_rank; - self->curr_epoch_slot = self->next_epoch_slot; - self->next_epoch_rank = epoch_rank; - self->next_epoch_slot = epoch_slot; - } -} - -void -ag_votor_set_shred_version( ag_votor_t * self, - ushort shred_version ) { - self->shred_version = shred_version; -} - static ushort own_rank( ag_votor_t const * self, ulong slot ) { @@ -349,7 +327,7 @@ received_shred( ag_votor_t const * self, FD_FN_PURE static ulong first_unpruned_slot( ag_votor_t const * self ) { - return ag_first_slot_in_window( self->highest_final_cert_slot ); + return ag_first_slot_in_window( fd_ulong_sat_sub( self->highest_final_cert_slot, AG_REWARD_SLOT_DELTA ) ); } FD_FN_PURE static ulong @@ -530,6 +508,24 @@ handle_cert_created( ag_votor_t * self, cert_events_push( self->cert_events, (ag_event_cert_t){ .seq = self->seq++, .ts = self->now, .cert = *cert } ); } +void +ag_votor_advance_epoch( ag_votor_t * self, + ulong epoch_rank, + ulong epoch_slot ) { + if( FD_UNLIKELY( self->curr_epoch_slot==ULONG_MAX ) ) { + self->curr_epoch_rank = epoch_rank; + self->curr_epoch_slot = epoch_slot; + } else if( FD_UNLIKELY( self->next_epoch_slot==ULONG_MAX ) ) { + self->next_epoch_rank = epoch_rank; + self->next_epoch_slot = epoch_slot; + } else { + self->curr_epoch_rank = self->next_epoch_rank; + self->curr_epoch_slot = self->next_epoch_slot; + self->next_epoch_rank = epoch_rank; + self->next_epoch_slot = epoch_slot; + } +} + void ag_votor_handle_pool_event( ag_votor_t * self, ag_event_pool_t const * event, @@ -625,7 +621,7 @@ void ag_votor_handle_replay_event( ag_votor_t * self, ag_event_replay_t const * event ) { ulong slot = event->slot; - if( FD_UNLIKELY( slot<=self->highest_final_cert_slot || is_retired( self, slot ) ) ) return; + if( FD_UNLIKELY( slotkind ) { case AG_EVENT_REPLAY_COMPLETED: diff --git a/src/choreo/votor/ag_votor.h b/src/choreo/votor/ag_votor.h index 2ea50592357..7d90310ed84 100644 --- a/src/choreo/votor/ag_votor.h +++ b/src/choreo/votor/ag_votor.h @@ -20,33 +20,30 @@ ag_votor_new( void * mem, ulong slot_max, ulong seed ); -void -ag_votor_advance_epoch( ag_votor_t * self, - ulong epoch_rank, - ulong epoch_slot ); +ag_votor_t * +ag_votor_join( void * mem ); -void -ag_votor_set_shred_version( ag_votor_t * self, - ushort shred_version ); +void * +ag_votor_leave( ag_votor_t const * votor ); + +void * +ag_votor_delete( void * mem ); void ag_votor_init( ag_votor_t * self, ulong slot, long now, + ushort shred_version, fd_bls_sign_fn sign_fn, void * sign_ctx ); void ag_votor_fini( ag_votor_t * self ); -ag_votor_t * -ag_votor_join( void * mem ); - -void * -ag_votor_leave( ag_votor_t const * votor ); - -void * -ag_votor_delete( void * mem ); +void +ag_votor_advance_epoch( ag_votor_t * self, + ulong epoch_rank, + ulong epoch_slot ); void ag_votor_handle_pool_event( ag_votor_t * self, diff --git a/src/choreo/votor/ag_votor_base.h b/src/choreo/votor/ag_votor_base.h index 64988972146..7d613b5e7e4 100644 --- a/src/choreo/votor/ag_votor_base.h +++ b/src/choreo/votor/ag_votor_base.h @@ -4,6 +4,7 @@ #include "../../util/fd_util.h" #define AG_SLOTS_PER_WINDOW (4UL) +#define AG_REWARD_SLOT_DELTA (8UL) /* Agave NUM_SLOTS_FOR_REWARD */ #define AG_VAT_MAX (2000UL) /* Validator Admission Ticket caps at 2000 */ #define AG_EQVOC_BLOCK_HASH_MAX (7UL) /* Corollary 50 */ #define AG_NOTAR_FALLBACK_VOTE_MAX (3UL) /* Definition 12 */ diff --git a/src/choreo/votor/test_ag_pool.c b/src/choreo/votor/test_ag_pool.c index c5b168e5fdc..2eda5351a0d 100644 --- a/src/choreo/votor/test_ag_pool.c +++ b/src/choreo/votor/test_ag_pool.c @@ -1,6 +1,7 @@ #include "ag_pool.c" #include "test_ag_cert_builder.h" #include "ag_cert_serde.h" +#include "ag_vote_serde.h" static int has_notar_cert( ag_pool_t const * pool, @@ -62,7 +63,7 @@ min_live_slot( ag_pool_t const * pool ) { } #define FD_TEST_PRUNED_TO_WATERMARK( pool ) \ - FD_TEST( min_live_slot( pool )>=pool_first_unpruned_slot( pool ) ) + FD_TEST( min_live_slot( pool )+AG_REWARD_SLOT_DELTA>=pool_first_unpruned_slot( pool ) ) static int is_parent_ready( ag_pool_t * pool, @@ -362,6 +363,194 @@ test_finalize_block( void ) { teardown_pool( pool ); } +static void +test_reward_readback_window( void ) { + ag_pool_t * pool = setup_pool(); + + ulong slot = 1UL; + ag_block_hash_t hash; random_hash( hash ); + add_notar_votes( pool, slot, hash, 0UL, 7UL ); + add_final_votes( pool, slot, 0UL, 7UL ); + FD_TEST( has_final_cert( pool, slot ) ); + add_notar_votes( pool, slot, hash, 7UL, 10UL ); + add_skip_votes( pool, slot, 10UL, 11UL ); + + ag_slot_state_t const * state = ag_pool_slot_state( pool, slot ); + FD_TEST( state ); + ag_slot_voted_stake_t const * voted_stake = &state->voted_stakes; + FD_TEST( voted_stake->notar_cnt==1UL ); + FD_TEST( fd_bls_set_cnt( voted_stake->notar[0].agg.set )==10UL ); + FD_TEST( fd_bls_set_cnt( voted_stake->skip_agg.set )==1UL ); + + uchar msg[ AG_VOTE_SIGNING_SER_MAX ]; + ulong msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, slot, hash, TEST_SHRED_VERSION, msg ); + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &voted_stake->notar[0].agg.pub, &voted_stake->notar[0].agg.sig ) ); + msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, slot, NULL, TEST_SHRED_VERSION, msg ); + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &voted_stake->skip_agg.pub, &voted_stake->skip_agg.sig ) ); + + for( ulong s=slot+1UL; s<=slot+AG_REWARD_SLOT_DELTA; s++ ) { + ag_block_hash_t hash2; random_hash( hash2 ); + add_notar_votes( pool, s, hash2, 0UL, 7UL ); + add_final_votes( pool, s, 0UL, 7UL ); + FD_TEST( ag_pool_slot_state( pool, slot ) ); /* retained for the reward certs */ + } + ag_block_hash_t hash2; random_hash( hash2 ); + add_notar_votes( pool, slot+AG_REWARD_SLOT_DELTA+1UL, hash2, 0UL, 7UL ); + add_final_votes( pool, slot+AG_REWARD_SLOT_DELTA+1UL, 0UL, 7UL ); + FD_TEST( !ag_pool_slot_state( pool, slot ) ); + + teardown_pool( pool ); +} + +static void +test_reward_late_skip_unverified( void ) { + ag_pool_t * pool = setup_pool(); + + ulong slot = 1UL; + add_skip_votes( pool, slot, 0UL, 7UL ); + FD_TEST( has_skip_cert( pool, slot ) ); + ag_vote_t poisoned = ag_vote_construct_skip( sec_sign_fn, &g_sk[4], slot, (ushort)8, TEST_SHRED_VERSION ); + FD_TEST( ag_pool_add_vote( pool, &poisoned, bad )==AG_POOL_SUCCESS ); + FD_TEST( fd_bls_set_is_null( bad ) ); + drain_events( pool ); + + ag_slot_state_t const * state = ag_pool_slot_state( pool, slot ); + FD_TEST( state ); + FD_TEST( fd_bls_set_cnt( state->voted_stakes.skip_agg.set )==8UL ); + FD_TEST( fd_bls_set_cnt( state->certs.skip.agg_skip.set )==7UL ); + + uchar msg[ AG_VOTE_SIGNING_SER_MAX ]; + ulong msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, slot, NULL, TEST_SHRED_VERSION, msg ); + FD_TEST( !fd_bls_agg_verify( msg, msg_sz, &state->voted_stakes.skip_agg.pub, &state->voted_stakes.skip_agg.sig ) ); + + fd_bls_agg_t base = state->certs.skip.agg_skip; + memset( &base.pub, 0, sizeof(fd_bls_pub_t) ); + for( ulong rank = fd_bls_set_const_iter_init( base.set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( base.set, rank ) ) { + blst_p1_add_or_double( &base.pub, &base.pub, g_epoch_info->pubkeys+rank ); + } + fd_bls_agg_t extra = { 0 }; + for( ulong rank = fd_bls_set_const_iter_init( state->voted_stakes.skip_agg.set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( state->voted_stakes.skip_agg.set, rank ) ) { + if( FD_UNLIKELY( fd_bls_set_test( base.set, rank ) ) ) continue; + fd_bls_set_insert( extra.set, rank ); + blst_p1_add_or_double( &extra.pub, &extra.pub, g_epoch_info->pubkeys +rank ); + blst_p2_add_or_double( &extra.sig, &extra.sig, state->voted_stakes.skip_sig+rank ); + } + FD_TEST( fd_bls_set_cnt( extra.set )==1UL && fd_bls_set_test( extra.set, 8UL ) ); + fd_bls_agg_t agg = base; + blst_p1_add_or_double( &agg.pub, &agg.pub, &extra.pub ); + blst_p2_add_or_double( &agg.sig, &agg.sig, &extra.sig ); + fd_bls_set_union( agg.set, agg.set, extra.set ); + FD_TEST( fd_bls_set_cnt( agg.set )==8UL ); + FD_TEST( !fd_bls_agg_verify( msg, msg_sz, &agg.pub, &agg.sig ) ); + + fd_bls_agg_verify_bisect( &extra, msg, msg_sz, g_epoch_info->pubkeys, state->voted_stakes.skip_sig, bad ); + FD_TEST( fd_bls_set_cnt( bad )==1UL && fd_bls_set_test( bad, 8UL ) ); + for( ulong rank = fd_bls_set_const_iter_init( bad ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( bad, rank ) ) { + fd_bls_pub_t neg_pub = g_epoch_info->pubkeys [ rank ]; blst_p1_cneg( &neg_pub, 1 ); + fd_bls_sig_t neg_sig = state->voted_stakes.skip_sig[ rank ]; blst_p2_cneg( &neg_sig, 1 ); + blst_p1_add_or_double( &agg.pub, &agg.pub, &neg_pub ); + blst_p2_add_or_double( &agg.sig, &agg.sig, &neg_sig ); + fd_bls_set_remove( agg.set, rank ); + } + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &agg.pub, &agg.sig ) ); + FD_TEST( fd_bls_set_cnt( agg.set )==7UL ); + + ag_block_hash_t hash; random_hash( hash ); + add_notar_votes( pool, slot+1UL, hash, 0UL, 7UL ); + FD_TEST( has_notar_cert( pool, slot+1UL ) ); + poisoned = ag_vote_construct_notar( sec_sign_fn, &g_sk[4], slot+1UL, hash, (ushort)8, TEST_SHRED_VERSION ); + FD_TEST( ag_pool_add_vote( pool, &poisoned, bad )==AG_POOL_SUCCESS ); + FD_TEST( fd_bls_set_cnt( bad )==1UL && fd_bls_set_test( bad, 8UL ) ); + drain_events( pool ); + state = ag_pool_slot_state( pool, slot+1UL ); + FD_TEST( state && state->voted_stakes.notar_cnt==1UL && fd_bls_set_cnt( state->voted_stakes.notar[0].agg.set )==7UL ); + msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, slot+1UL, hash, TEST_SHRED_VERSION, msg ); + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &state->voted_stakes.notar[0].agg.pub, &state->voted_stakes.notar[0].agg.sig ) ); + + teardown_pool( pool ); +} + +static void +test_reward_wire_cert_base( void ) { + ag_pool_t * pool = setup_pool(); + + ulong slot = 1UL; + ag_vote_skip_t sv [ 5 ]; + ag_vote_skip_fallback_t sfv[ 4 ]; + for( ulong v=0UL; v<5UL; v++ ) sv [v] = ag_vote_construct_skip ( sec_sign_fn, &g_sk[v], slot, (ushort)v, TEST_SHRED_VERSION ).skip; + for( ulong v=0UL; v<4UL; v++ ) sfv[v] = ag_vote_construct_skip_fallback( sec_sign_fn, &g_sk[v+5UL], slot, (ushort)(v+5UL), TEST_SHRED_VERSION ).skip_fallback; + ag_cert_t cert = cert_build_skip( sv, 5UL, sfv, 4UL, g_epoch_info ); + uchar buf[ AG_CERT_SER_MAX ]; + ulong buf_sz = ag_cert_ser( &cert, buf ); + FD_TEST( ag_cert_de( &cert, buf, buf_sz )==AG_CERT_DE_SUCCESS ); + FD_TEST( ag_pool_add_cert( pool, &cert, bad )==AG_POOL_SUCCESS ); + drain_events( pool ); + add_skip_votes( pool, slot, 0UL, 3UL ); + add_skip_votes( pool, slot, 9UL, 11UL ); + + ag_slot_state_t const * state = ag_pool_slot_state( pool, slot ); + FD_TEST( state ); + FD_TEST( fd_bls_set_cnt( state->certs.skip.agg_skip.set )==5UL && fd_bls_set_cnt( state->certs.skip.agg_skip_fallback.set )==4UL ); + FD_TEST( fd_bls_set_cnt( state->voted_stakes.skip_agg.set )==5UL ); + + uchar msg[ AG_VOTE_SIGNING_SER_MAX ]; + ulong msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, slot, NULL, TEST_SHRED_VERSION, msg ); + fd_bls_agg_t base = state->certs.skip.agg_skip; + memset( &base.pub, 0, sizeof(fd_bls_pub_t) ); + for( ulong rank = fd_bls_set_const_iter_init( base.set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( base.set, rank ) ) { + blst_p1_add_or_double( &base.pub, &base.pub, g_epoch_info->pubkeys+rank ); + } + FD_TEST( !fd_bls_agg_verify( msg, msg_sz, &base.pub, &base.sig ) ); + + memset( &base, 0, sizeof(fd_bls_agg_t) ); + fd_bls_agg_t extra = { 0 }; + for( ulong rank = fd_bls_set_const_iter_init( state->voted_stakes.skip_agg.set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( state->voted_stakes.skip_agg.set, rank ) ) { + if( FD_UNLIKELY( fd_bls_set_test( base.set, rank ) ) ) continue; + fd_bls_set_insert( extra.set, rank ); + blst_p1_add_or_double( &extra.pub, &extra.pub, g_epoch_info->pubkeys +rank ); + blst_p2_add_or_double( &extra.sig, &extra.sig, state->voted_stakes.skip_sig+rank ); + } + fd_bls_agg_t agg = base; + blst_p1_add_or_double( &agg.pub, &agg.pub, &extra.pub ); + blst_p2_add_or_double( &agg.sig, &agg.sig, &extra.sig ); + fd_bls_set_union( agg.set, agg.set, extra.set ); + FD_TEST( fd_bls_set_cnt( agg.set )==5UL ); + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &agg.pub, &agg.sig ) ); + + ag_block_hash_t hash; random_hash( hash ); + ag_vote_notar_t nv[ 7 ]; + for( ulong v=0UL; v<7UL; v++ ) nv[v] = ag_vote_construct_notar( sec_sign_fn, &g_sk[v], slot+1UL, hash, (ushort)v, TEST_SHRED_VERSION ).notar; + cert = cert_build_notar( nv, 7UL, g_epoch_info ); + buf_sz = ag_cert_ser( &cert, buf ); + FD_TEST( ag_cert_de( &cert, buf, buf_sz )==AG_CERT_DE_SUCCESS ); + FD_TEST( ag_pool_add_cert( pool, &cert, bad )==AG_POOL_SUCCESS ); + drain_events( pool ); + state = ag_pool_slot_state( pool, slot+1UL ); + FD_TEST( state && state->voted_stakes.notar_cnt==0UL && state->certs.notar.slot==slot+1UL ); + base = state->certs.notar.agg; + FD_TEST( blst_p1_is_inf( &base.pub ) ); + for( ulong rank = fd_bls_set_const_iter_init( base.set ); + !fd_bls_set_const_iter_done( rank ); + rank = fd_bls_set_const_iter_next( base.set, rank ) ) { + blst_p1_add_or_double( &base.pub, &base.pub, g_epoch_info->pubkeys+rank ); + } + msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, slot+1UL, hash, TEST_SHRED_VERSION, msg ); + FD_TEST( fd_bls_set_cnt( base.set )==7UL ); + FD_TEST( fd_bls_agg_verify( msg, msg_sz, &base.pub, &base.sig ) ); + + teardown_pool( pool ); +} + static void test_finalized_block_hash( void ) { ag_pool_t * pool = setup_pool(); @@ -587,7 +776,7 @@ test_pruning( void ) { ulong last_slot = 3UL*SLOTS_PER_WINDOW - 1UL; FD_TEST( ag_pool_finalized_slot( pool )==last_slot ); - for( ulong s=0UL; s=pool_first_unpruned_slot( pool )) ); FD_TEST( contains_slot( pool, last_slot ) ); FD_TEST_PRUNED_TO_WATERMARK( pool ); @@ -608,7 +797,7 @@ test_pruning( void ) { } FD_TEST( ag_pool_finalized_slot( pool )==last_slot+10UL ); - for( ulong s=0UL; s<10UL; s++ ) FD_TEST( !contains_slot( pool, last_slot+s ) ); + for( ulong s=0UL; s<10UL; s++ ) FD_TEST( contains_slot( pool, last_slot+s )==(last_slot+s+AG_REWARD_SLOT_DELTA>=pool_first_unpruned_slot( pool )) ); FD_TEST( contains_slot( pool, last_slot+10UL ) ); FD_TEST_PRUNED_TO_WATERMARK( pool ); @@ -672,13 +861,18 @@ test_out_of_bounds_votes( void ) { FD_TEST( ag_pool_finalized_slot( pool )==slot ); FD_TEST( pool_first_unpruned_slot( pool )==slot ); - for( ulong s=0UL; s<3UL*SLOTS_PER_WINDOW-1UL; s++ ) { + for( ulong s=0UL; s=watermark_slot ); add_notar_votes( pool, gap_slot, gap_hash, 0UL, 7UL ); FD_TEST( pool_first_unpruned_slot( pool )==next_start ); - FD_TEST( min_live_slot( pool )==next_start ); + FD_TEST( min_live_slot( pool )<=next_start && min_live_slot( pool )+AG_REWARD_SLOT_DELTA>=next_start ); ulong cnt; ag_pool_parents_ready( pool, next_start, &cnt ); FD_TEST( cnt==1UL ); @@ -1211,10 +1405,10 @@ test_retired_epoch_already_pruned( void ) { } FD_TEST( ag_pool_finalized_slot( pool )==EPOCH_B_LO ); - FD_TEST( !contains_slot( pool, EPOCH_B_LO-1UL ) ); - FD_TEST( contains_slot( pool, EPOCH_B_LO ) ); + FD_TEST( contains_slot( pool, EPOCH_B_LO-1UL ) ); /* retained for the reward certs */ + FD_TEST( contains_slot( pool, EPOCH_B_LO ) ); FD_TEST( pool_first_unpruned_slot( pool )==EPOCH_B_LO ); - FD_TEST( min_live_slot( pool )==EPOCH_B_LO ); + FD_TEST( min_live_slot( pool )<=EPOCH_B_LO && min_live_slot( pool )+AG_REWARD_SLOT_DELTA>=EPOCH_B_LO ); ag_epoch_info_t * c = make_epoch_info( 2UL, g_info, NV ); ag_pool_advance_epoch( pool, c, 0UL, EPOCH_B_HI+1UL ); @@ -1223,10 +1417,9 @@ test_retired_epoch_already_pruned( void ) { FD_TEST( epoch_info( pool, EPOCH_B_HI+1UL )==c ); FD_TEST( contains_slot( pool, EPOCH_B_LO ) ); - ag_vote_t v_below; ag_block_hash_t h_below; random_hash( h_below ); - v_below = ag_vote_construct_notar( sec_sign_fn, &g_sk[0], EPOCH_B_LO-1UL, h_below, (ushort)0, TEST_SHRED_VERSION ); - FD_TEST( ag_pool_add_vote( pool, &v_below, bad )==AG_POOL_ERR_SLOT_OUT_OF_BOUNDS ); - FD_TEST( !contains_slot( pool, EPOCH_B_LO-1UL ) ); + ag_vote_t v_late = ag_vote_construct_final( sec_sign_fn, &g_sk[0], EPOCH_B_LO-1UL, (ushort)0, TEST_SHRED_VERSION ); + FD_TEST( ag_pool_add_vote( pool, &v_late, bad )==AG_POOL_SUCCESS ); + FD_TEST( ag_pool_slot_state( pool, EPOCH_B_LO-1UL )->epoch_info==a ); /* retained, verified against its own epoch */ teardown_pool_only( pool ); } @@ -1288,7 +1481,7 @@ test_add_block_below_watermark( void ) { FD_TEST( ag_pool_add_block( pool, &stale, &parent, bad )==AG_POOL_ERR_SLOT_OUT_OF_BOUNDS ); drain_events( pool ); - FD_TEST( !contains_slot( pool, slot-2UL ) ); + FD_TEST( contains_slot( pool, slot-2UL ) ); /* retained, untouched */ FD_TEST( slot_state_pool_free( pool->slot_states->pool )==free_cnt ); FD_TEST_PRUNED_TO_WATERMARK( pool ); @@ -1313,6 +1506,9 @@ main( int argc, test_notarize_block(); test_skip_block(); test_finalize_block(); + test_reward_readback_window(); + test_reward_late_skip_unverified(); + test_reward_wire_cert_base(); test_fast_finalize_block(); test_finalized_block_hash(); test_simple_branch_certified(); diff --git a/src/choreo/votor/test_ag_votor.c b/src/choreo/votor/test_ag_votor.c index dfb171ffe7d..919426eb9a9 100644 --- a/src/choreo/votor/test_ag_votor.c +++ b/src/choreo/votor/test_ag_votor.c @@ -122,9 +122,8 @@ setup_votor( long now ) { FD_TEST( ag_votor_footprint( TEST_SLOT_MAX )<=sizeof(scratch) ); ag_votor_t * votor = ag_votor_join( ag_votor_new( scratch, TEST_SLOT_MAX, 42UL ) ); FD_TEST( votor ); - ag_votor_init ( votor, 0UL, now, sec_sign_fn, &g_sk[0] ); - ag_votor_advance_epoch ( votor, 0UL, 0UL ); - ag_votor_set_shred_version( votor, TEST_SHRED_VERSION ); + ag_votor_init ( votor, 0UL, now, TEST_SHRED_VERSION, sec_sign_fn, &g_sk[0] ); + ag_votor_advance_epoch( votor, 0UL, 0UL ); g_epoch_info = &epoch_info_mem; ag_epoch_info( g_epoch_info, g_info, NV ); @@ -371,15 +370,16 @@ test_prunes_to_finalized_window( void ) { ag_votor_handle_pool_event( votor, &event, 0L ); FD_TEST( votor->highest_final_cert_slot==finalized ); - /* the whole finalized window is kept */ - FD_TEST( min_live_slot( votor )>=window_start ); + /* the finalized window and the reward buffer before it are kept */ + ulong kept_start = ag_first_slot_in_window( fd_ulong_sat_sub( finalized, AG_REWARD_SLOT_DELTA ) ); + FD_TEST( min_live_slot( votor )>=kept_start ); for( ulong slot=window_start; slotbls_private_key ), ctx->_data, sz, bls_sig ); - fd_bls_sig_ser( dst, bls_sig ); + fd_bls_sig_ser( bls_sig, dst ); out_sz = FD_KEYGUARD_BLS_SIG_SZ; break; } diff --git a/src/disco/metrics/generated/fd_metrics_enums.h b/src/disco/metrics/generated/fd_metrics_enums.h index e81cce9c748..f72c01f1295 100644 --- a/src/disco/metrics/generated/fd_metrics_enums.h +++ b/src/disco/metrics/generated/fd_metrics_enums.h @@ -1110,7 +1110,7 @@ #define FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_UNKNOWN_TAG_NAME "unknown_tag" #define FD_METRICS_ENUM_VOTE_RX_RESULT_NAME "vote_rx_result" -#define FD_METRICS_ENUM_VOTE_RX_RESULT_CNT (12UL) +#define FD_METRICS_ENUM_VOTE_RX_RESULT_CNT (11UL) #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_SUCCESS_IDX 0 #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_SUCCESS_NAME "success" #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_BAD_SIZE_IDX 1 @@ -1133,8 +1133,6 @@ #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLASHABLE_NAME "slashable" #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_BANNED_IDX 10 #define FD_METRICS_ENUM_VOTE_RX_RESULT_V_BANNED_NAME "banned" -#define FD_METRICS_ENUM_VOTE_RX_RESULT_V_FAILED_VERIFY_IDX 11 -#define FD_METRICS_ENUM_VOTE_RX_RESULT_V_FAILED_VERIFY_NAME "failed_verify" #define FD_METRICS_ENUM_CERT_RX_RESULT_NAME "cert_rx_result" #define FD_METRICS_ENUM_CERT_RX_RESULT_CNT (11UL) diff --git a/src/disco/metrics/generated/fd_metrics_votor.c b/src/disco/metrics/generated/fd_metrics_votor.c index 9aef1358327..c92e07b910c 100644 --- a/src/disco/metrics/generated/fd_metrics_votor.c +++ b/src/disco/metrics/generated/fd_metrics_votor.c @@ -18,7 +18,6 @@ const fd_metrics_meta_t FD_METRICS_VOTOR[FD_METRICS_VOTOR_TOTAL] = { DECLARE_METRIC_ENUM( VOTOR_VOTE_RX, COUNTER, VOTE_RX_RESULT, DUPLICATE ), DECLARE_METRIC_ENUM( VOTOR_VOTE_RX, COUNTER, VOTE_RX_RESULT, SLASHABLE ), DECLARE_METRIC_ENUM( VOTOR_VOTE_RX, COUNTER, VOTE_RX_RESULT, BANNED ), - DECLARE_METRIC_ENUM( VOTOR_VOTE_RX, COUNTER, VOTE_RX_RESULT, FAILED_VERIFY ), DECLARE_METRIC_ENUM( VOTOR_CERT_RX, COUNTER, CERT_RX_RESULT, SUCCESS ), DECLARE_METRIC_ENUM( VOTOR_CERT_RX, COUNTER, CERT_RX_RESULT, BAD_SIZE ), DECLARE_METRIC_ENUM( VOTOR_CERT_RX, COUNTER, CERT_RX_RESULT, BAD_ENCODING ), diff --git a/src/disco/metrics/generated/fd_metrics_votor.h b/src/disco/metrics/generated/fd_metrics_votor.h index bb9b6ef8ec8..1ca49f5e5dd 100644 --- a/src/disco/metrics/generated/fd_metrics_votor.h +++ b/src/disco/metrics/generated/fd_metrics_votor.h @@ -25,7 +25,6 @@ enum { FD_METRICS_COUNTER_VOTOR_VOTE_RX_DUPLICATE_OFF, FD_METRICS_COUNTER_VOTOR_VOTE_RX_SLASHABLE_OFF, FD_METRICS_COUNTER_VOTOR_VOTE_RX_BANNED_OFF, - FD_METRICS_COUNTER_VOTOR_VOTE_RX_FAILED_VERIFY_OFF, FD_METRICS_COUNTER_VOTOR_CERT_RX_OFF, FD_METRICS_COUNTER_VOTOR_CERT_RX_SUCCESS_OFF = FD_METRICS_COUNTER_VOTOR_CERT_RX_OFF, FD_METRICS_COUNTER_VOTOR_CERT_RX_BAD_SIZE_OFF, @@ -50,7 +49,7 @@ enum { #define FD_METRICS_COUNTER_VOTOR_VOTE_RX_TYPE (FD_METRICS_TYPE_COUNTER) #define FD_METRICS_COUNTER_VOTOR_VOTE_RX_DESC "Result of processing an inbound vote (per vote)" #define FD_METRICS_COUNTER_VOTOR_VOTE_RX_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_VOTOR_VOTE_RX_CNT (12UL) +#define FD_METRICS_COUNTER_VOTOR_VOTE_RX_CNT (11UL) #define FD_METRICS_COUNTER_VOTOR_CERT_RX_NAME "votor_cert_rx" #define FD_METRICS_COUNTER_VOTOR_CERT_RX_TYPE (FD_METRICS_TYPE_COUNTER) @@ -58,7 +57,7 @@ enum { #define FD_METRICS_COUNTER_VOTOR_CERT_RX_CVT (FD_METRICS_CONVERTER_NONE) #define FD_METRICS_COUNTER_VOTOR_CERT_RX_CNT (11UL) -#define FD_METRICS_VOTOR_TOTAL (28UL) +#define FD_METRICS_VOTOR_TOTAL (27UL) extern const fd_metrics_meta_t FD_METRICS_VOTOR[FD_METRICS_VOTOR_TOTAL]; #endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_votor_h */ diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 9f46b7042dc..2006c06b378 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -2057,7 +2057,6 @@ EXAMPLES - diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index 810ce665994..f1edf6e1211 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -302,7 +302,7 @@ replay_reward_cert_voted( fd_replay_tile_t * ctx, ushort rank = replay_voter_rank( ctx, bank, reward_epoch ); *rank_out = rank; - fd_block_footer_t const * footer = fd_sched_get_footer( ctx->sched, bank->idx ); + fd_block_footer_t const * footer = bank==ctx->leader_bank ? ctx->leader_footer : fd_sched_get_footer( ctx->sched, bank->idx ); if( FD_LIKELY( !footer || ( !footer->has_skip_reward_cert && !footer->has_notar_reward_cert ) ) ) return 0; if( FD_UNLIKELY( rank==USHORT_MAX ) ) return 0; @@ -1265,10 +1265,9 @@ construct_footer_certs( fd_replay_tile_t const * ctx, leader_slot>=migration_slot+FD_NUM_SLOTS_FOR_REWARD+1UL; if( FD_LIKELY( reward_ok ) ) { ulong reward_slot = leader_slot-FD_NUM_SLOTS_FOR_REWARD; - fd_votor_certed_t const * rn = &ctx->votor_notar[ reward_slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; - fd_votor_certed_t const * rs = &ctx->votor_skip [ reward_slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; - footer->has_notar_reward_cert = rn->slot==reward_slot && fd_block_footer_cert_from_agg( &footer->notar_reward_cert, reward_slot, rn->block_id.uc, &rn->agg ); - footer->has_skip_reward_cert = rs->slot==reward_slot && fd_block_footer_cert_from_agg( &footer->skip_reward_cert, reward_slot, NULL, &rs->agg ); + fd_votor_reward_t const * reward = &ctx->votor_reward[ reward_slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; + footer->has_notar_reward_cert = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_notar.set ) && fd_block_footer_cert_from_agg( &footer->notar_reward_cert, reward_slot, reward->block_id.uc, &reward->agg_notar ); + footer->has_skip_reward_cert = reward->slot==reward_slot && !fd_bls_set_is_null( reward->agg_skip.set ) && fd_block_footer_cert_from_agg( &footer->skip_reward_cert, reward_slot, NULL, &reward->agg_skip ); } } @@ -1947,7 +1946,7 @@ process_poh_message( fd_replay_tile_t * ctx, ctx->leader_priority_fees = ctx->leader_bank->f.priority_fees; ctx->leader_tips = ctx->leader_bank->f.tips; - fd_block_footer_t footer[1]; + fd_block_footer_t * footer = ctx->leader_footer; fd_memset( footer, 0, sizeof(fd_block_footer_t) ); footer->block_producer_time_nanos = enforce_nanosecond_clock_bounds( ctx, ctx->leader_bank, (ulong)fd_clock_tile_now( ctx->clock ) ); @@ -4336,7 +4335,6 @@ returnable_frag( fd_replay_tile_t * ctx, } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_CERTED ) ) { fd_votor_certed_t const * certed = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); fd_votor_certed_t * fin = ctx->votor_final; - fd_votor_certed_t * ring = NULL; switch( certed->kind ) { case AG_CERT_KIND_FINAL: if( fin->slot==ULONG_MAX || certed->slot>fin->slot ) *fin = *certed; @@ -4344,12 +4342,12 @@ returnable_frag( fd_replay_tile_t * ctx, case AG_CERT_KIND_FAST_FINAL: /* fast beats slow at the same slot */ if( fin->slot==ULONG_MAX || certed->slot>fin->slot || ( certed->slot==fin->slot && fin->kind==AG_CERT_KIND_FINAL ) ) *fin = *certed; break; - case AG_CERT_KIND_NOTAR: ring = &ctx->votor_notar[ certed->slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; break; - case AG_CERT_KIND_SKIP: ring = &ctx->votor_skip [ certed->slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; break; default: break; } - /* newest slot wins the ring entry, and at the same slot the widest aggregate, which rewards the most voters */ - if( ring && ( ring->slot==ULONG_MAX || certed->slot>ring->slot || ( certed->slot==ring->slot && fd_bls_set_cnt( certed->agg.set )>fd_bls_set_cnt( ring->agg.set ) ) ) ) *ring = *certed; + } else if( FD_UNLIKELY( sig==FD_VOTOR_SIG_REWARD ) ) { + fd_votor_reward_t const * reward = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); + fd_votor_reward_t * ring = &ctx->votor_reward[ reward->slot%(FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL) ]; + if( ring->slot==ULONG_MAX || reward->slot>=ring->slot ) *ring = *reward; } break; } @@ -4796,8 +4794,7 @@ unprivileged_init( fd_topo_t const * topo, ctx->highwater_leader_slot = ULONG_MAX; ctx->votor_final->slot = ULONG_MAX; - for( ulong i=0UL; ivotor_notar[ i ].slot = ULONG_MAX; - for( ulong i=0UL; ivotor_skip [ i ].slot = ULONG_MAX; + for( ulong i=0UL; ivotor_reward[ i ].slot = ULONG_MAX; ctx->caught_up = 0; ctx->catch_up_max_fec_slot = ULONG_MAX; diff --git a/src/discof/replay/fd_replay_tile_private.h b/src/discof/replay/fd_replay_tile_private.h index b93864e95aa..62aaafe21fe 100644 --- a/src/discof/replay/fd_replay_tile_private.h +++ b/src/discof/replay/fd_replay_tile_private.h @@ -441,10 +441,10 @@ struct fd_replay_tile { ulong leader_execution_fees; /* ALPENGLOW-ONLY */ ulong leader_priority_fees; /* ALPENGLOW-ONLY */ ulong leader_tips; /* ALPENGLOW-ONLY */ + fd_block_footer_t leader_footer[ 1 ]; fd_votor_certed_t votor_final[ 1 ]; /* ALPENGLOW-ONLY: highest finalization, fast over slow at the same slot */ - fd_votor_certed_t votor_notar[ FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL ]; /* ALPENGLOW-ONLY: by slot, the notar reward */ - fd_votor_certed_t votor_skip [ FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL ]; /* ALPENGLOW-ONLY: by slot, the skip reward */ + fd_votor_reward_t votor_reward[ FD_NUM_SLOTS_FOR_REWARD+AG_SLOTS_PER_WINDOW+1UL ]; ulong next_leader_slot; long next_leader_tickcount; diff --git a/src/discof/votor/fd_votor_tile.c b/src/discof/votor/fd_votor_tile.c index 08dd3809a5f..16e727711c0 100644 --- a/src/discof/votor/fd_votor_tile.c +++ b/src/discof/votor/fd_votor_tile.c @@ -333,6 +333,60 @@ ban_bad_ranks( fd_votor_tile_t * ctx, } } +static void +publish_reward_certs( fd_votor_tile_t * ctx, + ulong slot ) { + publish_t pub = { .sig = FD_VOTOR_SIG_REWARD }; + fd_votor_reward_t * reward = &pub.msg.reward; + memset( reward, 0, sizeof(fd_votor_reward_t) ); + reward->slot = slot; + + ag_slot_state_t const * state = ag_pool_slot_state( ctx->pool, slot ); + if( FD_UNLIKELY( !state ) ) { + FD_TEST( !publishes_full( ctx->publishes ) ); + publishes_push( ctx->publishes, pub ); + return; + } + ag_epoch_info_t const * epoch_info = state->epoch_info; + ag_slot_voted_stake_t const * voted_stake = &state->voted_stakes; + + uchar msg[ AG_VOTE_SIGNING_SER_MAX ]; + ulong msg_sz; + int err; + + uchar const * hash = voted_stake->top_notar_hash; + ag_slot_voted_stake_hash_t const * top = NULL; + for( ulong i=0UL; inotar_cnt; i++ ) if( FD_LIKELY( !memcmp( voted_stake->notar[ i ].hash, hash, sizeof(ag_block_hash_t) ) ) ) top = &voted_stake->notar[ i ]; + if( FD_LIKELY( top ) ) { + fd_bls_agg_t agg = top->agg; + msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, slot, hash, ctx->shred_version, msg ); + err = fd_bls_agg_verify_subtract( &agg, msg, msg_sz, epoch_info->pubkeys, voted_stake->notar_sig, ctx->scratch.bad ); + ban_bad_ranks( ctx, ctx->scratch.bad, slot ); + switch( err ) { + case FD_BLS_SUCCESS: memcpy( reward->block_id.uc, hash, sizeof(fd_hash_t) ); reward->agg_notar = agg; break; + case FD_BLS_ERR_EMPTY: break; + case FD_BLS_ERR_INFINITY: FD_LOG_WARNING(( "slot %lu: notar reward cert cancels to infinity", slot )); break; + default: FD_LOG_CRIT(( "unhandled kind %d", err )); + } + } + + if( FD_LIKELY( !fd_bls_set_is_null( voted_stake->skip_agg.set ) ) ) { + fd_bls_agg_t agg = voted_stake->skip_agg; + msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, slot, NULL, ctx->shred_version, msg ); + err = fd_bls_agg_verify_subtract( &agg, msg, msg_sz, epoch_info->pubkeys, voted_stake->skip_sig, ctx->scratch.bad ); + ban_bad_ranks( ctx, ctx->scratch.bad, slot ); + switch( err ) { + case FD_BLS_SUCCESS: reward->agg_skip = agg; break; + case FD_BLS_ERR_EMPTY: break; + case FD_BLS_ERR_INFINITY: FD_LOG_WARNING(( "slot %lu: skip reward cert cancels to infinity", slot )); break; + default: FD_LOG_CRIT(( "unhandled kind %d", err )); + } + } + + FD_TEST( !publishes_full( ctx->publishes ) ); + publishes_push( ctx->publishes, pub ); +} + static void sign_ed25519( void * signer_ctx, uchar sig[ static FD_ED25519_SIG_SZ ], @@ -527,7 +581,6 @@ quic_server_datagram_rx( fd_quic_conn_t * conn, case AG_POOL_ERR_SLOT_OUT_OF_BOUNDS: ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLOT_OUT_OF_BOUNDS_IDX ]++; break; case AG_POOL_ERR_DUPLICATE: ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_DUPLICATE_IDX ]++; break; case AG_POOL_ERR_SLASHABLE: ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLASHABLE_IDX ]++; break; - case AG_POOL_ERR_VOTE_VERIFY: ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_FAILED_VERIFY_IDX ]++; break; default: FD_LOG_CRIT(( "unhandled kind" )); } @@ -785,7 +838,7 @@ handle_replay( fd_votor_tile_t * ctx, if( FD_UNLIKELY( ctx->rooted_block_id.slot==ULONG_MAX ) ) { ctx->rooted_block_id = block_id; ag_pool_init ( ctx->pool, block_id.slot ); - ag_votor_init( ctx->votor, block_id.slot, fd_log_wallclock(), sign_bls, ctx ); + ag_votor_init( ctx->votor, block_id.slot, fd_log_wallclock(), ctx->shred_version, sign_bls, ctx ); ctx->init = !!ctx->curr_epoch_info && !!ctx->shred_version; } else if( FD_UNLIKELY( block_id.slot!=0 ) ) { ag_pool_add_block( ctx->pool, &block_id, &parent_block_id, ctx->scratch.bad ); @@ -894,7 +947,8 @@ after_credit( fd_votor_tile_t * ctx, certed->agg = cert->skip.agg_skip; certed->agg2 = cert->skip.agg_skip_fallback; break; - default: FD_LOG_ERR(( "unexpected certificate kind %u", cert->kind )); + default: + FD_LOG_CRIT(( "unreachable" )); } if( FD_LIKELY( cert->kind!=AG_CERT_KIND_FINAL ) ) { FD_TEST( !publishes_full( ctx->publishes ) ); @@ -982,6 +1036,9 @@ after_credit( fd_votor_tile_t * ctx, ag_block_id_t parent = ag_pool_wait_for_parent_ready( ctx->pool, ctx->next_leader_slot ); if( FD_UNLIKELY( parent.slot==ULONG_MAX ) ) return; /* the pool has not granted parent ready yet */ + ulong reward_slot = fd_ulong_sat_sub( ctx->next_leader_slot, FD_NUM_SLOTS_FOR_REWARD ); + for( ulong i=0UL; inext_leader_slot; pub.msg.leader.parent_slot = parent.slot; @@ -1082,7 +1139,6 @@ after_frag( fd_votor_tile_t * ctx, case IN_KIND_IPECHO: FD_TEST( sig && sig<=USHORT_MAX ); ctx->shred_version = (ushort)sig; - ag_votor_set_shred_version( ctx->votor, ctx->shred_version ); ctx->init = !!ctx->curr_epoch_info && ctx->rooted_block_id.slot!=ULONG_MAX; break; case IN_KIND_NET: { diff --git a/src/discof/votor/fd_votor_tile.h b/src/discof/votor/fd_votor_tile.h index 392adca3402..e7fb3307e8e 100644 --- a/src/discof/votor/fd_votor_tile.h +++ b/src/discof/votor/fd_votor_tile.h @@ -9,6 +9,7 @@ #define FD_VOTOR_SIG_CERTED (1) #define FD_VOTOR_SIG_REPAIR (2) #define FD_VOTOR_SIG_LEADER (3) +#define FD_VOTOR_SIG_REWARD (4) typedef fd_votor_rooted_t fd_votor_repair_t; @@ -33,6 +34,9 @@ struct fd_votor_certed { }; typedef struct fd_votor_certed fd_votor_certed_t; +/* fd_votor_leader notifies that it is time to become leader for the + window beginning from slot. */ + struct fd_votor_leader { ulong slot; ulong parent_slot; @@ -40,11 +44,25 @@ struct fd_votor_leader { }; typedef struct fd_votor_leader fd_votor_leader_t; +/* fd_votor_reward notifies Votor has produced a new reward cert (agg of + all skip / reward votes). Votor always publishes a fd_votor_reward_t + for every leader slot before fd_votor_leader_t, and may publish add'l + reward certs that include more votes after fd_votor_leader_t. */ + +struct fd_votor_reward { + ulong slot; + fd_hash_t block_id; + fd_bls_agg_t agg_notar; + fd_bls_agg_t agg_skip; +}; +typedef struct fd_votor_reward fd_votor_reward_t; + union fd_votor_msg { fd_votor_certed_t certed; fd_votor_rooted_t rooted; fd_votor_repair_t repair; fd_votor_leader_t leader; + fd_votor_reward_t reward; }; typedef union fd_votor_msg fd_votor_msg_t; diff --git a/src/flamenco/alpenglow/fd_block_marker.h b/src/flamenco/alpenglow/fd_block_marker.h index 030b72f4e14..8bad665a72e 100644 --- a/src/flamenco/alpenglow/fd_block_marker.h +++ b/src/flamenco/alpenglow/fd_block_marker.h @@ -16,7 +16,7 @@ footer reach: a leader producing slot s attests the voters of s-FD_NUM_SLOTS_FOR_REWARD. */ -#define FD_NUM_SLOTS_FOR_REWARD (8UL) +#define FD_NUM_SLOTS_FOR_REWARD AG_REWARD_SLOT_DELTA struct fd_block_header { ulong parent_slot;