LCOV - code coverage report
Current view: top level - discof/votor - fd_votor_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1024 0.0 %
Date: 2026-09-17 04:28:31 Functions: 0 28 0.0 %

          Line data    Source code
       1             : #include "fd_votor_tile.h"
       2             : #include "generated/fd_votor_tile_seccomp.h"
       3             : 
       4             : #include "../../choreo/votor/ag_cert_serde.h"
       5             : #include "../../choreo/votor/ag_pool.h"
       6             : #include "../../choreo/votor/ag_slot_state.h"
       7             : #include "../../choreo/votor/ag_vote_serde.h"
       8             : #include "../../choreo/votor/ag_votor.h"
       9             : #include "../../disco/keyguard/fd_keyguard.h"
      10             : #include "../../disco/keyguard/fd_keyguard_client.h"
      11             : #include "../../disco/keyguard/fd_keyload.h"
      12             : #include "../../disco/metrics/fd_metrics.h"
      13             : #include "../../disco/net/fd_net_tile.h"
      14             : #include "../../disco/stem/fd_stem.h"
      15             : #include "../../disco/topo/fd_topo.h"
      16             : #include "../../flamenco/gossip/fd_gossip_message.h"
      17             : #include "../../flamenco/leaders/fd_leaders_base.h"
      18             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      19             : #include "../../flamenco/stakes/fd_stake_weight.h"
      20             : #include "../../util/net/fd_net_headers.h"
      21             : #include "../../waltz/quic/fd_quic.h"
      22             : #include "../../waltz/quic/fd_quic_conn.h"
      23             : #include "../../waltz/quic/tls/fd_quic_tls.h"
      24             : #include "../replay/fd_replay_tile.h"
      25             : 
      26           0 : #define IN_KIND_EPOCH  (0)
      27           0 : #define IN_KIND_GOSSIP (1)
      28           0 : #define IN_KIND_IPECHO (2)
      29           0 : #define IN_KIND_NET    (3)
      30           0 : #define IN_KIND_REPLAY (4)
      31           0 : #define IN_KIND_SIGN   (5)
      32             : 
      33           0 : #define OUT_IDX_VOTOR (0UL)
      34           0 : #define OUT_IDX_NET   (1UL)
      35             : 
      36             : #define QUIC_CONN_MAX (AG_VAT_MAX * 2) /* each validator is alloted 2 concurrent conns */
      37             : 
      38           0 : #define QUIC_CLOSE_CODE_UNKNOWN (2U)
      39           0 : #define QUIC_CLOSE_CODE_EVICTED (3U)
      40           0 : #define QUIC_CLOSE_CODE_BANNED  (4U)
      41             : 
      42             : #define QUIC_BAN_TIMEOUT_NS (10L*1000L*1000L*1000L) /* 10 seconds */
      43             : 
      44             : static fd_quic_limits_t quic_client_limits = {
      45             :   .conn_cnt                    = AG_VAT_MAX,
      46             :   .handshake_cnt               = 1024UL,
      47             :   .conn_id_cnt                 = FD_QUIC_MIN_CONN_ID_CNT,
      48             :   .inflight_frame_cnt          = 16UL * AG_VAT_MAX,
      49             :   .min_inflight_frame_cnt_conn = 8UL,
      50             : };
      51             : 
      52             : static fd_quic_limits_t quic_server_limits = {
      53             :   .conn_cnt                    = QUIC_CONN_MAX,
      54             :   .handshake_cnt               = 1024UL,
      55             :   .conn_id_cnt                 = FD_QUIC_MIN_CONN_ID_CNT,
      56             :   .inflight_frame_cnt          = 64UL * QUIC_CONN_MAX,
      57             :   .min_inflight_frame_cnt_conn = 32UL,
      58             : };
      59             : 
      60             : #define STACK_NAME rooted
      61             : #define STACK_T    ag_block_id_t
      62             : #include "../../util/tmpl/fd_stack.c"
      63             : 
      64             : struct replayed {
      65             :   ag_block_id_t block_id;
      66             :   ag_block_id_t parent_block_id;
      67             : };
      68             : typedef struct replayed replayed_t;
      69             : 
      70             : #define MAP_NAME               replayed
      71           0 : #define MAP_T                  replayed_t
      72           0 : #define MAP_KEY                block_id
      73           0 : #define MAP_KEY_T              ag_block_id_t
      74           0 : #define MAP_KEY_NULL           ((ag_block_id_t){ .slot = ULONG_MAX })
      75           0 : #define MAP_KEY_INVAL(k)       ((k).slot==ULONG_MAX)
      76           0 : #define MAP_KEY_EQUAL(k0,k1)   (!memcmp( &(k0), &(k1), sizeof(ag_block_id_t) ))
      77             : #define MAP_KEY_EQUAL_IS_SLOW  1
      78           0 : #define MAP_KEY_HASH(key,seed) ((uint)fd_hash( (seed), &(key), sizeof(ag_block_id_t) ))
      79             : #define MAP_MEMOIZE            0
      80             : #include "../../util/tmpl/fd_map_dynamic.c"
      81             : 
      82             : struct publish {
      83             :   ulong          sig;
      84             :   fd_votor_msg_t msg;
      85             : };
      86             : typedef struct publish publish_t;
      87             : 
      88             : #define QUEUE_NAME publishes
      89           0 : #define QUEUE_T    publish_t
      90             : #include "../../util/tmpl/fd_queue_dynamic.c"
      91             : 
      92           0 : #define CONTACT_INFOS_LG_SLOT_CNT (16) /* FD_CONTACT_INFO_TABLE_SIZE keys, fill ratio 0.5 */
      93             : FD_STATIC_ASSERT( (1UL<<CONTACT_INFOS_LG_SLOT_CNT)==2UL*FD_CONTACT_INFO_TABLE_SIZE, contact_infos );
      94             : 
      95             : struct contact_info {
      96             :   fd_pubkey_t id_key;
      97             :   uint        ip4;
      98             :   ushort      port;
      99             : };
     100             : typedef struct contact_info contact_info_t;
     101             : 
     102             : #define MAP_NAME              contact_infos
     103           0 : #define MAP_T                 contact_info_t
     104           0 : #define MAP_LG_SLOT_CNT       CONTACT_INFOS_LG_SLOT_CNT
     105           0 : #define MAP_KEY               id_key
     106           0 : #define MAP_KEY_T             fd_pubkey_t
     107           0 : #define MAP_KEY_NULL          ((fd_pubkey_t){ .ul = {0} }) /* no validator identity is the zero pubkey */
     108           0 : #define MAP_KEY_INVAL(k)      (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]))
     109           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( &(k0), &(k1), sizeof(fd_pubkey_t) ))
     110             : #define MAP_KEY_EQUAL_IS_SLOW 1
     111           0 : #define MAP_KEY_HASH(key)     ((uint)fd_hash( 0UL, &(key), sizeof(fd_pubkey_t) ))
     112             : #define MAP_MEMOIZE           0
     113             : #include "../../util/tmpl/fd_map.c"
     114             : 
     115           0 : #define PEERS_LG_SLOT_CNT (13) /* 3*AG_VAT_MAX keys, fill ratio 0.73 */
     116             : FD_STATIC_ASSERT( (1UL<<PEERS_LG_SLOT_CNT)>=4UL*AG_VAT_MAX, peers );
     117             : 
     118             : struct peer {
     119             :   fd_pubkey_t      id_key;
     120             :   ushort           prev_rank;
     121             :   ushort           curr_rank;
     122             :   ushort           next_rank;
     123             :   fd_quic_conn_t * tx_conn;
     124             :   fd_quic_conn_t * rx_conn;
     125             :   long             ban_ts;
     126             : };
     127             : typedef struct peer peer_t;
     128             : 
     129             : #define MAP_NAME              peers
     130           0 : #define MAP_T                 peer_t
     131           0 : #define MAP_LG_SLOT_CNT       PEERS_LG_SLOT_CNT
     132           0 : #define MAP_KEY               id_key
     133           0 : #define MAP_KEY_T             fd_pubkey_t
     134           0 : #define MAP_KEY_NULL          ((fd_pubkey_t){ .ul = {0} }) /* no validator identity is the zero pubkey */
     135           0 : #define MAP_KEY_INVAL(k)      (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]))
     136           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( &(k0), &(k1), sizeof(fd_pubkey_t) ))
     137             : #define MAP_KEY_EQUAL_IS_SLOW 1
     138           0 : #define MAP_KEY_HASH(key)     ((uint)fd_hash( 0UL, &(key), sizeof(fd_pubkey_t) ))
     139             : #define MAP_MEMOIZE           0
     140             : #include "../../util/tmpl/fd_map.c"
     141             : 
     142           0 : #define RANK_VOTERS_LG_SLOT_CNT (12) /* AG_VAT_MAX keys, fill ratio 0.49 */
     143             : FD_STATIC_ASSERT( (1UL<<RANK_VOTERS_LG_SLOT_CNT)>=2UL*AG_VAT_MAX, rank_voters );
     144             : 
     145             : union bls_key {
     146             :   uchar uc[ FD_BLS_PUB_COMPRESSED_SZ ];
     147             :   ulong ul[ FD_BLS_PUB_COMPRESSED_SZ/sizeof(ulong) ];
     148             : };
     149             : typedef union bls_key bls_key_t;
     150             : 
     151             : struct bls_key_cnt {
     152             :   bls_key_t key;
     153             :   ulong     cnt;
     154             : };
     155             : typedef struct bls_key_cnt bls_key_cnt_t;
     156             : 
     157             : #define MAP_NAME              bls_key_cnts
     158           0 : #define MAP_T                 bls_key_cnt_t
     159           0 : #define MAP_LG_SLOT_CNT       RANK_VOTERS_LG_SLOT_CNT
     160           0 : #define MAP_KEY               key
     161           0 : #define MAP_KEY_T             bls_key_t
     162           0 : #define MAP_KEY_NULL          ((bls_key_t){ .ul = {0} }) /* no compressed BLS key is all zero */
     163           0 : #define MAP_KEY_INVAL(k)      (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]|(k).ul[4]|(k).ul[5]))
     164           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( &(k0), &(k1), sizeof(bls_key_t) ))
     165             : #define MAP_KEY_EQUAL_IS_SLOW 1
     166           0 : #define MAP_KEY_HASH(key)     ((uint)fd_hash( 0UL, &(key), sizeof(bls_key_t) ))
     167             : #define MAP_MEMOIZE           0
     168             : #include "../../util/tmpl/fd_map.c"
     169             : 
     170             : struct id_key_cnt {
     171             :   fd_pubkey_t key;
     172             :   ulong       cnt;
     173             : };
     174             : typedef struct id_key_cnt id_key_cnt_t;
     175             : 
     176             : #define MAP_NAME              id_key_cnts
     177           0 : #define MAP_T                 id_key_cnt_t
     178           0 : #define MAP_LG_SLOT_CNT       RANK_VOTERS_LG_SLOT_CNT
     179           0 : #define MAP_KEY               key
     180           0 : #define MAP_KEY_T             fd_pubkey_t
     181           0 : #define MAP_KEY_NULL          ((fd_pubkey_t){ .ul = {0} }) /* no validator identity is the zero pubkey */
     182           0 : #define MAP_KEY_INVAL(k)      (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]))
     183           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( &(k0), &(k1), sizeof(fd_pubkey_t) ))
     184             : #define MAP_KEY_EQUAL_IS_SLOW 1
     185           0 : #define MAP_KEY_HASH(key)     ((uint)fd_hash( 0UL, &(key), sizeof(fd_pubkey_t) ))
     186             : #define MAP_MEMOIZE           0
     187             : #include "../../util/tmpl/fd_map.c"
     188             : 
     189           0 : #define CERT_SLOT_MAX (4UL*AG_SLOTS_PER_WINDOW)
     190             : 
     191             : struct final_notar_join {
     192             :   ulong           slot; /* ULONG_MAX when the entry holds no slot */
     193             :   int             has_notar;
     194             :   int             has_final;
     195             :   ag_block_hash_t notar_block_hash;
     196             :   fd_bls_agg_t    notar;
     197             :   fd_bls_agg_t    final;
     198             : };
     199             : typedef struct final_notar_join final_notar_join_t;
     200             : 
     201             : struct fd_votor_tile {
     202             : 
     203             :   /* Metadata */
     204             : 
     205             :   fd_pubkey_t          id_key;
     206             :   fd_keyguard_client_t keyguard_client[1];
     207             :   ushort               shred_version;
     208             : 
     209             :   /* Data */
     210             : 
     211             :   int                        init;
     212             :   ag_block_id_t              rooted_block_id;
     213             :   ag_block_id_t              finalized_block_id;
     214             :   ag_epoch_info_t *          prev_epoch_info;
     215             :   ulong                      prev_epoch_slot;
     216             :   ag_epoch_info_t *          curr_epoch_info;
     217             :   ulong                      curr_epoch_slot;
     218             :   ag_epoch_info_t *          next_epoch_info;
     219             :   ulong                      next_epoch_slot;
     220             :   fd_multi_epoch_leaders_t * mleaders;
     221             :   ulong                      next_leader_slot;
     222             :   final_notar_join_t         final_notar_join[CERT_SLOT_MAX];
     223             :   contact_info_t *           contact_infos;
     224             :   peer_t *                   peers;
     225             :   ag_pool_t *                pool;
     226             :   ag_votor_t *               votor;
     227             :   replayed_t *               replayed;
     228             :   ag_block_id_t *            rooted;
     229             :   publish_t *                publishes;
     230             : 
     231             :   /* Networking */
     232             : 
     233             :   fd_pubkey_t        client_peer_id_keys[ QUIC_CONN_MAX ];
     234             :   fd_pubkey_t        server_peer_id_keys[ QUIC_CONN_MAX ];
     235             :   fd_net_rx_bounds_t net_in_bounds[ 32 ];
     236             :   uchar              net_buf[ FD_NET_MTU ];
     237             :   fd_quic_t *        quic_client;
     238             :   fd_quic_t *        quic_server;
     239             :   fd_aio_t           quic_tx_aio[ 1 ];
     240             :   ushort             quic_client_listen_port;
     241             :   ushort             quic_server_listen_port;
     242             :   uint               src_ip_addr;
     243             :   fd_ip4_udp_hdrs_t  hdr[ 1 ];
     244             :   ushort             net_id;
     245             : 
     246             :   /* Links */
     247             : 
     248             :   int in_kind[ 32 ];
     249             :   struct {
     250             :     fd_wksp_t * mem;
     251             :     ulong       chunk0;
     252             :     ulong       wmark;
     253             :     ulong       mtu;
     254             :   } in[ 32 ];
     255             : 
     256             :   void * net_out_mem;
     257             :   ulong  net_out_chunk0;
     258             :   ulong  net_out_wmark;
     259             :   ulong  net_out_chunk;
     260             : 
     261             :   void * votor_out_mem;
     262             :   ulong  votor_out_chunk0;
     263             :   ulong  votor_out_wmark;
     264             :   ulong  votor_out_chunk;
     265             : 
     266             :   ulong                                        net_tx_cnt;
     267             :   struct { ulong chunk; ulong sz; ulong sig; } net_tx[ FD_VOTOR_NET_BURST ];
     268             : 
     269             :   /* Scratch */
     270             : 
     271             :   struct {
     272             :     union {
     273             :       ag_vote_t           vote;
     274             :       ag_cert_t           cert;
     275             :       ag_event_pool_t     pool_event;
     276             :       ag_event_repair_t   repair_event;
     277             :       ag_event_timeout_t  timeout_event;
     278             :       ag_event_vote_t     vote_event;
     279             :       ag_event_cert_t     cert_event;
     280             :     };
     281             :     ag_epoch_info_t prev_epoch_info;
     282             :     ag_epoch_info_t curr_epoch_info;
     283             :     ag_epoch_info_t next_epoch_info;
     284             :     bls_key_cnt_t   bls_key_cnts[ 1UL<<RANK_VOTERS_LG_SLOT_CNT ];
     285             :     id_key_cnt_t    id_key_cnts [ 1UL<<RANK_VOTERS_LG_SLOT_CNT ];
     286             : 
     287             :     uchar ser[ AG_VOTE_SER_MAX > AG_CERT_SER_MAX ? AG_VOTE_SER_MAX : AG_CERT_SER_MAX ];
     288             : 
     289             :     fd_bls_set_t bad[ fd_bls_set_word_cnt ];
     290             :   } scratch;
     291             : 
     292             :   /* Metrics */
     293             : 
     294             :   struct {
     295             :     ulong datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_CNT ];
     296             :     ulong vote_rx    [ FD_METRICS_ENUM_VOTE_RX_RESULT_CNT     ];
     297             :     ulong cert_rx    [ FD_METRICS_ENUM_CERT_RX_RESULT_CNT     ];
     298             :   } metrics;
     299             : };
     300             : typedef struct fd_votor_tile fd_votor_tile_t;
     301             : 
     302             : /* try_advance_root attempts to advance root to finalized_block_id.  For
     303             :    something to be rooted, it must be BOTH finalized AND replayed.  This
     304             :    walks up the replay block id lineage to find and set root.
     305             : 
     306             :    ON FINALIZED
     307             : 
     308             :    If finalized is ahead of replayed => root does not advance
     309             :    If replayed is ahead of finalized => root advances
     310             : 
     311             :    ON REPLAYED
     312             : 
     313             :    If finalized is ahead of replayed => root advances
     314             :    If replayed is ahead of finalized => root does not advance */
     315             : 
     316             : static void
     317             : try_advance_root( fd_votor_tile_t * ctx,
     318           0 :                   ag_block_id_t     finalized_block_id ) {
     319             : 
     320           0 :   ag_block_id_t ancestor_block_id = finalized_block_id;
     321           0 :   replayed_t *  replayed          = NULL;
     322             : 
     323           0 :   while( FD_LIKELY( ancestor_block_id.slot>ctx->rooted_block_id.slot && ( replayed = replayed_query( ctx->replayed, ancestor_block_id, NULL ) ) ) ) {
     324           0 :     FD_TEST( !rooted_full( ctx->rooted ) );
     325           0 :     rooted_push( ctx->rooted, ancestor_block_id );
     326           0 :     ancestor_block_id = replayed->parent_block_id;
     327           0 :   }
     328             : 
     329           0 :   if( FD_UNLIKELY( ancestor_block_id.slot != ctx->rooted_block_id.slot ) ) FD_TEST( memcmp( ancestor_block_id.hash, ctx->rooted_block_id.hash, sizeof(ag_block_hash_t) ) );
     330             : 
     331             :   /* When a block id is finalized ahead of replay, we need to cache it
     332             :      and process it when replay catches up. */
     333             : 
     334           0 :   if( FD_UNLIKELY( !ag_block_id_eq( &ancestor_block_id, &ctx->rooted_block_id ) ) ) {
     335           0 :     rooted_remove_all( ctx->rooted );
     336           0 :     if( FD_LIKELY( ctx->finalized_block_id.slot==ULONG_MAX || finalized_block_id.slot>ctx->finalized_block_id.slot ) ) ctx->finalized_block_id = finalized_block_id;
     337           0 :     return;
     338           0 :   }
     339             : 
     340           0 :   while( FD_UNLIKELY( !rooted_empty( ctx->rooted ) ) ) {
     341           0 :     ag_block_id_t rooted_block_id = rooted_pop( ctx->rooted );
     342             : 
     343           0 :     publish_t pub = { .sig = FD_VOTOR_SIG_ROOTED };
     344           0 :     pub.msg.rooted.slot = rooted_block_id.slot;
     345           0 :     memcpy( pub.msg.rooted.block_id.uc, rooted_block_id.hash, sizeof(fd_hash_t) );
     346           0 :     FD_TEST( !publishes_full( ctx->publishes ) );
     347           0 :     publishes_push( ctx->publishes, pub );
     348             : 
     349           0 :     replayed = replayed_query( ctx->replayed, rooted_block_id, NULL );
     350           0 :     if( FD_LIKELY( replayed ) ) replayed_remove( ctx->replayed, replayed );
     351             : 
     352           0 :     ctx->rooted_block_id = rooted_block_id;
     353           0 :   }
     354           0 : }
     355             : 
     356             : static void
     357           0 : ban_peer( peer_t * peer ) {
     358           0 :   FD_BASE58_ENCODE_32_BYTES( peer->id_key.uc, id_key_b58 );
     359           0 :   FD_LOG_WARNING(( "banning peer %s", id_key_b58 ));
     360           0 :   if( FD_LIKELY( peer->rx_conn ) ) {
     361           0 :     fd_quic_conn_set_context( peer->rx_conn, NULL );
     362           0 :     fd_quic_conn_close( peer->rx_conn, QUIC_CLOSE_CODE_BANNED );
     363           0 :     peer->rx_conn = NULL;
     364           0 :   }
     365           0 :   if( FD_LIKELY( peer->tx_conn ) ) {
     366           0 :     fd_quic_conn_set_context( peer->tx_conn, NULL );
     367           0 :     fd_quic_conn_close( peer->tx_conn, QUIC_CLOSE_CODE_BANNED );
     368           0 :     peer->tx_conn = NULL;
     369           0 :   }
     370           0 :   peer->ban_ts = fd_log_wallclock();
     371           0 : }
     372             : 
     373             : static void
     374             : ban_bad_ranks( fd_votor_tile_t *    ctx,
     375             :                fd_bls_set_t const * bad,
     376           0 :                ulong                slot_as_of ) {
     377           0 :   ag_epoch_info_t const * epoch_info = fd_ptr_if( slot_as_of>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( slot_as_of>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
     378           0 :   if( FD_UNLIKELY( !epoch_info ) ) return;
     379           0 :   long                    now        = fd_log_wallclock();
     380           0 :   for( ulong rank = fd_bls_set_const_iter_init( bad );
     381           0 :                    !fd_bls_set_const_iter_done( rank );
     382           0 :              rank = fd_bls_set_const_iter_next( bad, rank ) ) {
     383           0 :     fd_pubkey_t id_key; memcpy( id_key.uc, epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
     384           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
     385           0 :     if( FD_UNLIKELY( !peer || now<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) continue;
     386           0 :     ban_peer( peer );
     387           0 :   }
     388           0 : }
     389             : 
     390             : static void
     391             : publish_reward_certs( fd_votor_tile_t * ctx,
     392           0 :                       ulong             slot ) {
     393           0 :   publish_t           pub    = { .sig = FD_VOTOR_SIG_REWARD };
     394           0 :   fd_votor_reward_t * reward = &pub.msg.reward;
     395           0 :   memset( reward, 0, sizeof(fd_votor_reward_t) );
     396           0 :   reward->slot = slot;
     397             : 
     398           0 :   ag_slot_state_t const * state = ag_pool_slot_state( ctx->pool, slot );
     399           0 :   if( FD_UNLIKELY( !state ) ) {
     400           0 :     FD_TEST( !publishes_full( ctx->publishes ) );
     401           0 :     publishes_push( ctx->publishes, pub );
     402           0 :     return;
     403           0 :   }
     404           0 :   ag_epoch_info_t const *       epoch_info  = state->epoch_info;
     405           0 :   ag_slot_voted_stake_t const * voted_stake = &state->votes;
     406             : 
     407           0 :   uchar msg[ AG_VOTE_SIGNING_SER_MAX ];
     408           0 :   ulong msg_sz;
     409           0 :   int   err;
     410             : 
     411           0 :   uchar const *                      hash = voted_stake->top_notar_hash;
     412           0 :   ag_slot_voted_stake_hash_t const * top  = notar_map_query_const( voted_stake->notar, FD_LOAD( ag_block_hash_key_t, hash ), NULL );
     413           0 :   if( FD_LIKELY( top ) ) {
     414           0 :     fd_bls_agg_t agg = top->agg;
     415           0 :     msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, slot, hash, ctx->shred_version, msg );
     416           0 :     err    = fd_bls_agg_verify_subtract( &agg, msg, msg_sz, epoch_info->pubkeys, voted_stake->notar_sig, ctx->scratch.bad );
     417           0 :     ban_bad_ranks( ctx, ctx->scratch.bad, slot );
     418           0 :     switch( err ) {
     419           0 :     case FD_BLS_SUCCESS:      memcpy( reward->block_id.uc, hash, sizeof(fd_hash_t) ); reward->agg_notar = agg; break;
     420           0 :     case FD_BLS_ERR_EMPTY:    break;
     421           0 :     case FD_BLS_ERR_INFINITY: FD_LOG_WARNING(( "slot %lu: notar reward cert cancels to infinity", slot )); break;
     422           0 :     default:                  FD_LOG_CRIT(( "unhandled kind %d", err ));
     423           0 :     }
     424           0 :   }
     425             : 
     426           0 :   if( FD_LIKELY( !fd_bls_set_is_null( voted_stake->skip_agg.set ) ) ) {
     427           0 :     fd_bls_agg_t agg = voted_stake->skip_agg;
     428           0 :     msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, slot, NULL, ctx->shred_version, msg );
     429           0 :     err    = fd_bls_agg_verify_subtract( &agg, msg, msg_sz, epoch_info->pubkeys, voted_stake->skip_sig, ctx->scratch.bad );
     430           0 :     ban_bad_ranks( ctx, ctx->scratch.bad, slot );
     431           0 :     switch( err ) {
     432           0 :     case FD_BLS_SUCCESS:      reward->agg_skip = agg; break;
     433           0 :     case FD_BLS_ERR_EMPTY:    break;
     434           0 :     case FD_BLS_ERR_INFINITY: FD_LOG_WARNING(( "slot %lu: skip reward cert cancels to infinity", slot )); break;
     435           0 :     default:                  FD_LOG_CRIT(( "unhandled kind %d", err ));
     436           0 :     }
     437           0 :   }
     438             : 
     439           0 :   FD_TEST( !publishes_full( ctx->publishes ) );
     440           0 :   publishes_push( ctx->publishes, pub );
     441           0 : }
     442             : 
     443             : static void
     444             : sign_ed25519( void *      signer_ctx,
     445             :               uchar       sig[ static FD_ED25519_SIG_SZ ],
     446           0 :               uchar const msg[ static FD_TLS_CV_SIGN_SZ ] ) {
     447           0 :   fd_votor_tile_t * ctx = signer_ctx;
     448           0 :   fd_keyguard_client_sign( ctx->keyguard_client, sig, msg, FD_TLS_CV_SIGN_SZ, FD_KEYGUARD_SIGN_TYPE_ED25519 );
     449           0 : }
     450             : 
     451             : FD_STATIC_ASSERT( FD_BLS_SIG_SZ==FD_KEYGUARD_BLS_SIG_SZ, bls_sig_sz );
     452             : 
     453             : static void
     454             : sign_bls( void *         signer_ctx,
     455             :           fd_bls_sig_t * sig,
     456             :           uchar const *  payload,
     457           0 :           ulong          payload_sz ) {
     458           0 :   fd_votor_tile_t * ctx = signer_ctx;
     459           0 :   uchar sig_bytes[ FD_BLS_SIG_SZ ];
     460           0 :   fd_keyguard_client_sign( ctx->keyguard_client, sig_bytes, payload, payload_sz, FD_KEYGUARD_SIGN_TYPE_BLS );
     461           0 :   if( FD_UNLIKELY( fd_bls_sig_de( sig, sig_bytes ) ) ) FD_LOG_CRIT(( "sign tile returned an invalid BLS signature" ));
     462           0 : }
     463             : 
     464             : static int
     465             : quic_aio_tx( void *                    _ctx,
     466             :              fd_aio_pkt_info_t const * batch,
     467             :              ulong                     batch_cnt,
     468             :              ulong *                   opt_batch_idx,
     469           0 :              int                       flush ) {
     470           0 :   (void)flush;
     471             : 
     472           0 :   fd_votor_tile_t * ctx = _ctx;
     473             : 
     474           0 :   for( ulong i=0UL; i<batch_cnt; i++ ) {
     475           0 :     if( FD_UNLIKELY( ctx->net_tx_cnt==FD_VOTOR_NET_BURST ) ) {
     476           0 :       if( FD_LIKELY( opt_batch_idx ) ) *opt_batch_idx = i;
     477           0 :       return FD_AIO_ERR_AGAIN;
     478           0 :     }
     479           0 :     if( FD_UNLIKELY( batch[ i ].buf_sz<FD_NETMUX_SIG_MIN_HDR_SZ ) ) continue;
     480             : 
     481           0 :     ulong const sz_l2 = sizeof(fd_eth_hdr_t) + batch[ i ].buf_sz;
     482           0 :     if( FD_UNLIKELY( sz_l2>FD_ETH_PAYLOAD_MAX ) ) continue;
     483             : 
     484           0 :     uint const ip_dst = FD_LOAD( uint, batch[ i ].buf+offsetof( fd_ip4_hdr_t, daddr_c ) );
     485           0 :     uchar * packet_l2 = fd_chunk_to_laddr( ctx->net_out_mem, ctx->net_out_chunk );
     486           0 :     uchar * packet_l3 = packet_l2 + sizeof(fd_eth_hdr_t);
     487           0 :     memset( packet_l2, 0, 12 );
     488           0 :     FD_STORE( ushort, packet_l2+offsetof( fd_eth_hdr_t, net_type ), fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) );
     489           0 :     fd_memcpy( packet_l3, batch[ i ].buf, batch[ i ].buf_sz );
     490             : 
     491           0 :     ctx->net_tx[ ctx->net_tx_cnt ].chunk = ctx->net_out_chunk;
     492           0 :     ctx->net_tx[ ctx->net_tx_cnt ].sz    = sz_l2;
     493           0 :     ctx->net_tx[ ctx->net_tx_cnt ].sig   = fd_disco_netmux_sig( ip_dst, 0U, ip_dst, DST_PROTO_OUTGOING, FD_NETMUX_SIG_MIN_HDR_SZ );
     494           0 :     ctx->net_tx_cnt++;
     495             : 
     496           0 :     ctx->net_out_chunk = fd_dcache_compact_next( ctx->net_out_chunk, FD_NET_MTU, ctx->net_out_chunk0, ctx->net_out_wmark );
     497           0 :   }
     498             : 
     499           0 :   if( FD_LIKELY( opt_batch_idx ) ) *opt_batch_idx = batch_cnt;
     500             : 
     501           0 :   return FD_AIO_SUCCESS;
     502           0 : }
     503             : 
     504             : static void
     505             : quic_client_conn_final( fd_quic_conn_t * conn,
     506           0 :                         void *           quic_ctx ) {
     507           0 :   fd_votor_tile_t *   ctx    = quic_ctx;
     508           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     509           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     510           0 :   peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     511           0 :   if( FD_LIKELY( peer ) ) peer->tx_conn = NULL;
     512           0 : }
     513             : 
     514             : static void
     515             : quic_client_conn_hs_complete( fd_quic_conn_t * conn,
     516           0 :                               void *           quic_ctx ) {
     517           0 :   (void)quic_ctx;
     518           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     519           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     520             : 
     521           0 :   if( FD_LIKELY( !conn->tls_hs || memcmp( conn->tls_hs->hs.cli.server_pubkey, id_key->uc, sizeof(fd_pubkey_t) ) ) ) {
     522           0 :     fd_quic_conn_close( conn, QUIC_CLOSE_CODE_UNKNOWN );
     523           0 :   }
     524           0 : }
     525             : 
     526             : static void
     527             : quic_client_datagram_tx( fd_votor_tile_t *   ctx,
     528             :                          fd_stem_context_t * stem,
     529             :                          fd_quic_conn_t *    conn,
     530             :                          uchar const *       buf,
     531           0 :                          ulong               buf_sz ) {
     532           0 :   uchar * packet_l2 = fd_chunk_to_laddr( ctx->net_out_mem, ctx->net_out_chunk );
     533           0 :   uchar * payload   = packet_l2 + sizeof(fd_ip4_udp_hdrs_t);
     534             : 
     535           0 :   ulong pkt_sz = fd_quic_conn_tx_dgram( conn, payload, FD_NET_MTU-sizeof(fd_ip4_udp_hdrs_t), buf, buf_sz );
     536           0 :   if( FD_UNLIKELY( !pkt_sz ) ) return;
     537             : 
     538           0 :   fd_ip4_udp_hdrs_t * hdr = (fd_ip4_udp_hdrs_t *)fd_type_pun( packet_l2 );
     539           0 :   *hdr = *ctx->hdr;
     540             : 
     541           0 :   hdr->ip4->daddr       = conn->peer[ 0 ].ip_addr;
     542           0 :   hdr->ip4->net_tot_len = fd_ushort_bswap( (ushort)( pkt_sz+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) );
     543           0 :   hdr->ip4->net_id      = fd_ushort_bswap( ctx->net_id++ );
     544           0 :   hdr->ip4->check       = 0;
     545           0 :   hdr->ip4->check       = fd_ip4_hdr_check_fast( hdr->ip4 );
     546             : 
     547           0 :   hdr->udp->net_dport = fd_ushort_bswap( conn->peer[ 0 ].udp_port );
     548           0 :   hdr->udp->net_len   = fd_ushort_bswap( (ushort)( pkt_sz+sizeof(fd_udp_hdr_t) ) );
     549           0 :   hdr->udp->check     = (ushort)0;
     550             : 
     551           0 :   uint  ip_dst = hdr->ip4->daddr;
     552           0 :   ulong sig    = fd_disco_netmux_sig( ip_dst, 0U, ip_dst, DST_PROTO_OUTGOING, FD_NETMUX_SIG_MIN_HDR_SZ );
     553           0 :   ulong sz_l2  = sizeof(fd_ip4_udp_hdrs_t) + pkt_sz;
     554           0 :   fd_stem_publish( stem, OUT_IDX_NET, sig, ctx->net_out_chunk, sz_l2, fd_frag_meta_ctl( 0UL, 1, 1, 0 ), 0L, 0L );
     555           0 :   ctx->net_out_chunk = fd_dcache_compact_next( ctx->net_out_chunk, FD_NET_MTU, ctx->net_out_chunk0, ctx->net_out_wmark );
     556           0 : }
     557             : 
     558             : static void
     559             : quic_server_conn_new( fd_quic_conn_t * conn,
     560           0 :                       void *           _ctx ) {
     561           0 :   if( FD_UNLIKELY( !conn->tls_hs ) ) return; /* no authenticated identity, so no votes will be attributed */
     562           0 :   fd_pubkey_t const * id_key = (fd_pubkey_t const *)fd_type_pun_const( conn->tls_hs->hs.srv.client_pubkey );
     563             : 
     564           0 :   fd_votor_tile_t * ctx  = _ctx;
     565           0 :   peer_t *          peer = peers_query( ctx->peers, *id_key, NULL );
     566           0 :   if( FD_LIKELY( ctx->curr_epoch_info ) && FD_UNLIKELY( !peer ) ) {
     567           0 :     fd_quic_conn_close( conn, 0U );
     568           0 :     return;
     569           0 :   }
     570           0 :   if( FD_UNLIKELY( peer && fd_log_wallclock()<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     571           0 :     fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED );
     572           0 :     return;
     573           0 :   }
     574           0 :   ctx->server_peer_id_keys[ conn->conn_idx ] = *id_key;
     575           0 :   fd_quic_conn_set_context( conn, &ctx->server_peer_id_keys[ conn->conn_idx ] );
     576           0 :   if( FD_LIKELY( peer ) ) {
     577           0 :     if( FD_UNLIKELY( peer->rx_conn ) ) {
     578           0 :       fd_quic_conn_set_context( peer->rx_conn, NULL );
     579           0 :       fd_quic_conn_close( peer->rx_conn, 0U );
     580           0 :     }
     581           0 :     peer->rx_conn = conn;
     582           0 :   }
     583           0 : }
     584             : 
     585             : static void
     586             : quic_server_conn_final( fd_quic_conn_t * conn,
     587           0 :                         void *           _ctx ) {
     588           0 :   fd_votor_tile_t *   ctx    = _ctx;
     589           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     590           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     591           0 :   peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     592           0 :   if( FD_LIKELY( peer && peer->rx_conn==conn ) ) peer->rx_conn = NULL;
     593           0 : }
     594             : 
     595             : static void
     596             : quic_server_datagram_rx( fd_quic_conn_t * conn,
     597             :                          uchar const *    data,
     598             :                          ulong            data_sz,
     599           0 :                          void *           _ctx ) {
     600             : 
     601           0 :   fd_votor_tile_t * ctx = _ctx;
     602           0 :   if( FD_UNLIKELY( !ctx->init  ) ) { ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_NOT_READY_IDX ]++; return; }
     603           0 :   if( FD_UNLIKELY( data_sz<2UL ) ) { ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_TOO_SMALL_IDX ]++; return; }
     604           0 :   uchar kind = data[ 1 ];
     605             : 
     606           0 :   switch( kind ) {
     607           0 :   case AG_VOTE_SERDE_TAG_NOTAR:
     608           0 :   case AG_VOTE_SERDE_TAG_FINAL:
     609           0 :   case AG_VOTE_SERDE_TAG_SKIP:
     610           0 :   case AG_VOTE_SERDE_TAG_NOTAR_FALLBACK:
     611           0 :   case AG_VOTE_SERDE_TAG_SKIP_FALLBACK: {
     612           0 :     ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_VOTE_IDX ]++;
     613           0 :     int err = ag_vote_de( &ctx->scratch.vote, data, data_sz );
     614           0 :     if( FD_UNLIKELY( err ) ) {
     615           0 :       ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_BAD_SIZE_IDX     ] += (ulong)(err==AG_VOTE_DE_ERR_SZ   );
     616           0 :       ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_BAD_ENCODING_IDX ] += (ulong)(err==AG_VOTE_DE_ERR_INVAL);
     617           0 :       return;
     618           0 :     }
     619           0 :     ag_vote_t * vote = &ctx->scratch.vote;
     620           0 :     if( FD_UNLIKELY( ag_vote_shred_version( vote )!=ctx->shred_version ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SHRED_VERSION_IDX ]++; return; }
     621             : 
     622           0 :     fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     623           0 :     if( FD_UNLIKELY( !id_key ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_UNKNOWN_SIGNER_IDX ]++; return; }
     624           0 :     peer_t const * peer = peers_query( ctx->peers, *id_key, NULL );
     625           0 :     if( FD_UNLIKELY( !peer ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_NOT_A_PEER_IDX ]++; return; }
     626           0 :     if( FD_UNLIKELY( fd_log_wallclock()<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     627           0 :       ctx->metrics.vote_rx[FD_METRICS_ENUM_VOTE_RX_RESULT_V_BANNED_IDX]++;
     628           0 :       fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED );
     629           0 :       return;
     630           0 :     }
     631             : 
     632           0 :     ulong  vote_slot = ag_vote_slot( vote  );
     633           0 :     ushort rank      = fd_ushort_if( vote_slot>=ctx->next_epoch_slot, peer->next_rank, fd_ushort_if( vote_slot>=ctx->curr_epoch_slot, peer->curr_rank, peer->prev_rank ) );
     634           0 :     if( FD_UNLIKELY( rank==USHORT_MAX ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_NOT_RANKED_IDX ]++; return; } /* peer is not ranked in their vote slot's epoch */
     635           0 :     switch( vote->kind ) {
     636           0 :     case AG_VOTE_KIND_NOTAR:          vote->notar.rank          = rank; break;
     637           0 :     case AG_VOTE_KIND_FINAL:          vote->final.rank          = rank; break;
     638           0 :     case AG_VOTE_KIND_SKIP:           vote->skip.rank           = rank; break;
     639           0 :     case AG_VOTE_KIND_NOTAR_FALLBACK: vote->notar_fallback.rank = rank; break;
     640           0 :     case AG_VOTE_KIND_SKIP_FALLBACK:  vote->skip_fallback.rank  = rank; break;
     641           0 :     default:                          FD_LOG_CRIT(( "unreachable" ));
     642           0 :     }
     643             : 
     644           0 :     switch( ag_pool_add_vote( ctx->pool, &ctx->scratch.vote, ctx->scratch.bad ) ) {
     645           0 :     case AG_POOL_SUCCESS:                ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SUCCESS_IDX            ]++; break;
     646           0 :     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;
     647           0 :     case AG_POOL_ERR_DUPLICATE:          ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_DUPLICATE_IDX          ]++; break;
     648           0 :     case AG_POOL_ERR_SLASHABLE:          ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLASHABLE_IDX          ]++; break;
     649           0 :     default:
     650           0 :       FD_LOG_CRIT(( "unhandled kind" ));
     651           0 :     }
     652           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, vote_slot );
     653           0 :     return;
     654           0 :   }
     655           0 :   case AG_CERT_SERDE_TAG_FINAL:
     656           0 :   case AG_CERT_SERDE_TAG_FAST_FINAL:
     657           0 :   case AG_CERT_SERDE_TAG_NOTAR:
     658           0 :   case AG_CERT_SERDE_TAG_NOTAR_FALLBACK:
     659           0 :   case AG_CERT_SERDE_TAG_SKIP: {
     660           0 :     ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_CERT_IDX ]++;
     661           0 :     int err = ag_cert_de( &ctx->scratch.cert, data, data_sz );
     662           0 :     if( FD_UNLIKELY( err ) ) {
     663           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BAD_SIZE_IDX     ] += (ulong)(err==AG_CERT_DE_ERR_SZ   );
     664           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BAD_ENCODING_IDX ] += (ulong)(err==AG_CERT_DE_ERR_INVAL);
     665           0 :       return;
     666           0 :     }
     667           0 :     if( FD_UNLIKELY( ag_cert_shred_version( &ctx->scratch.cert )!=ctx->shred_version ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SHRED_VERSION_IDX ]++; return; }
     668             : 
     669           0 :     fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     670           0 :     if( FD_UNLIKELY( !id_key ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_UNKNOWN_SIGNER_IDX ]++; return; }
     671           0 :     peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     672           0 :     if( FD_UNLIKELY( !peer ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_NOT_A_PEER_IDX ]++; return; }
     673           0 :     if( FD_UNLIKELY( fd_log_wallclock()<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BANNED_IDX ]++; fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED ); return; }
     674             : 
     675           0 :     ulong  cert_slot = ag_cert_slot( &ctx->scratch.cert );
     676           0 :     ushort rank      = fd_ushort_if( cert_slot>=ctx->next_epoch_slot, peer->next_rank, fd_ushort_if( cert_slot>=ctx->curr_epoch_slot, peer->curr_rank, peer->prev_rank ) );
     677           0 :     if( FD_UNLIKELY( rank==USHORT_MAX ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_NOT_RANKED_IDX ]++; return; } /* peer is not ranked in this cert slot's epoch */
     678             : 
     679           0 :     switch( ag_pool_add_cert( ctx->pool, &ctx->scratch.cert, ctx->scratch.bad ) ) {
     680           0 :     case AG_POOL_SUCCESS:                ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SUCCESS_IDX            ]++; break;
     681           0 :     case AG_POOL_ERR_SLOT_OUT_OF_BOUNDS: ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SLOT_OUT_OF_BOUNDS_IDX ]++; break;
     682           0 :     case AG_POOL_ERR_DUPLICATE:          ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_DUPLICATE_IDX          ]++; break;
     683           0 :     case AG_POOL_ERR_CERT_VERIFY:
     684           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_FAILED_VERIFY_IDX ]++;
     685           0 :       ban_peer( peer );
     686           0 :       break;
     687           0 :     default:
     688           0 :       FD_LOG_CRIT(( "unhandled kind" ));
     689           0 :     }
     690           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, cert_slot );
     691           0 :     return;
     692           0 :   }
     693           0 :   default:
     694           0 :     ctx->metrics.datagram_rx[FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_UNKNOWN_TAG_IDX]++;
     695           0 :     break;
     696           0 :   }
     697           0 : }
     698             : 
     699             : struct rank_voter { ulong stake; uchar const * bls; ulong src; fd_bls_pub_t pk; };
     700             : typedef struct rank_voter rank_voter_t;
     701             : 
     702             : #define SORT_NAME        rank_voters_sort
     703           0 : #define SORT_KEY_T       rank_voter_t
     704           0 : #define SORT_BEFORE(a,b) ( (a).stake>(b).stake ||                                            \
     705           0 :                           ( (a).stake==(b).stake &&                                         \
     706           0 :                             memcmp( (a).bls, (b).bls, FD_BLS_PUB_COMPRESSED_SZ )<0 ) )
     707             : #include "../../util/tmpl/fd_sort.c"
     708             : 
     709             : FD_STATIC_ASSERT( sizeof(((fd_vote_stake_weight_t *)0)->bls_key)==FD_BLS_PUB_COMPRESSED_SZ, bls_key_sz );
     710             : 
     711             : static ag_epoch_info_t *
     712             : rank_voters( fd_votor_tile_t *              ctx,
     713             :              ag_epoch_info_t *              mem,
     714             :              fd_vote_stake_weight_t const * stakes,
     715           0 :              ulong                          stake_cnt ) {
     716           0 :   bls_key_cnt_t * bls_key_cnts = bls_key_cnts_join( bls_key_cnts_new( ctx->scratch.bls_key_cnts ) );
     717           0 :   id_key_cnt_t *  id_key_cnts  = id_key_cnts_join ( id_key_cnts_new ( ctx->scratch.id_key_cnts  ) );
     718             : 
     719           0 :   rank_voter_t rank[ AG_VAT_MAX ]; /* surviving validators, pre-sort */
     720           0 :   ulong        in_cnt = fd_ulong_min( stake_cnt, AG_VAT_MAX );
     721           0 :   ulong        m      = 0UL;
     722           0 :   for( ulong i=0UL; i<in_cnt; i++ ) {
     723           0 :     if( FD_UNLIKELY( !stakes[i].stake ) ) continue; /* re-check nonzero stake, in case stakes came verbatim from a snapshot */
     724           0 :     uchar const * bls = stakes[i].bls_key;
     725           0 :     if( FD_UNLIKELY( fd_bls_pub_de( &rank[m].pk, bls, FD_BLS_PUB_COMPRESSED_SZ ) ) ) continue; /* no / invalid BLS key */
     726           0 :     rank[m].stake = stakes[i].stake;
     727           0 :     rank[m].bls   = bls;
     728           0 :     rank[m].src   = i;
     729           0 :     m++;
     730             : 
     731           0 :     bls_key_cnt_t * bls_key_cnt = bls_key_cnts_query( bls_key_cnts, FD_LOAD( bls_key_t, bls ), NULL );
     732           0 :     if( FD_LIKELY( !bls_key_cnt ) ) { bls_key_cnt = bls_key_cnts_insert( bls_key_cnts, FD_LOAD( bls_key_t, bls ) ); bls_key_cnt->cnt = 0UL; }
     733           0 :     bls_key_cnt->cnt++;
     734           0 :     id_key_cnt_t * id_key_cnt = id_key_cnts_query( id_key_cnts, stakes[i].id_key, NULL );
     735           0 :     if( FD_LIKELY( !id_key_cnt ) ) { id_key_cnt = id_key_cnts_insert( id_key_cnts, stakes[i].id_key ); id_key_cnt->cnt = 0UL; }
     736           0 :     id_key_cnt->cnt++;
     737           0 :   }
     738             : 
     739             :   /* ALL copies of a duplicated BLS key or identity are dropped */
     740             : 
     741           0 :   ulong k = 0UL;
     742           0 :   for( ulong i=0UL; i<m; i++ ) {
     743           0 :     if( FD_UNLIKELY( bls_key_cnts_query( bls_key_cnts, FD_LOAD( bls_key_t, rank[i].bls ), NULL )->cnt!=1UL ) ) continue;
     744           0 :     if( FD_UNLIKELY( id_key_cnts_query ( id_key_cnts,  stakes[ rank[i].src ].id_key,       NULL )->cnt!=1UL ) ) continue;
     745           0 :     rank[k++] = rank[i];
     746           0 :   }
     747             : 
     748           0 :   if( FD_UNLIKELY( !k ) ) { FD_LOG_WARNING(( "no validators survived ranking" )); return NULL; }
     749             : 
     750           0 :   rank_voters_sort_inplace( rank, k );
     751             : 
     752           0 :   ag_epoch_info_t * epoch_info = mem;
     753             : 
     754           0 :   ulong total = 0UL;
     755           0 :   for( ulong r=0UL; r<k; r++ ) {
     756           0 :     ulong                 src = rank[r].src;
     757           0 :     ag_validator_info_t * vi  = epoch_info->validators + r;
     758           0 :     memset( vi, 0, sizeof(ag_validator_info_t) );
     759           0 :     vi->id    = r;
     760           0 :     vi->stake = stakes[src].stake;
     761           0 :     memcpy( vi->id_key,   stakes[src].id_key.uc,   sizeof(ag_id_key_t)   );
     762           0 :     memcpy( vi->vote_key, stakes[src].vote_key.uc, sizeof(ag_vote_key_t) );
     763           0 :     vi->bls_key            = rank[r].pk;
     764           0 :     epoch_info->pubkeys[r] = rank[r].pk;
     765           0 :     total += vi->stake;
     766           0 :   }
     767           0 :   epoch_info->validator_cnt = k;
     768           0 :   epoch_info->total_stake   = total;
     769           0 :   return mem;
     770           0 : }
     771             : 
     772             : static void
     773             : handle_epoch( fd_votor_tile_t *           ctx,
     774           0 :               fd_epoch_info_msg_t const * msg ) {
     775             : 
     776           0 :   ag_epoch_info_t * epoch_info;
     777           0 :   if     ( FD_UNLIKELY( !ctx->curr_epoch_info ) ) epoch_info = &ctx->scratch.curr_epoch_info;
     778           0 :   else if( FD_UNLIKELY( !ctx->next_epoch_info ) ) epoch_info = &ctx->scratch.next_epoch_info;
     779           0 :   else if( FD_UNLIKELY( !ctx->prev_epoch_info ) ) epoch_info = &ctx->scratch.prev_epoch_info;
     780           0 :   else                                            epoch_info = ctx->prev_epoch_info;
     781           0 :   rank_voters( ctx, epoch_info, fd_epoch_info_msg_stake_weights( msg ), msg->staked_vote_cnt );
     782             : 
     783             :   /* swap pointers */
     784             : 
     785           0 :   if( FD_UNLIKELY( !ctx->curr_epoch_info ) ) {
     786           0 :     ctx->curr_epoch_info = epoch_info;
     787           0 :     ctx->curr_epoch_slot = msg->start_slot;
     788           0 :   } else {
     789           0 :     if( FD_LIKELY( ctx->next_epoch_info ) ) {
     790           0 :       ctx->prev_epoch_info = ctx->curr_epoch_info;
     791           0 :       ctx->prev_epoch_slot = ctx->curr_epoch_slot;
     792           0 :       ctx->curr_epoch_info = ctx->next_epoch_info;
     793           0 :       ctx->curr_epoch_slot = ctx->next_epoch_slot;
     794           0 :     }
     795           0 :     ctx->next_epoch_info = epoch_info;
     796           0 :     ctx->next_epoch_slot = msg->start_slot;
     797           0 :   }
     798             : 
     799             :   /* mark all for deletion */
     800             : 
     801           0 :   for( ulong slot=0UL; slot<peers_slot_cnt(); slot++ ) {
     802           0 :     peer_t * peer = &ctx->peers[ slot ];
     803           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
     804           0 :     peer->prev_rank = USHORT_MAX;
     805           0 :     peer->curr_rank = USHORT_MAX;
     806           0 :     peer->next_rank = USHORT_MAX;
     807           0 :   }
     808             : 
     809             :   /* unmark all ranked in prev epoch */
     810             : 
     811           0 :   ulong prev_cnt = ctx->prev_epoch_info ? ctx->prev_epoch_info->validator_cnt : 0UL;
     812           0 :   for( ulong rank=0UL; rank<prev_cnt; rank++ ) {
     813           0 :     fd_pubkey_t id_key;
     814           0 :     memcpy( id_key.uc, ctx->prev_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
     815             : 
     816           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
     817           0 :     if( FD_UNLIKELY( !peer ) ) {
     818           0 :       peer              = peers_insert( ctx->peers, id_key );
     819           0 :       peer->curr_rank   = USHORT_MAX;
     820           0 :       peer->next_rank   = USHORT_MAX;
     821           0 :       peer->tx_conn     = NULL;
     822           0 :       peer->rx_conn     = NULL;
     823           0 :       peer->ban_ts      = 0L;
     824           0 :     }
     825           0 :     peer->prev_rank = (ushort)rank;
     826           0 :   }
     827             : 
     828             :   /* unmark all ranked in curr epoch */
     829             : 
     830           0 :   ushort own_rank = USHORT_MAX; /* our own rank in the new epoch */
     831           0 :   for( ulong rank=0UL; rank<ctx->curr_epoch_info->validator_cnt; rank++ ) {
     832           0 :     fd_pubkey_t id_key;
     833           0 :     memcpy( id_key.uc, ctx->curr_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
     834           0 :     if( FD_UNLIKELY( ctx->curr_epoch_info==epoch_info && fd_pubkey_eq( &id_key, &ctx->id_key ) ) ) own_rank = (ushort)rank;
     835             : 
     836           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
     837           0 :     if( FD_UNLIKELY( !peer ) ) {
     838           0 :       peer              = peers_insert( ctx->peers, id_key );
     839           0 :       peer->prev_rank   = USHORT_MAX;
     840           0 :       peer->next_rank   = USHORT_MAX;
     841           0 :       peer->tx_conn     = NULL;
     842           0 :       peer->rx_conn     = NULL;
     843           0 :       peer->ban_ts      = 0L;
     844           0 :     }
     845           0 :     peer->curr_rank = (ushort)rank;
     846           0 :   }
     847             : 
     848             :   /* unmark all ranked in next epoch */
     849             : 
     850           0 :   ulong next_cnt = ctx->next_epoch_info ? ctx->next_epoch_info->validator_cnt : 0UL;
     851           0 :   for( ulong rank=0UL; rank<next_cnt; rank++ ) {
     852           0 :     fd_pubkey_t id_key;
     853           0 :     memcpy( id_key.uc, ctx->next_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
     854           0 :     if( FD_UNLIKELY( ctx->next_epoch_info==epoch_info && fd_pubkey_eq( &id_key, &ctx->id_key ) ) ) own_rank = (ushort)rank;
     855             : 
     856           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
     857           0 :     if( FD_UNLIKELY( !peer ) ) {
     858           0 :       peer            = peers_insert( ctx->peers, id_key );
     859           0 :       peer->prev_rank = USHORT_MAX;
     860           0 :       peer->curr_rank = USHORT_MAX;
     861           0 :       peer->tx_conn   = NULL;
     862           0 :       peer->rx_conn   = NULL;
     863           0 :       peer->ban_ts    = 0L;
     864           0 :     }
     865           0 :     peer->next_rank = (ushort)rank;
     866           0 :   }
     867             : 
     868             :   /* quic_connect new peers */
     869             : 
     870           0 :   long now = fd_log_wallclock();
     871           0 :   for( ulong slot=0UL; slot<peers_slot_cnt(); slot++ ) {
     872           0 :     peer_t * peer = &ctx->peers[ slot ];
     873           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
     874           0 :     if( FD_LIKELY( peers_query( ctx->peers, peer->id_key, NULL ) ) ) {
     875           0 :       contact_info_t * ci = contact_infos_query( ctx->contact_infos, peer->id_key, NULL );
     876           0 :       if( FD_LIKELY( ci && !peer->tx_conn && now>=peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     877           0 :         fd_quic_conn_t * conn = fd_quic_connect( ctx->quic_client, ci->ip4, ci->port, ctx->src_ip_addr, ctx->quic_client_listen_port, now );
     878           0 :         if( FD_LIKELY( conn ) ) {
     879           0 :           ctx->client_peer_id_keys[ conn->conn_idx ] = peer->id_key;
     880           0 :           fd_quic_conn_set_context( conn, &ctx->client_peer_id_keys[ conn->conn_idx ] );
     881           0 :           peer->tx_conn = conn;
     882           0 :         }
     883           0 :       }
     884           0 :     }
     885           0 :   }
     886             : 
     887             :   /* quic_conn_close evicted peers */
     888             : 
     889           0 :   for( ulong slot=0UL; slot<peers_slot_cnt(); ) {
     890           0 :     peer_t * peer = &ctx->peers[ slot ];
     891           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) )                            { slot++; continue; }
     892           0 :     if( FD_LIKELY( peer->prev_rank!=USHORT_MAX || peer->curr_rank!=USHORT_MAX || peer->next_rank!=USHORT_MAX ) ) { slot++; continue; }
     893           0 :     if( FD_LIKELY( peer->tx_conn ) ) {
     894           0 :       fd_quic_conn_set_context( peer->tx_conn, NULL );
     895           0 :       fd_quic_conn_close( peer->tx_conn, QUIC_CLOSE_CODE_EVICTED );
     896           0 :       peer->tx_conn = NULL;
     897           0 :     }
     898           0 :     if( FD_LIKELY( peer->rx_conn ) ) {
     899           0 :       fd_quic_conn_set_context( peer->rx_conn, NULL );
     900           0 :       fd_quic_conn_close( peer->rx_conn, QUIC_CLOSE_CODE_EVICTED );
     901           0 :       peer->rx_conn = NULL;
     902           0 :     }
     903           0 :     peers_remove( ctx->peers, peer ); /* relocates, so reconsider the freed slot */
     904           0 :   }
     905             : 
     906             :   /* update structures */
     907             : 
     908           0 :   ag_pool_advance_epoch ( ctx->pool,  epoch_info, own_rank, msg->start_slot );
     909           0 :   ag_votor_advance_epoch( ctx->votor, own_rank, msg->start_slot );
     910             : 
     911             :   /* update our leader schedule */
     912             : 
     913           0 :   fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, msg );
     914           0 :   fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
     915           0 :   if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX ) ) ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, msg->start_slot, &ctx->id_key );
     916             : 
     917           0 :   ctx->init = ctx->rooted_block_id.slot!=ULONG_MAX && !!ctx->shred_version;
     918           0 : }
     919             : 
     920             : static void
     921             : handle_gossip( fd_votor_tile_t *                  ctx,
     922             :                ulong                              sig,
     923           0 :                fd_gossip_update_message_t const * msg ) {
     924             : 
     925           0 :   fd_pubkey_t id_key;
     926           0 :   memcpy( id_key.uc, msg->origin, sizeof(fd_pubkey_t) );
     927           0 :   if( FD_UNLIKELY( peers_key_inval( id_key ) ) ) return;
     928             : 
     929           0 :   contact_info_t new_ci = {0}; /* dummy 0:0 address for removal */
     930           0 :   switch( sig ) {
     931           0 :   case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
     932           0 :     fd_gossip_socket_t const * socket = &msg->contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW ];
     933           0 :     new_ci.ip4  = fd_uint_if  ( !socket->is_ipv6, socket->ip4,                     0U        );
     934           0 :     new_ci.port = fd_ushort_if( !socket->is_ipv6, fd_ushort_bswap( socket->port ), (ushort)0 );
     935           0 :     break;
     936           0 :   }
     937           0 :   case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE:
     938           0 :     break;
     939           0 :   default:
     940           0 :     FD_LOG_ERR(( "unexpected gossip sig %lu", sig ));
     941           0 :   }
     942             : 
     943           0 :   contact_info_t * ci   = contact_infos_query( ctx->contact_infos, id_key, NULL );
     944           0 :   peer_t *         peer = peers_query        ( ctx->peers,         id_key, NULL );
     945             : 
     946           0 :   if( FD_UNLIKELY( !new_ci.port ) ) { /* nowhere left to reach it */
     947           0 :     if( FD_LIKELY( ci ) ) contact_infos_remove( ctx->contact_infos, ci );
     948           0 :     if( FD_UNLIKELY( peer && peer->tx_conn ) ) {
     949           0 :       fd_quic_conn_set_context( peer->tx_conn, NULL );
     950           0 :       fd_quic_conn_close( peer->tx_conn, 0U );
     951           0 :       peer->tx_conn = NULL;
     952           0 :     }
     953           0 :     return;
     954           0 :   }
     955             : 
     956           0 :   if( FD_UNLIKELY( !ci ) ) {
     957           0 :     ci       = contact_infos_insert( ctx->contact_infos, id_key );
     958           0 :     ci->ip4  = new_ci.ip4;
     959           0 :     ci->port = new_ci.port;
     960           0 :   } else if( FD_UNLIKELY( ci->ip4 !=new_ci.ip4 || ci->port!=new_ci.port ) ) {
     961           0 :     ci->ip4  = new_ci.ip4;
     962           0 :     ci->port = new_ci.port;
     963           0 :     if( FD_UNLIKELY( peer && peer->tx_conn ) ) { /* our conn is to the old address */
     964           0 :       fd_quic_conn_set_context( peer->tx_conn, NULL );
     965           0 :       fd_quic_conn_close( peer->tx_conn, 0U );
     966           0 :       peer->tx_conn = NULL;
     967           0 :     }
     968           0 :   }
     969             : 
     970           0 :   long now = fd_log_wallclock();
     971           0 :   if( FD_LIKELY( peer && !peer->tx_conn && now>=peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     972           0 :     fd_quic_conn_t * conn = fd_quic_connect( ctx->quic_client, ci->ip4, ci->port, ctx->src_ip_addr, ctx->quic_client_listen_port, now );
     973           0 :     if( FD_LIKELY( conn ) ) {
     974           0 :       ctx->client_peer_id_keys[ conn->conn_idx ] = peer->id_key;
     975           0 :       fd_quic_conn_set_context( conn, &ctx->client_peer_id_keys[ conn->conn_idx ] );
     976           0 :       peer->tx_conn = conn;
     977           0 :     }
     978           0 :   }
     979           0 : }
     980             : 
     981             : static void
     982             : handle_replay( fd_votor_tile_t *           ctx,
     983             :                ulong                       sig,
     984           0 :                fd_replay_message_t const * replay ) {
     985             : 
     986           0 :   switch( sig ) {
     987           0 :   case REPLAY_SIG_SLOT_COMPLETED: {
     988           0 :     fd_replay_slot_completed_t const * slot_completed  = &replay->slot_completed;
     989           0 :     ag_block_id_t                      block_id        = ag_block_id( slot_completed->slot,        slot_completed->block_id.uc        );
     990           0 :     ag_block_id_t                      parent_block_id = ag_block_id( slot_completed->parent_slot, slot_completed->parent_block_id.uc );
     991           0 :     if( FD_LIKELY( !replayed_query( ctx->replayed, block_id, NULL ) ) ) {
     992           0 :       replayed_insert( ctx->replayed, block_id )->parent_block_id = parent_block_id;
     993           0 :     }
     994           0 :     if( FD_UNLIKELY( ag_block_id_eq( &block_id, &ctx->finalized_block_id ) ) ) {
     995           0 :       try_advance_root( ctx, ctx->finalized_block_id );
     996           0 :     }
     997           0 :     if( FD_UNLIKELY( ctx->rooted_block_id.slot==ULONG_MAX ) ) {
     998           0 :       ctx->rooted_block_id = block_id;
     999           0 :       ag_pool_init( ctx->pool, block_id.slot );
    1000           0 :       if( FD_LIKELY( ctx->shred_version ) ) ag_votor_init( ctx->votor, block_id.slot, fd_log_wallclock(), ctx->shred_version, sign_bls, ctx );
    1001           0 :       ctx->init = !!ctx->curr_epoch_info && !!ctx->shred_version;
    1002           0 :     } else if( FD_UNLIKELY( block_id.slot!=0 ) ) {
    1003           0 :       ag_pool_add_block( ctx->pool, &block_id, &parent_block_id, ctx->scratch.bad );
    1004           0 :       if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, block_id.slot );
    1005           0 :     }
    1006           0 :     ag_event_replay_t completed = { .kind = AG_EVENT_REPLAY_COMPLETED, .slot = block_id.slot, .block_info = { .parent = parent_block_id } };
    1007           0 :     memcpy( completed.block_info.hash, block_id.hash, sizeof(ag_block_hash_t) );
    1008           0 :     ag_votor_handle_replay_event( ctx->votor, &completed );
    1009           0 :     break;
    1010           0 :   }
    1011           0 :   case REPLAY_SIG_SLOT_DEAD: {
    1012           0 :     fd_replay_slot_dead_t const * slot_dead = &replay->slot_dead;
    1013           0 :     ag_event_replay_t             dead      = { .kind = AG_EVENT_REPLAY_DEAD, .slot = slot_dead->slot };
    1014           0 :     ag_votor_handle_replay_event( ctx->votor, &dead );
    1015           0 :     break;
    1016           0 :   }
    1017           0 :   default:
    1018           0 :     FD_LOG_ERR(( "unexpected replay sig %lu", sig ));
    1019           0 :   }
    1020           0 : }
    1021             : 
    1022             : FD_FN_CONST static inline ulong
    1023           0 : scratch_align( void ) {
    1024           0 :   return fd_ulong_max( alignof(fd_votor_tile_t), fd_quic_align() );
    1025           0 : }
    1026             : 
    1027             : FD_FN_PURE static inline ulong
    1028           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
    1029           0 :   int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( AG_EQVOC_BLOCK_HASH_MAX*tile->votor.max_live_slots ) ) + 1;
    1030           0 :   ulong l = FD_LAYOUT_INIT;
    1031           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_votor_tile_t),       sizeof(fd_votor_tile_t)                           );
    1032           0 :   l = FD_LAYOUT_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_client_limits )          );
    1033           0 :   l = FD_LAYOUT_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_server_limits )          );
    1034           0 :   l = FD_LAYOUT_APPEND( l, ag_pool_align(),                ag_pool_footprint( tile->votor.max_live_slots )   );
    1035           0 :   l = FD_LAYOUT_APPEND( l, ag_votor_align(),               ag_votor_footprint( tile->votor.max_live_slots )  );
    1036           0 :   l = FD_LAYOUT_APPEND( l, replayed_align(),               replayed_footprint( lg_blk_max )                  );
    1037           0 :   l = FD_LAYOUT_APPEND( l, rooted_align(),                 rooted_footprint( tile->votor.max_live_slots )    );
    1038           0 :   l = FD_LAYOUT_APPEND( l, publishes_align(),              publishes_footprint( tile->votor.max_live_slots ) );
    1039           0 :   l = FD_LAYOUT_APPEND( l, peers_align(),                  peers_footprint()                                 );
    1040           0 :   l = FD_LAYOUT_APPEND( l, contact_infos_align(),          contact_infos_footprint()                         );
    1041           0 :   l = FD_LAYOUT_APPEND( l, fd_multi_epoch_leaders_align(), fd_multi_epoch_leaders_footprint()                );
    1042           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
    1043           0 : }
    1044             : 
    1045             : static inline void
    1046             : after_credit( fd_votor_tile_t *   ctx,
    1047             :               fd_stem_context_t * stem,
    1048             :               int *               opt_poll_in,
    1049           0 :               int *               charge_busy ) {
    1050             : 
    1051           0 :   long now     = fd_log_wallclock();
    1052           0 :   *charge_busy = fd_quic_service( ctx->quic_client, now ) | fd_quic_service( ctx->quic_server, now );
    1053           0 :   for( ulong i=0UL; i<ctx->net_tx_cnt; i++ ) fd_stem_publish( stem, OUT_IDX_NET, ctx->net_tx[ i ].sig, ctx->net_tx[ i ].chunk, ctx->net_tx[ i ].sz, fd_frag_meta_ctl( 0UL, 1, 1, 0 ), 0L, 0L );
    1054           0 :   ctx->net_tx_cnt = 0UL;
    1055             : 
    1056           0 :   if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) {
    1057           0 :     publish_t pub = publishes_pop( ctx->publishes );
    1058           0 :     memcpy( fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk ), &pub.msg, sizeof(fd_votor_msg_t) );
    1059           0 :     fd_stem_publish( stem, OUT_IDX_VOTOR, pub.sig, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    1060           0 :     ctx->votor_out_chunk = fd_dcache_compact_next( ctx->votor_out_chunk, sizeof(fd_votor_msg_t), ctx->votor_out_chunk0, ctx->votor_out_wmark );
    1061           0 :     *opt_poll_in         = 0; /* drain the publishes */
    1062           0 :     *charge_busy         = 1;
    1063           0 :     return;
    1064           0 :   }
    1065             : 
    1066           0 :   if( FD_UNLIKELY( !ctx->init ) ) return;
    1067             : 
    1068           0 :   if( FD_UNLIKELY( ag_pool_poll_pool_event( ctx->pool, &ctx->scratch.pool_event ) ) ) {
    1069           0 :     ag_votor_handle_pool_event( ctx->votor, &ctx->scratch.pool_event, now );
    1070           0 :     ag_cert_t const * cert = &ctx->scratch.pool_event.cert_created;
    1071           0 :     if( FD_UNLIKELY( ctx->scratch.pool_event.kind==AG_EVENT_POOL_CERT_CREATED ) ) {
    1072           0 :       ulong                slot = ag_cert_slot( cert );
    1073           0 :       final_notar_join_t * cs   = &ctx->final_notar_join[ slot%CERT_SLOT_MAX ];
    1074           0 :       if( FD_UNLIKELY( cs->slot!=slot ) ) {
    1075           0 :         cs->slot      = slot;
    1076           0 :         cs->has_notar = 0;
    1077           0 :         cs->has_final = 0;
    1078           0 :       }
    1079             : 
    1080           0 :       publish_t pub = { .sig = FD_VOTOR_SIG_CERTED };
    1081           0 :       fd_votor_certed_t * certed = &pub.msg.certed;
    1082           0 :       memset( certed, 0, sizeof(fd_votor_certed_t) );
    1083           0 :       certed->kind = cert->kind;
    1084           0 :       certed->slot = slot;
    1085           0 :       switch( cert->kind ) {
    1086           0 :       case AG_CERT_KIND_FINAL: /* reported with its notarization below */
    1087           0 :         cs->has_final = 1;
    1088           0 :         cs->final     = cert->final.agg;
    1089           0 :         break;
    1090           0 :       case AG_CERT_KIND_FAST_FINAL:
    1091           0 :         memcpy( certed->block_id.uc, cert->fast_final.block_hash, sizeof(fd_hash_t) );
    1092           0 :         certed->agg = cert->fast_final.agg;
    1093           0 :         break;
    1094           0 :       case AG_CERT_KIND_NOTAR:
    1095           0 :         memcpy( certed->block_id.uc, cert->notar.block_hash, sizeof(fd_hash_t) );
    1096           0 :         certed->agg = cert->notar.agg;
    1097           0 :         cs->has_notar = 1;
    1098           0 :         cs->notar     = cert->notar.agg;
    1099           0 :         memcpy( cs->notar_block_hash, cert->notar.block_hash, sizeof(ag_block_hash_t) );
    1100           0 :         break;
    1101           0 :       case AG_CERT_KIND_NOTAR_FALLBACK:
    1102           0 :         memcpy( certed->block_id.uc, cert->notar_fallback.block_hash, sizeof(fd_hash_t) );
    1103           0 :         certed->agg  = cert->notar_fallback.agg_notar;
    1104           0 :         certed->agg2 = cert->notar_fallback.agg_notar_fallback;
    1105           0 :         break;
    1106           0 :       case AG_CERT_KIND_SKIP:
    1107           0 :         certed->agg  = cert->skip.agg_skip;
    1108           0 :         certed->agg2 = cert->skip.agg_skip_fallback;
    1109           0 :         break;
    1110           0 :       default:
    1111           0 :         FD_LOG_CRIT(( "unreachable" ));
    1112           0 :       }
    1113           0 :       if( FD_LIKELY( cert->kind!=AG_CERT_KIND_FINAL ) ) {
    1114           0 :         FD_TEST( !publishes_full( ctx->publishes ) );
    1115           0 :         publishes_push( ctx->publishes, pub );
    1116           0 :       }
    1117             : 
    1118           0 :       if( FD_UNLIKELY( cs->has_final && cs->has_notar ) ) {
    1119           0 :         memset( certed, 0, sizeof(fd_votor_certed_t) );
    1120           0 :         certed->kind = AG_CERT_KIND_FINAL;
    1121           0 :         certed->slot = slot;
    1122           0 :         certed->agg  = cs->final;
    1123           0 :         certed->agg2 = cs->notar;
    1124           0 :         memcpy( certed->block_id.uc, cs->notar_block_hash, sizeof(fd_hash_t) );
    1125           0 :         cs->has_final = 0;
    1126           0 :         FD_TEST( !publishes_full( ctx->publishes ) );
    1127           0 :         publishes_push( ctx->publishes, pub );
    1128           0 :       }
    1129           0 :     }
    1130           0 :     *charge_busy = 1;
    1131           0 :   }
    1132             : 
    1133           0 :   if( FD_UNLIKELY( ag_pool_poll_repair_event( ctx->pool, &ctx->scratch.repair_event ) ) ) {
    1134           0 :     publish_t pub = { .sig = FD_VOTOR_SIG_REPAIR };
    1135           0 :     pub.msg.repair.slot = ctx->scratch.repair_event.block.slot;
    1136           0 :     memcpy( &pub.msg.repair.block_id, ctx->scratch.repair_event.block.hash, sizeof(fd_hash_t) );
    1137           0 :     FD_TEST( !publishes_full( ctx->publishes ) );
    1138           0 :     publishes_push( ctx->publishes, pub );
    1139           0 :     *charge_busy = 1;
    1140           0 :   }
    1141             : 
    1142           0 :   if( FD_UNLIKELY( ag_votor_poll_timeout_event( ctx->votor, now, &ctx->scratch.timeout_event ) ) ) { /* a timeout we set on ParentReady */
    1143           0 :     ag_votor_handle_timeout_event( ctx->votor, &ctx->scratch.timeout_event );
    1144           0 :     *charge_busy = 1;
    1145           0 :   }
    1146             : 
    1147           0 :   if( FD_UNLIKELY( ag_votor_poll_vote_event( ctx->votor, &ctx->scratch.vote_event ) ) ) { /* our own vote */
    1148           0 :     ulong                   vote_slot  = ag_vote_slot( &ctx->scratch.vote_event.vote );
    1149           0 :     ag_epoch_info_t const * epoch_info = fd_ptr_if( vote_slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( vote_slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
    1150           0 :     ulong                   rank       = ag_vote_rank( &ctx->scratch.vote_event.vote );
    1151           0 :     if( FD_LIKELY( epoch_info && rank<epoch_info->validator_cnt ) ) {
    1152           0 :       ag_pool_add_vote( ctx->pool, &ctx->scratch.vote_event.vote, ctx->scratch.bad );
    1153           0 :       if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, vote_slot );
    1154             : 
    1155           0 :       ulong ser_sz = ag_vote_ser( &ctx->scratch.vote_event.vote, ctx->scratch.ser );
    1156           0 :       for( ulong slot=0UL; slot<peers_slot_cnt(); slot++ ) {
    1157           0 :         peer_t const * peer = &ctx->peers[ slot ];
    1158           0 :         if( FD_LIKELY( peers_key_inval( peer->id_key ) || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) continue;
    1159           0 :         quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
    1160           0 :       }
    1161             : 
    1162           0 :       *charge_busy = 1;
    1163           0 :     }
    1164           0 :   }
    1165             : 
    1166           0 :   if( FD_UNLIKELY( ag_votor_poll_cert_event( ctx->votor, &ctx->scratch.cert_event ) ) ) { /* a cert the pool accepted, or a standstill re-broadcast */
    1167           0 :     ag_pool_add_cert( ctx->pool, &ctx->scratch.cert_event.cert, ctx->scratch.bad );
    1168           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, ag_cert_slot( &ctx->scratch.cert_event.cert ) );
    1169             : 
    1170           0 :     ulong ser_sz = ag_cert_ser( &ctx->scratch.cert_event.cert, ctx->scratch.ser );
    1171           0 :     for( ulong slot=0UL; slot<peers_slot_cnt(); slot++ ) {
    1172           0 :       peer_t const * peer = &ctx->peers[ slot ];
    1173           0 :       if( FD_LIKELY( peers_key_inval( peer->id_key ) || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) continue;
    1174           0 :       quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
    1175           0 :     }
    1176             : 
    1177           0 :     uint          kind           = ctx->scratch.cert_event.cert.kind;
    1178           0 :     uchar const * finalized_hash = ag_pool_finalized_block_hash( ctx->pool );
    1179           0 :     if( FD_LIKELY( ( kind==AG_CERT_KIND_FINAL || kind==AG_CERT_KIND_FAST_FINAL ) && finalized_hash ) ) {
    1180           0 :       try_advance_root( ctx, ag_block_id( ag_pool_finalized_slot( ctx->pool ), finalized_hash ) );
    1181           0 :     }
    1182           0 :     *charge_busy = 1;
    1183           0 :   }
    1184             : 
    1185           0 :   if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX ) ) return; /* never will be leader */
    1186             : 
    1187             :   /* Check if it's time to become leader. */
    1188             : 
    1189           0 :   ulong finalized_slot = ag_pool_finalized_slot( ctx->pool );
    1190           0 :   while( FD_UNLIKELY( ctx->next_leader_slot<=finalized_slot ) ) {
    1191           0 :     ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, ctx->next_leader_slot+AG_SLOTS_PER_WINDOW, &ctx->id_key );
    1192           0 :     if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX ) ) return; /* schedule exhausted */
    1193           0 :   }
    1194             : 
    1195           0 :   ag_block_id_t parent = ag_pool_wait_for_parent_ready( ctx->pool, ctx->next_leader_slot );
    1196           0 :   if( FD_UNLIKELY( parent.slot==ULONG_MAX ) ) return; /* the pool has not granted parent ready yet */
    1197             : 
    1198           0 :   ulong reward_slot = fd_ulong_sat_sub( ctx->next_leader_slot, FD_NUM_SLOTS_FOR_REWARD );
    1199           0 :   for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) publish_reward_certs( ctx, reward_slot+i );
    1200             : 
    1201           0 :   publish_t pub = { .sig = FD_VOTOR_SIG_LEADER };
    1202           0 :   pub.msg.leader.slot        = ctx->next_leader_slot;
    1203           0 :   pub.msg.leader.parent_slot = parent.slot;
    1204           0 :   memcpy( pub.msg.leader.parent_block_id.uc, parent.hash, sizeof(fd_hash_t) );
    1205           0 :   FD_TEST( !publishes_full( ctx->publishes ) );
    1206           0 :   publishes_push( ctx->publishes, pub );
    1207             : 
    1208           0 :   ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, ctx->next_leader_slot+AG_SLOTS_PER_WINDOW, &ctx->id_key );
    1209           0 :   *charge_busy = 1;
    1210           0 : }
    1211             : 
    1212             : static int
    1213             : before_frag( fd_votor_tile_t * ctx,
    1214             :              ulong             in_idx,
    1215             :              ulong             seq,
    1216           0 :              ulong             sig ) {
    1217           0 :   (void)seq;
    1218             : 
    1219           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1220           0 :   case IN_KIND_EPOCH:
    1221           0 :     return 0;
    1222           0 :   case IN_KIND_GOSSIP:
    1223           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
    1224           0 :   case IN_KIND_IPECHO:
    1225           0 :     return 0;
    1226           0 :   case IN_KIND_NET:
    1227           0 :     if( FD_UNLIKELY( !ctx->curr_epoch_info ) ) return 1;
    1228           0 :     return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_VOTOR;
    1229           0 :   case IN_KIND_REPLAY:
    1230           0 :     if( FD_UNLIKELY( !ctx->curr_epoch_info ) ) return 1;
    1231           0 :     return sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_SLOT_DEAD;
    1232           0 :   default:
    1233           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    1234           0 :   }
    1235           0 : }
    1236             : 
    1237             : static void
    1238             : during_frag( fd_votor_tile_t * ctx,
    1239             :              ulong             in_idx,
    1240             :              ulong             seq,
    1241             :              ulong             sig,
    1242             :              ulong             chunk,
    1243             :              ulong             sz,
    1244           0 :              ulong             ctl ) {
    1245           0 :   (void)seq;
    1246             : 
    1247           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1248           0 :   case IN_KIND_EPOCH:
    1249           0 :     handle_epoch( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    1250           0 :     break;
    1251           0 :   case IN_KIND_GOSSIP: {
    1252           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) {
    1253           0 :       FD_LOG_ERR(( "chunk %lu sz %lu from gossip out of bounds, chunk0 %lu wmark %lu",
    1254           0 :                    chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
    1255           0 :     }
    1256           0 :     handle_gossip( ctx, sig, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    1257           0 :     break;
    1258           0 :   }
    1259           0 :   case IN_KIND_IPECHO:
    1260             :     /* unreliable link, handled in after_frag */
    1261           0 :     break;
    1262           0 :   case IN_KIND_NET:
    1263           0 :     fd_memcpy( ctx->net_buf, fd_net_rx_translate_frag( &ctx->net_in_bounds[ in_idx ], chunk, ctl, sz ), sz );
    1264           0 :     break;
    1265           0 :   case IN_KIND_REPLAY: {
    1266           0 :     if( FD_UNLIKELY( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>sizeof(fd_replay_message_t) ) ) {
    1267           0 :       FD_LOG_ERR(( "chunk %lu sz %lu from replay out of bounds, chunk0 %lu wmark %lu",
    1268           0 :                    chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
    1269           0 :     }
    1270           0 :     handle_replay( ctx, sig, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    1271           0 :     break;
    1272           0 :   }
    1273           0 :   default:
    1274           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    1275           0 :   }
    1276           0 : }
    1277             : 
    1278             : static void
    1279             : after_frag( fd_votor_tile_t *   ctx,
    1280             :             ulong               in_idx,
    1281             :             ulong               seq,
    1282             :             ulong               sig,
    1283             :             ulong               sz,
    1284             :             ulong               tsorig,
    1285             :             ulong               tspub,
    1286           0 :             fd_stem_context_t * stem ) {
    1287           0 :   (void)seq; (void)tsorig; (void)tspub;
    1288             : 
    1289           0 :   switch( ctx->in_kind[ in_idx ] ) {
    1290           0 :   case IN_KIND_EPOCH:
    1291             :     /* reliable link, handled in during_frag */
    1292           0 :     break;
    1293           0 :   case IN_KIND_GOSSIP:
    1294             :     /* reliable link, handled in during_frag */
    1295           0 :     break;
    1296           0 :   case IN_KIND_IPECHO:
    1297           0 :     FD_TEST( sig && sig<=USHORT_MAX );
    1298           0 :     if( FD_UNLIKELY( !ctx->shred_version && ctx->rooted_block_id.slot!=ULONG_MAX ) ) ag_votor_init( ctx->votor, ctx->rooted_block_id.slot, fd_log_wallclock(), (ushort)sig, sign_bls, ctx );
    1299           0 :     ctx->shred_version = (ushort)sig;
    1300           0 :     ctx->init = !!ctx->curr_epoch_info && ctx->rooted_block_id.slot!=ULONG_MAX;
    1301           0 :     break;
    1302           0 :   case IN_KIND_NET: {
    1303           0 :     if( FD_UNLIKELY( sz<sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) ) break;
    1304           0 :     fd_ip4_hdr_t const * ip4   = (fd_ip4_hdr_t const *)fd_type_pun_const( ctx->net_buf+sizeof(fd_eth_hdr_t) );
    1305           0 :     ulong                iplen = FD_IP4_GET_LEN( *ip4 );
    1306           0 :     if( FD_UNLIKELY( iplen<sizeof(fd_ip4_hdr_t) || sz<sizeof(fd_eth_hdr_t)+iplen+sizeof(fd_udp_hdr_t) ) ) break;
    1307           0 :     fd_udp_hdr_t const * udp   = (fd_udp_hdr_t const *)fd_type_pun_const( ctx->net_buf+sizeof(fd_eth_hdr_t)+iplen );
    1308           0 :     ushort               dport = fd_ushort_bswap( udp->net_dport );
    1309           0 :     if( FD_UNLIKELY( dport!=ctx->quic_client_listen_port && dport!=ctx->quic_server_listen_port ) ) break;
    1310           0 :     fd_quic_t * quic = fd_ptr_if( dport==ctx->quic_client_listen_port, ctx->quic_client, ctx->quic_server );
    1311           0 :     fd_quic_process_packet( quic, ctx->net_buf+sizeof(fd_eth_hdr_t), sz-sizeof(fd_eth_hdr_t), fd_log_wallclock() );
    1312           0 :     for( ulong i=0UL; i<ctx->net_tx_cnt; i++ ) fd_stem_publish( stem, OUT_IDX_NET, ctx->net_tx[ i ].sig, ctx->net_tx[ i ].chunk, ctx->net_tx[ i ].sz, fd_frag_meta_ctl( 0UL, 1, 1, 0 ), 0L, 0L );
    1313           0 :     ctx->net_tx_cnt = 0UL;
    1314           0 :     break;
    1315           0 :   }
    1316           0 :   case IN_KIND_REPLAY:
    1317             :     /* reliable link, handled in during_frag */
    1318           0 :     break;
    1319           0 :   default:
    1320           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    1321           0 :   }
    1322           0 : }
    1323             : 
    1324             : static void
    1325             : privileged_init( fd_topo_t const *      topo,
    1326           0 :                  fd_topo_tile_t const * tile ) {
    1327           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1328             : 
    1329           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1330           0 :   fd_votor_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t), sizeof(fd_votor_tile_t) );
    1331             : 
    1332           0 :   if( FD_UNLIKELY( !strcmp( tile->votor.identity_key_path, "" ) ) )
    1333           0 :     FD_LOG_ERR(( "identity_key_path not set" ));
    1334             : 
    1335           0 :   ctx->id_key = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->votor.identity_key_path, /* pubkey only: */ 1 ) );
    1336             : 
    1337           0 :   fd_log_wallclock();
    1338           0 : }
    1339             : 
    1340             : static void
    1341             : unprivileged_init( fd_topo_t const *      topo,
    1342           0 :                    fd_topo_tile_t const * tile ) {
    1343             : 
    1344           0 :   int    lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( AG_EQVOC_BLOCK_HASH_MAX*tile->votor.max_live_slots ) ) + 1;
    1345           0 :   void * scratch    = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    1346             : 
    1347           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    1348           0 :   fd_votor_tile_t * ctx           = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t),       sizeof(fd_votor_tile_t)                           );
    1349           0 :   void *            quic_client   = FD_SCRATCH_ALLOC_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_client_limits )          );
    1350           0 :   void *            quic_server   = FD_SCRATCH_ALLOC_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_server_limits )          );
    1351           0 :   void *            pool          = FD_SCRATCH_ALLOC_APPEND( l, ag_pool_align(),                ag_pool_footprint( tile->votor.max_live_slots )   );
    1352           0 :   void *            votor         = FD_SCRATCH_ALLOC_APPEND( l, ag_votor_align(),               ag_votor_footprint( tile->votor.max_live_slots )  );
    1353           0 :   void *            replayed      = FD_SCRATCH_ALLOC_APPEND( l, replayed_align(),               replayed_footprint( lg_blk_max )                  );
    1354           0 :   void *            rooted        = FD_SCRATCH_ALLOC_APPEND( l, rooted_align(),                 rooted_footprint( tile->votor.max_live_slots )    );
    1355           0 :   void *            publishes     = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(),              publishes_footprint( tile->votor.max_live_slots ) );
    1356           0 :   void *            peers         = FD_SCRATCH_ALLOC_APPEND( l, peers_align(),                  peers_footprint()                                 );
    1357           0 :   void *            contact_infos = FD_SCRATCH_ALLOC_APPEND( l, contact_infos_align(),          contact_infos_footprint()                         );
    1358           0 :   void *            mleaders      = FD_SCRATCH_ALLOC_APPEND( l, fd_multi_epoch_leaders_align(), fd_multi_epoch_leaders_footprint()                );
    1359           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    1360           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
    1361           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    1362             : 
    1363           0 :   ctx->shred_version = (ushort)0;
    1364             : 
    1365           0 :   memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    1366             : 
    1367           0 :   ulong seed;
    1368           0 :   FD_TEST( fd_rng_secure( &seed, sizeof(seed) ) );
    1369             : 
    1370           0 :   ctx->pool = ag_pool_join( ag_pool_new( pool, tile->votor.max_live_slots, seed ) );
    1371           0 :   FD_TEST( ctx->pool );
    1372             : 
    1373           0 :   ctx->votor = ag_votor_join( ag_votor_new( votor, tile->votor.max_live_slots, seed ) );
    1374           0 :   FD_TEST( ctx->votor );
    1375             : 
    1376           0 :   ctx->prev_epoch_info = NULL;
    1377           0 :   ctx->prev_epoch_slot = ULONG_MAX;
    1378           0 :   ctx->curr_epoch_info = NULL;
    1379           0 :   ctx->curr_epoch_slot = ULONG_MAX;
    1380           0 :   ctx->next_epoch_info = NULL;
    1381           0 :   ctx->next_epoch_slot = ULONG_MAX;
    1382           0 :   memset( ctx->client_peer_id_keys, 0, sizeof(ctx->client_peer_id_keys) );
    1383           0 :   memset( ctx->server_peer_id_keys, 0, sizeof(ctx->server_peer_id_keys) );
    1384             : 
    1385           0 :   if( FD_UNLIKELY( !tile->votor.quic_client_listen_port ) )
    1386           0 :     FD_LOG_ERR(( "[development.votor.quic_client_listen_port] must be non-zero when alpenglow is enabled" ));
    1387           0 :   if( FD_UNLIKELY( tile->votor.quic_client_listen_port==tile->votor.quic_server_listen_port ) )
    1388           0 :     FD_LOG_ERR(( "[development.votor.quic_client_listen_port] %hu must differ from [development.votor.quic_server_listen_port]",
    1389           0 :                  tile->votor.quic_client_listen_port ));
    1390             : 
    1391           0 :   ctx->quic_client_listen_port = tile->votor.quic_client_listen_port;
    1392           0 :   ctx->quic_server_listen_port = tile->votor.quic_server_listen_port;
    1393           0 :   ctx->src_ip_addr             = tile->votor.ip_addr;
    1394           0 :   ctx->net_id                  = (ushort)0;
    1395           0 :   fd_ip4_udp_hdr_init( ctx->hdr, FD_NET_MTU, ctx->src_ip_addr, ctx->quic_client_listen_port );
    1396             : 
    1397           0 :   ctx->rooted_block_id    = (ag_block_id_t){ .slot = ULONG_MAX };
    1398           0 :   ctx->finalized_block_id = (ag_block_id_t){ .slot = ULONG_MAX };
    1399             : 
    1400           0 :   ctx->replayed = replayed_join( replayed_new( replayed, lg_blk_max, seed ) );
    1401           0 :   FD_TEST( ctx->replayed );
    1402             : 
    1403           0 :   ctx->rooted = rooted_join( rooted_new( rooted, tile->votor.max_live_slots ) );
    1404           0 :   FD_TEST( ctx->rooted );
    1405             : 
    1406           0 :   ctx->publishes = publishes_join( publishes_new( publishes, tile->votor.max_live_slots ) );
    1407           0 :   FD_TEST( ctx->publishes );
    1408             : 
    1409           0 :   ctx->peers = peers_join( peers_new( peers ) );
    1410           0 :   FD_TEST( ctx->peers );
    1411             : 
    1412           0 :   ctx->contact_infos = contact_infos_join( contact_infos_new( contact_infos ) );
    1413           0 :   FD_TEST( ctx->contact_infos );
    1414             : 
    1415           0 :   ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( mleaders ) );
    1416           0 :   FD_TEST( ctx->mleaders );
    1417             : 
    1418           0 :   ctx->init             = 0;
    1419           0 :   ctx->net_tx_cnt       = 0UL;
    1420           0 :   ctx->next_leader_slot = ULONG_MAX;
    1421           0 :   for( ulong i=0UL; i<CERT_SLOT_MAX; i++ ) ctx->final_notar_join[ i ].slot = ULONG_MAX;
    1422             : 
    1423           0 :   FD_TEST( tile->in_cnt<=sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
    1424           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    1425           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    1426             : 
    1427           0 :     if     ( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
    1428           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_out"   ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
    1429           0 :     else if( FD_LIKELY( !strcmp( link->name, "ipecho_out"   ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
    1430           0 :     else if( FD_LIKELY( !strcmp( link->name, "net_votor"    ) ) ) {
    1431           0 :       ctx->in_kind[ i ] = IN_KIND_NET;
    1432           0 :       fd_net_rx_bounds_init( &ctx->net_in_bounds[ i ], link->dcache );
    1433           0 :     }
    1434           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_out"   ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    1435           0 :     else if( FD_LIKELY( !strcmp( link->name, "sign_votor"   ) ) ) ctx->in_kind[ i ] = IN_KIND_SIGN;
    1436           0 :     else FD_LOG_ERR(( "votor tile has unexpected input link %lu %s", i, link->name ));
    1437             : 
    1438           0 :     if( FD_LIKELY( link->mtu ) ) {
    1439           0 :       ctx->in[ i ].mem    = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    1440           0 :       ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
    1441           0 :       ctx->in[ i ].wmark  = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
    1442           0 :       ctx->in[ i ].mtu    = link->mtu;
    1443           0 :     }
    1444           0 :   }
    1445             : 
    1446           0 :   FD_TEST( tile->out_cnt>OUT_IDX_NET );
    1447           0 :   fd_topo_link_t const * votor_out = &topo->links[ tile->out_link_id[ OUT_IDX_VOTOR ] ];
    1448           0 :   FD_TEST( !strcmp( votor_out->name, "votor_out" ) );
    1449           0 :   ctx->votor_out_mem    = topo->workspaces[ topo->objs[ votor_out->dcache_obj_id ].wksp_id ].wksp;
    1450           0 :   ctx->votor_out_chunk0 = fd_dcache_compact_chunk0( ctx->votor_out_mem, votor_out->dcache );
    1451           0 :   ctx->votor_out_wmark  = fd_dcache_compact_wmark ( ctx->votor_out_mem, votor_out->dcache, votor_out->mtu );
    1452           0 :   ctx->votor_out_chunk  = ctx->votor_out_chunk0;
    1453             : 
    1454           0 :   fd_topo_link_t const * net_out = &topo->links[ tile->out_link_id[ OUT_IDX_NET ] ];
    1455           0 :   FD_TEST( !strcmp( net_out->name, "votor_net" ) );
    1456           0 :   FD_TEST( net_out->burst>=FD_VOTOR_NET_BURST );
    1457           0 :   ctx->net_out_mem    = topo->workspaces[ topo->objs[ net_out->dcache_obj_id ].wksp_id ].wksp;
    1458           0 :   ctx->net_out_chunk0 = fd_dcache_compact_chunk0( ctx->net_out_mem, net_out->dcache );
    1459           0 :   ctx->net_out_wmark  = fd_dcache_compact_wmark ( ctx->net_out_mem, net_out->dcache, net_out->mtu );
    1460           0 :   ctx->net_out_chunk  = ctx->net_out_chunk0;
    1461             : 
    1462           0 :   ulong sign_in_idx  = fd_topo_find_tile_in_link ( topo, tile, "sign_votor", tile->kind_id );
    1463           0 :   ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "votor_sign", tile->kind_id );
    1464           0 :   FD_TEST( sign_in_idx !=ULONG_MAX );
    1465           0 :   FD_TEST( sign_out_idx!=ULONG_MAX );
    1466           0 :   fd_topo_link_t const * sign_in  = &topo->links[ tile->in_link_id [ sign_in_idx  ] ];
    1467           0 :   fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ];
    1468           0 :   if( FD_UNLIKELY( !fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client, sign_out->mcache, sign_out->dcache, sign_in->mcache, sign_in->dcache, sign_out->mtu, sign_in->mtu ) ) ) ) {
    1469           0 :     FD_LOG_ERR(( "failed to construct keyguard client" ));
    1470           0 :   }
    1471             : 
    1472           0 :   fd_aio_t * quic_tx_aio = fd_aio_join( fd_aio_new( ctx->quic_tx_aio, ctx, quic_aio_tx ) );
    1473           0 :   FD_TEST( quic_tx_aio );
    1474             : 
    1475           0 :   ctx->quic_client = fd_quic_join( fd_quic_new( quic_client, &quic_client_limits ) );
    1476           0 :   FD_TEST( ctx->quic_client );
    1477           0 :   fd_quic_set_aio_net_tx( ctx->quic_client, quic_tx_aio );
    1478             : 
    1479           0 :   ctx->quic_client->config.role                       = FD_QUIC_ROLE_CLIENT;
    1480           0 :   ctx->quic_client->config.retry                      = 0;
    1481           0 :   ctx->quic_client->config.keep_alive                 = 1;
    1482           0 :   ctx->quic_client->config.idle_timeout               = 5L*1000L*1000L*1000L;
    1483           0 :   ctx->quic_client->config.ack_delay                  = 2L*1000L*1000L;
    1484           0 :   memcpy( ctx->quic_client->config.identity_public_key, ctx->id_key.uc, 32UL );
    1485           0 :   ctx->quic_client->config.sign                       = sign_ed25519;
    1486           0 :   ctx->quic_client->config.sign_ctx                   = ctx;
    1487           0 :   ctx->quic_client->config.alpn[ 0 ]                  = 0x0c;
    1488           0 :   memcpy( ctx->quic_client->config.alpn+1, "alpenglow-v1", 12UL );
    1489           0 :   ctx->quic_client->config.alpn_sz                    = 13UL;
    1490           0 :   ctx->quic_client->config.initial_rx_max_stream_data = 0UL;
    1491             : 
    1492           0 :   ctx->quic_client->cb.quic_ctx         = ctx;
    1493           0 :   ctx->quic_client->cb.conn_hs_complete = quic_client_conn_hs_complete;
    1494           0 :   ctx->quic_client->cb.conn_final       = quic_client_conn_final;
    1495             : 
    1496           0 :   FD_TEST( fd_quic_init( ctx->quic_client ) );
    1497             : 
    1498           0 :   ctx->quic_server = fd_quic_join( fd_quic_new( quic_server, &quic_server_limits ) );
    1499           0 :   FD_TEST( ctx->quic_server );
    1500           0 :   fd_quic_set_aio_net_tx( ctx->quic_server, quic_tx_aio );
    1501             : 
    1502           0 :   ctx->quic_server->config.role                       = FD_QUIC_ROLE_SERVER;
    1503           0 :   ctx->quic_server->config.retry                      = 0;
    1504           0 :   ctx->quic_server->config.idle_timeout               = 5L*1000L*1000L*1000L;
    1505           0 :   ctx->quic_server->config.ack_delay                  = 2L*1000L*1000L;
    1506           0 :   memcpy( ctx->quic_server->config.identity_public_key, ctx->id_key.uc, 32UL );
    1507           0 :   ctx->quic_server->config.sign                       = sign_ed25519;
    1508           0 :   ctx->quic_server->config.sign_ctx                   = ctx;
    1509           0 :   ctx->quic_server->config.alpn[ 0 ]                  = 0x0c;
    1510           0 :   memcpy( ctx->quic_server->config.alpn+1, "alpenglow-v1", 12UL );
    1511           0 :   ctx->quic_server->config.alpn_sz                    = 13UL;
    1512           0 :   ctx->quic_server->config.initial_rx_max_stream_data = 0UL;
    1513           0 :   ctx->quic_server->config.max_datagram_frame_size    = 1280UL;
    1514             : 
    1515           0 :   ctx->quic_server->cb.quic_ctx    = ctx;
    1516           0 :   ctx->quic_server->cb.conn_new    = quic_server_conn_new;
    1517           0 :   ctx->quic_server->cb.conn_final  = quic_server_conn_final;
    1518           0 :   ctx->quic_server->cb.datagram_rx = quic_server_datagram_rx;
    1519             : 
    1520           0 :   FD_TEST( fd_quic_init( ctx->quic_server ) );
    1521           0 : }
    1522             : 
    1523             : static ulong
    1524             : populate_allowed_seccomp( fd_topo_t const *      topo,
    1525             :                           fd_topo_tile_t const * tile,
    1526             :                           ulong                  out_cnt,
    1527           0 :                           struct sock_filter *   out ) {
    1528           0 :   (void)topo; (void)tile;
    1529           0 :   populate_sock_filter_policy_fd_votor_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() );
    1530           0 :   return sock_filter_policy_fd_votor_tile_instr_cnt;
    1531           0 : }
    1532             : 
    1533             : static ulong
    1534             : populate_allowed_fds( fd_topo_t const *      topo,
    1535             :                       fd_topo_tile_t const * tile,
    1536             :                       ulong                  out_fds_cnt,
    1537           0 :                       int *                  out_fds ) {
    1538           0 :   (void)topo; (void)tile;
    1539           0 :   if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    1540             : 
    1541           0 :   ulong out_cnt = 0UL;
    1542           0 :   out_fds[ out_cnt++ ] = 2;
    1543           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    1544           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd();
    1545           0 :   return out_cnt;
    1546           0 : }
    1547             : 
    1548             : static void
    1549           0 : metrics_write( fd_votor_tile_t * ctx ) {
    1550           0 :   FD_MCNT_ENUM_COPY( VOTOR, DATAGRAM_RX, ctx->metrics.datagram_rx );
    1551           0 :   FD_MCNT_ENUM_COPY( VOTOR, VOTE_RX,     ctx->metrics.vote_rx     );
    1552           0 :   FD_MCNT_ENUM_COPY( VOTOR, CERT_RX,     ctx->metrics.cert_rx     );
    1553           0 : }
    1554             : 
    1555           0 : #define STEM_BURST (2UL)
    1556           0 : #define STEM_LAZY  (128L*3000L)
    1557             : 
    1558           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_votor_tile_t
    1559           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_votor_tile_t)
    1560           0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
    1561           0 : #define STEM_CALLBACK_AFTER_CREDIT  after_credit
    1562           0 : #define STEM_CALLBACK_BEFORE_FRAG   before_frag
    1563           0 : #define STEM_CALLBACK_DURING_FRAG   during_frag
    1564           0 : #define STEM_CALLBACK_AFTER_FRAG    after_frag
    1565             : 
    1566             : #include "../../disco/stem/fd_stem.c"
    1567             : 
    1568             : fd_topo_run_tile_t fd_tile_votor = {
    1569             :   .name                     = "votor",
    1570             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    1571             :   .populate_allowed_fds     = populate_allowed_fds,
    1572             :   .scratch_align            = scratch_align,
    1573             :   .scratch_footprint        = scratch_footprint,
    1574             :   .privileged_init          = privileged_init,
    1575             :   .unprivileged_init        = unprivileged_init,
    1576             :   .run                      = stem_run,
    1577             : };

Generated by: LCOV version 1.14