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;