LCOV - code coverage report
Current view: top level - discof/votor - fd_votor_tile.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1967 0.0 %
Date: 2026-10-23 17:13:25 Functions: 0 86 0.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE /* syscall, pwritev2, RENAME_EXCHANGE */
       2             : #include "fd_votor_tile.h"
       3             : #include <linux/futex.h>
       4             : #include "generated/fd_votor_tile_seccomp.h"
       5             : 
       6             : #include "../../choreo/votor/ag_cert_serde.h"
       7             : #include "../../choreo/votor/ag_pool.h"
       8             : #include "../../choreo/votor/ag_slot_state.h"
       9             : #include "../../choreo/votor/ag_vote_history_serde.h"
      10             : #include "../../choreo/votor/ag_vote_serde.h"
      11             : #include "../../choreo/votor/ag_votor.h"
      12             : #include "../../disco/events/generated/fd_event_gen.h"
      13             : #include "../../disco/keyguard/fd_keyguard.h"
      14             : #include "../../disco/keyguard/fd_keyguard_client.h"
      15             : #include "../../disco/keyguard/fd_keyload.h"
      16             : #include "../../disco/keyguard/fd_keyswitch.h"
      17             : #include "../../disco/metrics/fd_metrics.h"
      18             : #include "../../disco/net/fd_net_tile.h"
      19             : #include "../../disco/stem/fd_stem.h"
      20             : #include "../../disco/topo/fd_topo.h"
      21             : #include "../../disco/fd_clock_tile.h"
      22             : #include "../../flamenco/gossip/fd_gossip_message.h"
      23             : #include "../../flamenco/leaders/fd_leaders_base.h"
      24             : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
      25             : #include "../../flamenco/stakes/fd_stake_weight.h"
      26             : #include "../../util/net/fd_net_headers.h"
      27             : #include "../../util/fd_hash32.h"
      28             : #include "../../waltz/quic/fd_quic.h"
      29             : #include "../../waltz/quic/fd_quic_conn.h"
      30             : #include "../../waltz/quic/fd_quic_private.h"
      31             : #include "../replay/fd_replay_tile.h"
      32             : #include "../restore/utils/fd_wfs.h"
      33             : 
      34             : #include <dirent.h>
      35             : #include <errno.h>
      36             : #include <fcntl.h>
      37             : #include <stdio.h>
      38             : #include <unistd.h>
      39             : #include <sys/mman.h>
      40             : #include <sys/stat.h>
      41             : #include <sys/syscall.h>
      42             : #include <sys/uio.h>
      43             : 
      44           0 : #define IN_KIND_EPOCH  (0)
      45           0 : #define IN_KIND_GOSSIP (1)
      46           0 : #define IN_KIND_IPECHO (2)
      47           0 : #define IN_KIND_NET    (3)
      48           0 : #define IN_KIND_REPLAY (4)
      49           0 : #define IN_KIND_SIGN   (5)
      50             : 
      51           0 : #define OUT_IDX_VOTOR (0UL)
      52           0 : #define OUT_IDX_NET   (1UL)
      53             : 
      54           0 : #define QUIC_CLOSE_CODE_INVALID_IDENTITY     (2U) /* agave_votor_transport::error::close_codes::INVALID_IDENTITY */
      55           0 : #define QUIC_CLOSE_CODE_NOT_ADMITTED         (3U) /* agave_votor_transport::error::close_codes::NOT_ADMITTED */
      56           0 : #define QUIC_CLOSE_CODE_BANNED               (4U) /* agave_votor_transport::error::close_codes::BANNED */
      57           0 : #define QUIC_CLOSE_CODE_TOO_MANY_CONNECTIONS (5U) /* agave_votor_transport::error::close_codes::TOO_MANY_CONNECTIONS */
      58             : 
      59           0 : #define QUIC_BAN_TIMEOUT_NS   (10L*1000L*1000L*1000L) /* agave_bls_sigverify::bls_sigverifier::BAN_TIMEOUT */
      60           0 : #define QUIC_K_GRANULARITY_NS (1000000L)              /* 1 ms, RFC 9002 kGranularity, same as FD_QUIC_K_GRANULARITY_NS */
      61             : 
      62           0 : #define QUIC_CLIENT_CONN_BACKOFF_MIN_NS (1L*1000L*1000L*1000L)  /* 1 s */
      63           0 : #define QUIC_CLIENT_CONN_BACKOFF_MAX_NS (1L*1000L*1000L*1000L)  /* 1 s */
      64           0 : #define QUIC_CLIENT_CONN_AHEAD_NS       (10L*1000L*1000L*1000L) /* connect to peers this long before our first ranked epoch, spreading the handshakes out (Agave admits next-epoch voters from 30000 slots out) */
      65             : #define QUIC_CLIENT_CONN_MAX            (AG_VAT_MAX * (3 + 1))  /* one conn to every validator of the prev, curr and next epochs, plus an epoch of evicted conns still closing */
      66             : 
      67           0 : #define QUIC_SERVER_CONN_PER_PEER_MAX (2UL)                                        /* allow up to 2 per peer, in case of failures (zombies, network partition, etc.) */
      68             : #define QUIC_SERVER_CONN_MAX          (AG_VAT_MAX * QUIC_SERVER_CONN_PER_PEER_MAX) /* at most AG_VAT_MAX connections */
      69             : 
      70           0 : #define REWARD_VOTE_MAX        (AG_REWARD_SLOT_DELTA+AG_SLOTS_PER_WINDOW) /* voting slot+REWARD_VOTE_MAX needs ParentReady past slot+8's window, deciding slot's reward */
      71           0 : #define REWARD_VOTE_RTT_MIN_NS (10000000L)                                /* retry a reward vote no sooner than 10ms */
      72           0 : #define REWARD_VOTE_RTT_MAX_NS (200000000L)                               /* retry a reward vote no later than 200ms */
      73           0 : #define REWARD_VOTE_TX_MAX     (16UL)                                     /* sends per reward vote: RTT_MIN doubling to RTT_MAX fits 13 before slot+8's footer seals at 200ms slots */
      74           0 : #define REWARD_REPUBLISH_NS    (50000000L)                                /* republish a reward cert no sooner than 50ms, each publish verifies its aggregates */
      75             : 
      76           0 : #define STANDSTILL_TX_INTERVAL_NS    (50000000L)   /* 20 standstill messages a second, Agave's STANDSTILL_REFRESH_BATCH_SIZE per STANDSTILL_REFRESH_INTERVAL */
      77           0 : #define STANDSTILL_CYCLE_INTERVAL_NS (1000000000L) /* pause between standstill cycles, Agave's STANDSTILL_REFRESH_INTERVAL */
      78             : 
      79             : #define VOTE_TOO_NEW (40UL) /* lookahead at most 40 slots from highest ParentReady, Agave's MAX_VOTE_SLOT_DISTANCE_FROM_PARENT_READY */
      80             : 
      81             : #define SER_MAX (AG_VOTE_SER_MAX>AG_CERT_SER_MAX ? AG_VOTE_SER_MAX : AG_CERT_SER_MAX)
      82             : 
      83             : static fd_quic_limits_t quic_client_limits = {
      84             :   .conn_cnt                    = QUIC_CLIENT_CONN_MAX,
      85             :   .handshake_cnt               = 1024UL,
      86             :   .conn_id_cnt                 = FD_QUIC_MIN_CONN_ID_CNT,
      87             :   .inflight_frame_cnt          = 16UL * QUIC_CLIENT_CONN_MAX,
      88             :   .min_inflight_frame_cnt_conn = 8UL,
      89             : };
      90             : 
      91             : static fd_quic_limits_t quic_server_limits = {
      92             :   .conn_cnt                    = QUIC_SERVER_CONN_MAX,
      93             :   .handshake_cnt               = 1024UL,
      94             :   .conn_id_cnt                 = FD_QUIC_MIN_CONN_ID_CNT,
      95             :   .inflight_frame_cnt          = 64UL * QUIC_SERVER_CONN_MAX,
      96             :   .min_inflight_frame_cnt_conn = 32UL,
      97             : };
      98             : 
      99           0 : #define CONTACT_INFOS_LG_SLOT_CNT (16) /* FD_CONTACT_INFO_TABLE_SIZE keys, fill ratio 0.5 */
     100             : FD_STATIC_ASSERT( (1UL<<CONTACT_INFOS_LG_SLOT_CNT)==2UL*FD_CONTACT_INFO_TABLE_SIZE, contact_infos );
     101             : 
     102             : struct contact_info {
     103             :   fd_pubkey_t id_key;
     104             :   uint        hash;
     105             :   uint        ip4;
     106             :   ushort      port;
     107             :   uchar       removed; /* gossip removed it, kept until its peer is evicted */
     108             : };
     109             : typedef struct contact_info contact_info_t;
     110             : 
     111             : #define MAP_NAME                contact_infos
     112           0 : #define MAP_T                   contact_info_t
     113           0 : #define MAP_KEY                 id_key
     114           0 : #define MAP_KEY_T               fd_pubkey_t
     115           0 : #define MAP_KEY_NULL            ((fd_pubkey_t){ .ul = {0} }) /* no validator identity is the zero pubkey */
     116           0 : #define MAP_KEY_INVAL(k)        (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]))
     117           0 : #define MAP_KEY_EQUAL(k0,k1)    (!memcmp( &(k0), &(k1), sizeof(fd_pubkey_t) ))
     118             : #define MAP_KEY_EQUAL_IS_SLOW   1
     119           0 : #define MAP_KEY_HASH(key,seed)  ((uint)fd_hash32( (key).uc, (seed) ))
     120             : #include "../../util/tmpl/fd_map_dynamic.c"
     121             : 
     122           0 : #define PEERS_LG_SLOT_CNT (13) /* 2*AG_VAT_MAX, fill ratio 0.5 */
     123             : FD_STATIC_ASSERT( (1UL<<PEERS_LG_SLOT_CNT)>=4UL*AG_VAT_MAX, peers );
     124             : 
     125             : struct peer {
     126             :   fd_pubkey_t      id_key;
     127             :   uint             hash;
     128             :   ushort           prev_rank;
     129             :   ushort           curr_rank;
     130             :   ushort           next_rank;
     131             :   fd_quic_conn_t * tx_conn;
     132             :   fd_quic_conn_t * rx_conn[ QUIC_SERVER_CONN_PER_PEER_MAX ];
     133             :   long             ban_ts;
     134             :   long             conn_ts;
     135             :   long             conn_backoff;
     136             :   int              reconn_pending; /* 1 if the peer has an entry in reconn_prq */
     137             : };
     138             : typedef struct peer peer_t;
     139             : 
     140             : #define MAP_NAME                peers
     141           0 : #define MAP_T                   peer_t
     142           0 : #define MAP_KEY                 id_key
     143           0 : #define MAP_KEY_T               fd_pubkey_t
     144           0 : #define MAP_KEY_NULL            ((fd_pubkey_t){ .ul = {0} }) /* no validator identity is the zero pubkey */
     145           0 : #define MAP_KEY_INVAL(k)        (!((k).ul[0]|(k).ul[1]|(k).ul[2]|(k).ul[3]))
     146           0 : #define MAP_KEY_EQUAL(k0,k1)    (!memcmp( &(k0), &(k1), sizeof(fd_pubkey_t) ))
     147             : #define MAP_KEY_EQUAL_IS_SLOW   1
     148           0 : #define MAP_KEY_HASH(key,seed)  ((uint)fd_hash32( (key).uc, (seed) ))
     149             : #include "../../util/tmpl/fd_map_dynamic.c"
     150             : 
     151             : struct reconn {
     152             :   long        timeout;
     153             :   fd_pubkey_t id_key;
     154             : };
     155             : typedef struct reconn reconn_t;
     156             : 
     157             : #define PRQ_NAME reconn_prq
     158           0 : #define PRQ_T    reconn_t
     159             : #include "../../util/tmpl/fd_prq.c"
     160             : 
     161           0 : #define RECONN_MAX (1UL<<PEERS_LG_SLOT_CNT)
     162             : 
     163             : /* reward_vote specially tracks that our vote for slot is ACK'd by the
     164             :    leader for the reward slot (slot + 8). */
     165             : 
     166             : struct reward_vote {
     167             :   ulong            slot; /* ULONG_MAX if free */
     168             :   long             retry_ts;
     169             :   long             backoff;
     170             :   ulong            tx_cnt;
     171             :   fd_quic_conn_t * conn; /* conn the pkt_nums were sent on */
     172             :   ulong            pkt_num[ REWARD_VOTE_TX_MAX ]; /* every send on conn, an ACK of any acks the vote */
     173             :   ag_vote_t        vote;
     174             : };
     175             : typedef struct reward_vote reward_vote_t;
     176             : 
     177             : /* reward_cert tracks whether we've obtained a better reward cert for a
     178             :    given slot, marking it dirty to be republished. */
     179             : 
     180             : struct reward_cert {
     181             :   ulong slot; /* ULONG_MAX if free */
     182             :   int   dirty;
     183             :   long  publish_ts;
     184             : };
     185             : typedef struct reward_cert reward_cert_t;
     186             : 
     187           0 : #define AUTH_VTR_LG_SLOT_CNT (5) /* identity plus 16 authorized voters, fill ratio 0.53 */
     188             : 
     189             : struct auth_vtr_key {
     190             :   uchar uc[ FD_BLS_PUB_COMPRESSED_SZ ];
     191             : };
     192             : typedef struct auth_vtr_key auth_vtr_key_t;
     193             : 
     194             : struct auth_vtr {
     195             :   auth_vtr_key_t bls_key;   /* derived from the identity or an authorized voter */
     196             :   ulong          paths_idx; /* index in authorized voter paths, ULONG_MAX for the identity */
     197             : };
     198             : typedef struct auth_vtr auth_vtr_t;
     199             : 
     200             : #define MAP_NAME              auth_vtr
     201           0 : #define MAP_T                 auth_vtr_t
     202           0 : #define MAP_LG_SLOT_CNT       AUTH_VTR_LG_SLOT_CNT
     203           0 : #define MAP_KEY               bls_key
     204           0 : #define MAP_KEY_T             auth_vtr_key_t
     205           0 : #define MAP_KEY_NULL          ((auth_vtr_key_t){ .uc = {0} }) /* no compressed BLS public key is all zero */
     206           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( &(k0), &(k1), sizeof(auth_vtr_key_t) ))
     207           0 : #define MAP_KEY_INVAL(k)      (MAP_KEY_EQUAL( (k), MAP_KEY_NULL ))
     208             : #define MAP_KEY_EQUAL_IS_SLOW 1
     209           0 : #define MAP_KEY_HASH(key)     ((uint)fd_hash( 0UL, &(key), sizeof(auth_vtr_key_t) ))
     210             : #define MAP_MEMOIZE           0
     211             : #include "../../util/tmpl/fd_map.c"
     212             : 
     213             : struct sort_voter {
     214             :   uchar const * bls;
     215             :   uchar const * id;
     216             :   ulong         stake;
     217             :   fd_bls_pub_t  pub;
     218             :   ulong         idx;
     219             :   int           dup;
     220             : };
     221             : typedef struct sort_voter sort_voter_t;
     222             : 
     223             : #define SORT_NAME        sort_voter_bls
     224           0 : #define SORT_KEY_T       sort_voter_t
     225           0 : #define SORT_BEFORE(a,b) ( memcmp( (a).bls, (b).bls, FD_BLS_PUB_COMPRESSED_SZ )<0 )
     226             : #include "../../util/tmpl/fd_sort.c"
     227             : 
     228             : #define SORT_NAME        sort_voter_id
     229           0 : #define SORT_KEY_T       sort_voter_t
     230           0 : #define SORT_BEFORE(a,b) ( memcmp( (a).id, (b).id, sizeof(fd_pubkey_t) )<0 )
     231             : #include "../../util/tmpl/fd_sort.c"
     232             : 
     233             : /* known_key is a validated point from an earlier epoch's ranking, so
     234             :    an unchanged key skips decompression and the subgroup check. */
     235             : 
     236             : struct known_key {
     237             :   uchar const *        bls;
     238             :   fd_bls_pub_t const * pub;
     239             : };
     240             : typedef struct known_key known_key_t;
     241             : 
     242             : #define SORT_NAME        sort_known_key
     243           0 : #define SORT_KEY_T       known_key_t
     244           0 : #define SORT_BEFORE(a,b) ( memcmp( (a).bls, (b).bls, FD_BLS_PUB_COMPRESSED_SZ )<0 )
     245             : #include "../../util/tmpl/fd_sort.c"
     246             : 
     247             : #define SORT_NAME        sort_voter_stake
     248           0 : #define SORT_KEY_T       sort_voter_t
     249           0 : #define SORT_BEFORE(a,b) ( (a).stake> (b).stake ||                                  \
     250           0 :                          ( (a).stake==(b).stake &&                                  \
     251           0 :                            memcmp( (a).bls, (b).bls, FD_BLS_PUB_COMPRESSED_SZ )<0 ) )
     252             : #include "../../util/tmpl/fd_sort.c"
     253             : 
     254             : struct fd_votor_tile {
     255             : 
     256             :   /* Signing */
     257             : 
     258             :   fd_pubkey_t          id_key;
     259             :   fd_pubkey_t          id_key_bak;    /* the identity whose .bak is the vote history spare */
     260             :   fd_keyswitch_t *     id_keyswitch;
     261             :   int                  halt_signing;  /* switching identity, see during_housekeeping */
     262             :   ulong                replay_in_seq; /* seq after the last replay_slot frag consumed */
     263             :   auth_vtr_t *         auth_vtr;
     264             :   ulong                auth_vtr_path_cnt;
     265             :   fd_keyswitch_t *     auth_vtr_keyswitch;
     266             :   fd_keyguard_client_t keyguard_client[1];
     267             : 
     268             :   /* Vote history */
     269             : 
     270             :   ulong       vote_history_slot;     /* first slot votor may vote in, from the vote history file, 0 if none */
     271             :   int         vote_history_bak_full; /* the spare holds id_key_bak's vote history */
     272             :   int         vote_history_pending;  /* the next write names the new identity's file, which a switch leaves unnamed */
     273             :   int         vote_history_fd;
     274             :   int         vote_history_fd_swp;   /* swap file, atomic rename */
     275             :   int         vote_history_fd_bak;   /* spare, the new identity's at a switch */
     276             :   int         vote_history_fd_dir;
     277             : 
     278             :   /* Initialization */
     279             : 
     280             :   int           init;
     281             :   ag_block_id_t boot_block_id;
     282             :   ulong         slot_max; /* max_live_slots */
     283             : 
     284             :   /* Wait-for-supermajority (see fd_wfs.h). */
     285             : 
     286             :   ulong  wfs_slot;
     287             :   int    wfs_hash_is_zero;
     288             :   ushort wfs_shred_version;
     289             :   int    wfs_signalled;  /* latch: REPLAY_SIG_WFS_DONE seen */
     290             : 
     291             :   /* Cluster metadata */
     292             : 
     293             :   ushort                     shred_version;
     294             :   long                       ns_per_slot;
     295             :   ulong                      slots_per_epoch;
     296             :   ag_epoch_info_t *          prev_epoch_info;
     297             :   ulong                      prev_epoch_slot;
     298             :   ag_epoch_info_t *          curr_epoch_info;
     299             :   ulong                      curr_epoch_slot;
     300             :   ag_epoch_info_t *          next_epoch_info;
     301             :   ulong                      next_epoch_slot;
     302             :   fd_multi_epoch_leaders_t * mleaders;
     303             :   ulong                      next_leader_slot;
     304             :   ulong                      highest_vote_or_lead_slot; /* voting can be out of order, so track a hi-watermark of every vote votor decided and window it led */
     305             :   ulong                      highest_parent_ready_slot;
     306             :   ulong                      highest_certed_final_slot; /* highest slot whose final cert was published paired with its notar cert */
     307             :   ulong                      catchup_slot;              /* highest slot past the pool window with a verified cert, 0 if none */
     308             :   ulong                      catchup_final_slot;        /* same, for a final or fast-final cert */
     309             :   ulong                      catchup_vote_slot;         /* first slot worth a vote or cert while catching up: slot_max behind catchup_slot, or the first whose reward block follows catchup_final_slot */
     310             : 
     311             :   /* Alpenglow data structures */
     312             : 
     313             :   ag_pool_t *  pool;
     314             :   ag_votor_t * votor;
     315             : 
     316             :   /* Standstill */
     317             : 
     318             :   ulong                     standstill_slot; /* the finalized slot when standstill_ts was last reset */
     319             :   long                      standstill_ts;   /* LONG_MAX until votor runs, then AG_DELTA_STANDSTILL_NS after standstill_slot changes, then every STANDSTILL_TX_INTERVAL_NS */
     320             :   ag_pool_standstill_iter_t standstill_iter[1];
     321             : 
     322             :   /* Networking */
     323             : 
     324             :   contact_info_t *   contact_infos;
     325             :   peer_t *           peers;
     326             :   fd_pubkey_t        client_peer_id_keys[ QUIC_CLIENT_CONN_MAX ];
     327             :   fd_pubkey_t        server_peer_id_keys[ QUIC_SERVER_CONN_MAX ];
     328             :   fd_net_rx_bounds_t net_in_bounds[ 32 ];
     329             :   uchar              net_buf[ FD_NET_MTU ];
     330             :   fd_quic_t *        quic_client;
     331             :   fd_quic_t *        quic_server;
     332             :   fd_clock_tile_t    clock[1];
     333             :   fd_aio_t           quic_tx_aio[ 1 ];
     334             :   ushort             quic_client_listen_port;
     335             :   ushort             quic_server_listen_port;
     336             :   uint               src_ip_addr;
     337             :   fd_ip4_udp_hdrs_t  hdr[ 1 ];
     338             :   ushort             net_id;
     339             :   reconn_t *         reconn_prq;
     340             :   ulong              conn_ahead_slot; /* next_epoch_slot we last queued connects ahead of */
     341             :   reward_vote_t      reward_votes[ REWARD_VOTE_MAX ]; /* our reward votes awaiting an ACK from the leader for the reward slot.  circ buf indexed by slot%REWARD_VOTE_MAX */
     342             :   reward_cert_t      reward_certs[ AG_SLOTS_PER_WINDOW ]; /* reward certs published for our leader window.  circ buf indexed by slot%AG_SLOTS_PER_WINDOW */
     343             : 
     344             :   /* Links */
     345             : 
     346             :   int in_kind[32];
     347             :   struct {
     348             :     fd_wksp_t * mem;
     349             :   } in[ 32 ];
     350             : 
     351             :   void * net_out_mem;
     352             :   ulong  net_out_chunk0;
     353             :   ulong  net_out_wmark;
     354             :   ulong  net_out_chunk;
     355             : 
     356             :   void * votor_out_mem;
     357             :   ulong  votor_out_chunk0;
     358             :   ulong  votor_out_wmark;
     359             :   ulong  votor_out_chunk;
     360             : 
     361             :   ulong                                        net_tx_cnt;
     362             :   struct { ulong chunk; ulong sz; ulong sig; } net_tx[ FD_VOTOR_NET_BURST ];
     363             : 
     364             :   fd_stem_context_t * stem; /* for publishing from the quic callbacks */
     365             : 
     366             :   /* Scratch */
     367             : 
     368             :   struct {
     369             :     union {
     370             :       ag_vote_t       vote;
     371             :       ag_cert_t       cert;
     372             :       ag_pool_event_t pool_event;
     373             :       ag_block_id_t   repair_event;
     374             :     };
     375             :     ag_epoch_info_t prev_epoch_info;
     376             :     ag_epoch_info_t curr_epoch_info;
     377             :     ag_epoch_info_t next_epoch_info;
     378             :     uchar           ser[ SER_MAX ];
     379             :     fd_bls_set_t    bad[ fd_bls_set_word_cnt ];
     380             :     uchar           vote_history[ AG_VOTE_HISTORY_SER_SZ ];
     381             :   } scratch;
     382             : 
     383             :   /* Metrics */
     384             : 
     385             :   struct {
     386             :     ulong datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_CNT ];
     387             :     ulong vote_rx    [ FD_METRICS_ENUM_VOTE_RX_RESULT_CNT     ];
     388             :     ulong cert_rx    [ FD_METRICS_ENUM_CERT_RX_RESULT_CNT     ];
     389             :     ulong bls_verify;
     390             :     ulong vote_history_write;
     391             :     ulong leader_window_superseded;
     392             :     ulong cert_tx_unstaked;
     393             :   } metrics;
     394             : };
     395             : typedef struct fd_votor_tile fd_votor_tile_t;
     396             : 
     397             : static void
     398             : report_alpenglow_vote( fd_votor_tile_t * ctx,
     399             :                        fd_quic_conn_t *  conn,
     400             :                        ag_vote_t const * vote,
     401             :                        uchar             tag,
     402             :                        int               result,
     403             :                        uchar             quorum_reached,
     404             :                        int               reason,
     405             :                        long              aggregation_start_time,
     406             :                        long              broadcast_start_time,
     407           0 :                        peer_t const *    reward_leader ) {
     408           0 :   fd_event_alpenglow_vote_t ev;
     409           0 :   fd_memset( &ev, 0, FD_EVENT_ALPENGLOW_VOTE_PREFIX_SZ );
     410           0 :   ev.kind       = FD_EVENT_ALPENGLOW_VOTE_KIND_NOTAR+tag-AG_VOTE_SERDE_TAG_NOTAR;
     411           0 :   ev.voter_rank = USHORT_MAX;
     412             : 
     413           0 :   ag_epoch_info_t const * epoch_info = NULL;
     414           0 :   if( FD_LIKELY( vote ) ) {
     415           0 :     ev.slot       = ag_vote_slot( vote );
     416           0 :     ev.voter_rank = ag_vote_rank( vote );
     417           0 :     epoch_info    = fd_ptr_if( ev.slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( ev.slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
     418           0 :     uchar const * block_hash = ag_vote_block_hash( vote );
     419           0 :     if( FD_LIKELY( block_hash                                            ) ) memcpy( ev.block_id,       block_hash,                                  sizeof(ag_block_hash_t) );
     420           0 :     if( FD_LIKELY( epoch_info && ev.voter_rank<epoch_info->validator_cnt ) ) memcpy( ev.voter_identity, epoch_info->validators[ ev.voter_rank ].id_key, sizeof(ag_id_key_t)     );
     421           0 :   }
     422             : 
     423           0 :   fd_pubkey_t const * id_key = conn ? fd_quic_conn_get_context( conn ) : &ctx->id_key;
     424           0 :   FD_STORE( ushort, ev.received_from_ip+10, (ushort)0xffff );
     425           0 :   FD_STORE( uint,   ev.received_from_ip+12, conn ? conn->peer[ 0 ].ip_addr : ctx->src_ip_addr );
     426           0 :   if( FD_LIKELY( id_key ) ) memcpy( ev.received_from_identity, id_key->uc, sizeof(fd_pubkey_t) );
     427             : 
     428           0 :   ev.our_vote                           = !conn;
     429           0 :   ev.reason                            = reason;
     430           0 :   ev.processing_result                  = result;
     431           0 :   ev.quorum_reached_safe_to_notar       = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_SAFE_TO_NOTAR  );
     432           0 :   ev.quorum_reached_safe_to_skip        = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_SAFE_TO_SKIP   );
     433           0 :   ev.quorum_reached_final_cert          = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_FINAL          );
     434           0 :   ev.quorum_reached_fast_final_cert     = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_FAST_FINAL     );
     435           0 :   ev.quorum_reached_notar_cert          = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_NOTAR          );
     436           0 :   ev.quorum_reached_notar_fallback_cert = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_NOTAR_FALLBACK );
     437           0 :   ev.quorum_reached_skip_cert           = fd_uchar_extract_bit( quorum_reached, AG_POOL_QUORUM_REACHED_SKIP           );
     438           0 :   ev.aggregation_start_time             = (ulong)fd_long_if( result==FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_ACCEPTED, aggregation_start_time, 0L );
     439           0 :   ev.broadcast_start_time               = (ulong)broadcast_start_time;
     440           0 :   ev.done_time                          = (ulong)fd_clock_tile_now( ctx->clock );
     441             : 
     442           0 :   if( FD_UNLIKELY( reason==FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD_NO_CONNECTION ) ) {
     443           0 :     ulong                   reward_slot = ev.slot+AG_REWARD_SLOT_DELTA;
     444           0 :     ag_epoch_info_t const * reward_info = fd_ptr_if( reward_slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( reward_slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
     445           0 :     ulong                   cnt         = reward_info ? reward_info->validator_cnt : 0UL;
     446           0 :     ushort                  rank        = fd_ushort_if( reward_slot>=ctx->next_epoch_slot, reward_leader->next_rank, fd_ushort_if( reward_slot>=ctx->curr_epoch_slot, reward_leader->curr_rank, reward_leader->prev_rank ) );
     447           0 :     contact_info_t const *  ci          = contact_infos_query( ctx->contact_infos, reward_leader->id_key, NULL );
     448           0 :     fd_memset( ev.broadcast_to, 0, cnt*sizeof(fd_event_alpenglow_vote_broadcast_to_t) );
     449           0 :     if( FD_LIKELY( rank<cnt ) ) memcpy( ev.broadcast_to[ rank ].identity, reward_leader->id_key.uc, sizeof(fd_pubkey_t) );
     450           0 :     if( FD_LIKELY( rank<cnt && ci ) ) {
     451           0 :       FD_STORE( ushort, ev.broadcast_to[ rank ].ip+10, (ushort)0xffff );
     452           0 :       FD_STORE( uint,   ev.broadcast_to[ rank ].ip+12, ci->ip4 );
     453           0 :       ev.broadcast_to[ rank ].port = ci->port;
     454           0 :     }
     455           0 :     ev.broadcast_to_cnt = cnt;
     456           0 :   }
     457             : 
     458           0 :   if( FD_UNLIKELY( !conn && reason!=FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD_ACKED && reason!=FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD_NO_CONNECTION ) ) {
     459           0 :     ulong                   rank_slot = fd_ulong_if( reason==FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD, ev.slot+AG_REWARD_SLOT_DELTA, ev.slot );
     460           0 :     ag_epoch_info_t const * rank_info = fd_ptr_if( rank_slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( rank_slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
     461           0 :     ulong                   cnt       = rank_info ? rank_info->validator_cnt : 0UL;
     462           0 :     fd_memset( ev.broadcast_to, 0, cnt*sizeof(fd_event_alpenglow_vote_broadcast_to_t) );
     463           0 :     for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
     464           0 :       peer_t const * peer = &ctx->peers[ slot ];
     465           0 :       if( FD_LIKELY(    peers_key_inval( peer->id_key )
     466           0 :                      || !peer->tx_conn
     467           0 :                      || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE
     468           0 :                      || (peer==reward_leader)!=(reason==FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD) ) ) continue;
     469           0 :       ushort rank = fd_ushort_if( rank_slot>=ctx->next_epoch_slot, peer->next_rank, fd_ushort_if( rank_slot>=ctx->curr_epoch_slot, peer->curr_rank, peer->prev_rank ) );
     470           0 :       if( FD_UNLIKELY( rank>=cnt ) ) continue;
     471           0 :       memcpy( ev.broadcast_to[ rank ].identity, peer->id_key.uc, sizeof(fd_pubkey_t) );
     472           0 :       FD_STORE( ushort, ev.broadcast_to[ rank ].ip+10, (ushort)0xffff );
     473           0 :       FD_STORE( uint,   ev.broadcast_to[ rank ].ip+12, peer->tx_conn->peer[ 0 ].ip_addr );
     474           0 :       ev.broadcast_to[ rank ].port = peer->tx_conn->peer[ 0 ].udp_port;
     475           0 :     }
     476           0 :     ev.broadcast_to_cnt = cnt;
     477           0 :   }
     478             : 
     479           0 :   fd_event_report_alpenglow_vote( &ev );
     480           0 : }
     481             : 
     482             : static void
     483             : report_alpenglow_cert( fd_votor_tile_t * ctx,
     484             :                        fd_quic_conn_t *  conn,
     485             :                        ag_cert_t const * cert,
     486             :                        uchar             tag,
     487             :                        int               result,
     488             :                        long              verify_start_time,
     489           0 :                        long              broadcast_start_time ) {
     490           0 :   fd_event_alpenglow_cert_t ev;
     491           0 :   fd_memset( &ev, 0, FD_EVENT_ALPENGLOW_CERT_PREFIX_SZ );
     492           0 :   ev.kind = FD_EVENT_ALPENGLOW_CERT_KIND_FINAL+tag-AG_CERT_SERDE_TAG_FINAL;
     493             : 
     494           0 :   if( FD_LIKELY( cert ) ) {
     495           0 :     ev.slot = ag_cert_slot( cert );
     496           0 :     uchar const * block_hash = ag_cert_block_hash( cert );
     497           0 :     if( FD_LIKELY( block_hash ) ) memcpy( ev.block_id, block_hash, sizeof(ag_block_hash_t) );
     498           0 :     fd_bls_agg_t const * agg;
     499           0 :     fd_bls_agg_t const * agg2 = NULL;
     500           0 :     switch( cert->kind ) {
     501           0 :     case AG_CERT_KIND_FINAL:          agg = &cert->final.agg;                                                                 break;
     502           0 :     case AG_CERT_KIND_FAST_FINAL:     agg = &cert->fast_final.agg;                                                            break;
     503           0 :     case AG_CERT_KIND_NOTAR:          agg = &cert->notar.agg;                                                                 break;
     504           0 :     case AG_CERT_KIND_NOTAR_FALLBACK: agg = &cert->notar_fallback.agg_notar; agg2 = &cert->notar_fallback.agg_notar_fallback; break;
     505           0 :     case AG_CERT_KIND_SKIP:           agg = &cert->skip.agg_skip;            agg2 = &cert->skip.agg_skip_fallback;            break;
     506           0 :     default:                          FD_LOG_CRIT(( "unreachable" ));
     507           0 :     }
     508           0 :     for( ulong rank = fd_bls_set_const_iter_init( agg->set );
     509           0 :                      !fd_bls_set_const_iter_done( rank );
     510           0 :                rank = fd_bls_set_const_iter_next( agg->set, rank ) ) {
     511           0 :       ev.voters[ rank>>3 ] = (uchar)( ev.voters[ rank>>3 ] | (1U<<(rank&7UL)) );
     512           0 :       ev.voters_len        = (rank>>3)+1UL;
     513           0 :     }
     514           0 :     if( FD_UNLIKELY( agg2 ) ) {
     515           0 :       for( ulong rank = fd_bls_set_const_iter_init( agg2->set );
     516           0 :                        !fd_bls_set_const_iter_done( rank );
     517           0 :                  rank = fd_bls_set_const_iter_next( agg2->set, rank ) ) {
     518           0 :         ev.fallback_voters[ rank>>3 ] = (uchar)( ev.fallback_voters[ rank>>3 ] | (1U<<(rank&7UL)) );
     519           0 :         ev.fallback_voters_len        = (rank>>3)+1UL;
     520           0 :       }
     521           0 :     }
     522           0 :   }
     523             : 
     524           0 :   fd_pubkey_t const * id_key = conn ? fd_quic_conn_get_context( conn ) : &ctx->id_key;
     525           0 :   FD_STORE( ushort, ev.relayer_ip+10, (ushort)0xffff );
     526           0 :   FD_STORE( uint,   ev.relayer_ip+12, conn ? conn->peer[ 0 ].ip_addr : ctx->src_ip_addr );
     527           0 :   if( FD_LIKELY( id_key ) ) memcpy( ev.relayer_identity, id_key->uc, sizeof(fd_pubkey_t) );
     528             : 
     529           0 :   ev.our_cert             = !conn;
     530           0 :   ev.processing_result    = result;
     531           0 :   ev.verify_start_time    = (ulong)verify_start_time;
     532           0 :   ev.broadcast_start_time = (ulong)broadcast_start_time;
     533           0 :   ev.done_time            = (ulong)fd_clock_tile_now( ctx->clock );
     534             : 
     535           0 :   if( FD_UNLIKELY( !conn ) ) {
     536           0 :     ag_epoch_info_t const * epoch_info = fd_ptr_if( ev.slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( ev.slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
     537           0 :     ulong                   cnt        = epoch_info ? epoch_info->validator_cnt : 0UL;
     538           0 :     fd_memset( ev.broadcast_to, 0, cnt*sizeof(fd_event_alpenglow_cert_broadcast_to_t) );
     539           0 :     for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
     540           0 :       peer_t const * peer = &ctx->peers[ slot ];
     541           0 :       if( FD_LIKELY( peers_key_inval( peer->id_key ) || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) continue;
     542           0 :       ushort rank = fd_ushort_if( ev.slot>=ctx->next_epoch_slot, peer->next_rank, fd_ushort_if( ev.slot>=ctx->curr_epoch_slot, peer->curr_rank, peer->prev_rank ) );
     543           0 :       if( FD_UNLIKELY( rank>=cnt ) ) continue;
     544           0 :       memcpy( ev.broadcast_to[ rank ].identity, peer->id_key.uc, sizeof(fd_pubkey_t) );
     545           0 :       FD_STORE( ushort, ev.broadcast_to[ rank ].ip+10, (ushort)0xffff );
     546           0 :       FD_STORE( uint,   ev.broadcast_to[ rank ].ip+12, peer->tx_conn->peer[ 0 ].ip_addr );
     547           0 :       ev.broadcast_to[ rank ].port = peer->tx_conn->peer[ 0 ].udp_port;
     548           0 :     }
     549           0 :     ev.broadcast_to_cnt = cnt;
     550           0 :   }
     551             : 
     552           0 :   fd_event_report_alpenglow_cert( &ev );
     553           0 : }
     554             : 
     555             : static void
     556             : ban_peer( fd_votor_tile_t * ctx,
     557             :           peer_t *          peer,
     558           0 :           long              now ) {
     559           0 :   FD_BASE58_ENCODE_32_BYTES( peer->id_key.uc, id_key_b58 );
     560           0 :   FD_LOG_WARNING(( "banning peer %s", id_key_b58 ));
     561           0 :   for( ulong i=0UL; i<QUIC_SERVER_CONN_PER_PEER_MAX; i++ ) {
     562           0 :     if( FD_UNLIKELY( !peer->rx_conn[ i ] ) ) continue;
     563           0 :     fd_quic_conn_set_context( peer->rx_conn[ i ], NULL );
     564           0 :     fd_quic_conn_close( peer->rx_conn[ i ], QUIC_CLOSE_CODE_BANNED );
     565           0 :     peer->rx_conn[ i ] = NULL;
     566           0 :   }
     567           0 :   if( FD_LIKELY( peer->tx_conn ) ) {
     568           0 :     fd_quic_conn_set_context( peer->tx_conn, NULL );
     569           0 :     fd_quic_conn_close( peer->tx_conn, QUIC_CLOSE_CODE_BANNED );
     570           0 :     peer->tx_conn = NULL;
     571           0 :   }
     572           0 :   peer->ban_ts = now;
     573           0 :   if( FD_LIKELY( !peer->reconn_pending ) ) { /* redial outbound when the ban lapses */
     574           0 :     reconn_t reconn = { .timeout = now+QUIC_BAN_TIMEOUT_NS, .id_key = peer->id_key };
     575           0 :     reconn_prq_insert( ctx->reconn_prq, &reconn );
     576           0 :     peer->reconn_pending = 1;
     577           0 :   }
     578           0 : }
     579             : 
     580             : static void
     581             : ban_bad_ranks( fd_votor_tile_t *    ctx,
     582             :                fd_bls_set_t const * bad,
     583           0 :                ulong                slot_as_of ) {
     584           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 ) );
     585           0 :   if( FD_UNLIKELY( !epoch_info ) ) return;
     586           0 :   long now = fd_clock_tile_now( ctx->clock );
     587           0 :   for( ulong rank = fd_bls_set_const_iter_init( bad );
     588           0 :                    !fd_bls_set_const_iter_done( rank );
     589           0 :              rank = fd_bls_set_const_iter_next( bad, rank ) ) {
     590           0 :     fd_pubkey_t id_key; memcpy( id_key.uc, epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
     591           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
     592           0 :     if( FD_UNLIKELY( !peer || now<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) continue;
     593           0 :     ban_peer( ctx, peer, now );
     594           0 :   }
     595           0 : }
     596             : 
     597             : static void
     598             : publish_reward_cert( fd_votor_tile_t *   ctx,
     599             :                      fd_stem_context_t * stem,
     600           0 :                      ulong               reward_slot ) {
     601           0 :   fd_votor_msg_t *    chunk  = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
     602           0 :   fd_votor_reward_t * reward = &chunk->reward;
     603           0 :   ctx->reward_certs[ reward_slot%AG_SLOTS_PER_WINDOW ] = (reward_cert_t){ .slot = reward_slot, .publish_ts = fd_clock_tile_now( ctx->clock ) };
     604             : 
     605           0 :   reward->slot     = reward_slot;
     606           0 :   reward->block_id = (fd_hash_t){ 0 };
     607           0 :   fd_bls_agg_null( &reward->agg_notar );
     608           0 :   fd_bls_agg_null( &reward->agg_skip );
     609             : 
     610           0 :   ag_slot_state_t const * state = ag_pool_slot_state( ctx->pool, reward_slot );
     611           0 :   if( FD_UNLIKELY( !state ) ) { /* no notar or skip votes for this slot: send a frag with empty reward certs */
     612           0 :     fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_REWARD, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
     613           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 );
     614           0 :     return;
     615           0 :   }
     616           0 :   ag_slot_states_t const * states     = ag_pool_slot_states( ctx->pool );
     617           0 :   ag_epoch_info_t const *  epoch_info = fd_ptr_if( reward_slot>=states->next_epoch_slot, states->next_epoch_info, fd_ptr_if( reward_slot>=states->curr_epoch_slot, states->curr_epoch_info, states->prev_epoch_info ) );
     618           0 :   ag_slot_votes_t const *  votes      = &state->votes;
     619           0 :   fd_bls_sig_t             sigs[ AG_VAT_MAX ];
     620             : 
     621           0 :   uchar msg[ AG_VOTE_SIGNING_SER_MAX ];
     622           0 :   ulong msg_sz;
     623           0 :   int   err;
     624             : 
     625           0 :   uchar const *        hash  = votes->top_notar_hash;
     626           0 :   fd_bls_agg_t const * notar = ag_slot_state_notar_reward_agg( state, states, &reward->agg_notar );
     627           0 :   if( FD_LIKELY( notar ) ) {
     628           0 :     ag_slot_state_sigs( state, states, AG_VOTE_KIND_NOTAR, hash, reward->agg_notar.set, sigs );
     629           0 :     msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_NOTAR, reward_slot, hash, ctx->shred_version, msg );
     630           0 :     ctx->metrics.bls_verify++;
     631           0 :     err    = fd_bls_agg_verify_subtract( &reward->agg_notar, msg, msg_sz, epoch_info->pubkeys, sigs, ctx->scratch.bad );
     632           0 :     ban_bad_ranks( ctx, ctx->scratch.bad, reward_slot );
     633           0 :     if( FD_UNLIKELY( err ) ) fd_bls_agg_null( &reward->agg_notar );
     634           0 :     else                     memcpy( reward->block_id.uc, hash, sizeof(fd_hash_t) );
     635           0 :   }
     636             : 
     637           0 :   fd_bls_agg_t const * skip = ag_slot_state_skip_reward_agg( state );
     638           0 :   if( FD_LIKELY( skip ) ) {
     639           0 :     reward->agg_skip = *skip;
     640           0 :     ag_slot_state_sigs( state, states, AG_VOTE_KIND_SKIP, NULL, reward->agg_skip.set, sigs );
     641           0 :     msg_sz = ag_vote_signing_ser( AG_VOTE_KIND_SKIP, reward_slot, NULL, ctx->shred_version, msg );
     642           0 :     ctx->metrics.bls_verify++;
     643           0 :     err    = fd_bls_agg_verify_subtract( &reward->agg_skip, msg, msg_sz, epoch_info->pubkeys, sigs, ctx->scratch.bad );
     644           0 :     ban_bad_ranks( ctx, ctx->scratch.bad, reward_slot );
     645           0 :     if( FD_UNLIKELY( err ) ) fd_bls_agg_null( &reward->agg_skip );
     646           0 :   }
     647             : 
     648           0 :   fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_REWARD, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
     649           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 );
     650           0 : }
     651             : 
     652             : static peer_t const *
     653             : query_reward_leader( fd_votor_tile_t const * ctx,
     654           0 :                      reward_vote_t const *   rv ) {
     655           0 :   fd_pubkey_t const * leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, rv->slot+AG_REWARD_SLOT_DELTA );
     656           0 :   peer_t const *      peer   = leader ? peers_query( ctx->peers, *leader, NULL ) : NULL;
     657           0 :   if( FD_UNLIKELY( !peer || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) return NULL;
     658           0 :   return peer;
     659           0 : }
     660             : 
     661             : static void
     662             : quic_client_connect( fd_votor_tile_t *      ctx,
     663             :                      peer_t *               peer,
     664             :                      contact_info_t const * ci,
     665           0 :                      long                   now ) {
     666           0 :   if( FD_UNLIKELY( ctx->halt_signing ) ) return; /* halted for an identity switch, and the TLS handshake must sign */
     667           0 :   peer_t const * self = peers_query( ctx->peers, ctx->id_key, NULL );
     668           0 :   if( FD_UNLIKELY( !self || peer==self ) ) return;
     669           0 :   if( FD_UNLIKELY( self->curr_rank==USHORT_MAX ) ) { /* not voting yet: only connect ahead, within QUIC_CLIENT_CONN_AHEAD_NS of our first ranked epoch */
     670           0 :     ulong root = ag_pool_finalized_slot( ctx->pool );
     671           0 :     if( FD_UNLIKELY( self->next_rank==USHORT_MAX || root==ULONG_MAX )                                                     ) return;
     672           0 :     if( FD_UNLIKELY( root<ctx->next_epoch_slot && (long)( ctx->next_epoch_slot-root )*ctx->ns_per_slot>QUIC_CLIENT_CONN_AHEAD_NS ) ) return;
     673           0 :   }
     674           0 :   if( FD_UNLIKELY( peer->prev_rank==USHORT_MAX && peer->curr_rank==USHORT_MAX && peer->next_rank==USHORT_MAX ) ) return;
     675           0 :   if( FD_UNLIKELY( peer->tx_conn ) ) return;
     676           0 :   if( FD_UNLIKELY( now<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) { /* redial when the ban lapses, nothing else would */
     677           0 :     if( FD_LIKELY( !peer->reconn_pending ) ) {
     678           0 :       reconn_t reconn = { .timeout = peer->ban_ts+QUIC_BAN_TIMEOUT_NS, .id_key = peer->id_key };
     679           0 :       reconn_prq_insert( ctx->reconn_prq, &reconn );
     680           0 :       peer->reconn_pending = 1;
     681           0 :     }
     682           0 :     return;
     683           0 :   }
     684             : 
     685           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 );
     686           0 :   if( FD_UNLIKELY( !conn ) ) {
     687           0 :     if( FD_LIKELY( !peer->reconn_pending ) ) {
     688           0 :       reconn_t reconn = { .timeout = now+QUIC_CLIENT_CONN_BACKOFF_MIN_NS, .id_key = peer->id_key };
     689           0 :       reconn_prq_insert( ctx->reconn_prq, &reconn );
     690           0 :       peer->reconn_pending = 1;
     691           0 :     }
     692           0 :     return;
     693           0 :   }
     694             : 
     695           0 :   memcpy( conn->tls_hs->hs.cli.server_pubkey, peer->id_key.uc, sizeof(fd_pubkey_t) ); /* fail the handshake before authenticating to whoever else holds the peer's address */
     696           0 :   conn->tls_hs->hs.cli.server_pubkey_len = sizeof(fd_pubkey_t);
     697           0 :   conn->tls_hs->hs.cli.server_pubkey_pin = 1;
     698             : 
     699           0 :   peer->conn_ts = now;
     700           0 :   ctx->client_peer_id_keys[ conn->conn_idx ] = peer->id_key;
     701           0 :   fd_quic_conn_set_context( conn, &ctx->client_peer_id_keys[ conn->conn_idx ] );
     702           0 :   peer->tx_conn = conn;
     703           0 :   for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) if( ctx->reward_votes[ i ].conn==conn ) ctx->reward_votes[ i ].conn = NULL;
     704           0 : }
     705             : 
     706             : static ulong
     707             : quic_client_datagram_tx( fd_votor_tile_t *   ctx,
     708             :                          fd_stem_context_t * stem,
     709             :                          fd_quic_conn_t *    conn,
     710             :                          uchar const *       buf,
     711           0 :                          ulong               buf_sz ) {
     712           0 :   uchar * packet_l2 = fd_chunk_to_laddr( ctx->net_out_mem, ctx->net_out_chunk );
     713           0 :   uchar * payload   = packet_l2 + sizeof(fd_ip4_udp_hdrs_t);
     714             : 
     715           0 :   ulong pkt_num;
     716           0 :   ulong pkt_sz = fd_quic_conn_tx_dgram( conn, payload, FD_NET_MTU-sizeof(fd_ip4_udp_hdrs_t), buf, buf_sz, &pkt_num );
     717           0 :   if( FD_UNLIKELY( !pkt_sz ) ) return ULONG_MAX;
     718             : 
     719           0 :   fd_ip4_udp_hdrs_t * hdr = (fd_ip4_udp_hdrs_t *)fd_type_pun( packet_l2 );
     720           0 :   *hdr = *ctx->hdr;
     721             : 
     722           0 :   hdr->ip4->daddr       = conn->peer[ 0 ].ip_addr;
     723           0 :   hdr->ip4->net_tot_len = fd_ushort_bswap( (ushort)( pkt_sz+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) );
     724           0 :   hdr->ip4->net_id      = fd_ushort_bswap( ctx->net_id++ );
     725           0 :   hdr->ip4->check       = 0;
     726           0 :   hdr->ip4->check       = fd_ip4_hdr_check_fast( hdr->ip4 );
     727             : 
     728           0 :   hdr->udp->net_dport = fd_ushort_bswap( conn->peer[ 0 ].udp_port );
     729           0 :   hdr->udp->net_len   = fd_ushort_bswap( (ushort)( pkt_sz+sizeof(fd_udp_hdr_t) ) );
     730           0 :   hdr->udp->check     = (ushort)0;
     731             : 
     732           0 :   uint  ip_dst = hdr->ip4->daddr;
     733           0 :   ulong sig    = fd_disco_netmux_sig( ip_dst, 0U, ip_dst, DST_PROTO_OUTGOING, FD_NETMUX_SIG_MIN_HDR_SZ );
     734           0 :   ulong sz_l2  = sizeof(fd_ip4_udp_hdrs_t) + pkt_sz;
     735           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 );
     736           0 :   ctx->net_out_chunk = fd_dcache_compact_next( ctx->net_out_chunk, FD_NET_MTU, ctx->net_out_chunk0, ctx->net_out_wmark );
     737           0 :   ctx->quic_client->metrics.net_tx_pkt_cnt++; /* bypasses fd_quic's aio send, which counts the rest */
     738           0 :   ctx->quic_client->metrics.net_tx_byte_cnt += sz_l2-sizeof(fd_eth_hdr_t); /* IP bytes, as fd_quic counts */
     739           0 :   return pkt_num;
     740           0 : }
     741             : 
     742             : /* reward_vote_tx sends rv to peer, its reward leader, recording the
     743             :    pkt_num an ACK must match and backing off the next retry. */
     744             : 
     745             : static void
     746             : reward_vote_tx( fd_votor_tile_t *   ctx,
     747             :                 fd_stem_context_t * stem,
     748             :                 reward_vote_t *     rv,
     749             :                 peer_t const *      peer,
     750           0 :                 long                now ) {
     751           0 :   long  broadcast_start_time = fd_clock_tile_now( ctx->clock );
     752           0 :   ulong ser_sz               = ag_vote_ser( &rv->vote, ctx->scratch.ser );
     753           0 :   ulong pkt_num              = quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
     754           0 :   if( FD_UNLIKELY( pkt_num==ULONG_MAX ) ) {
     755           0 :     rv->retry_ts = now+REWARD_VOTE_RTT_MIN_NS;
     756           0 :     return;
     757           0 :   }
     758             : 
     759           0 :   if( FD_UNLIKELY( rv->conn!=peer->tx_conn ) ) {
     760           0 :     rv->conn = peer->tx_conn;
     761           0 :     for( ulong j=0UL; j<REWARD_VOTE_TX_MAX; j++ ) rv->pkt_num[ j ] = ULONG_MAX;
     762           0 :   }
     763           0 :   rv->pkt_num[ rv->tx_cnt ] = pkt_num;
     764           0 :   rv->tx_cnt++;
     765           0 :   report_alpenglow_vote( ctx, NULL, &rv->vote, ctx->scratch.ser[ 1 ], FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_ACCEPTED, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD, 0L, broadcast_start_time, peer );
     766           0 :   long pto     = (long)( peer->tx_conn->rtt->smoothed_rtt + fmaxf( 4.0f*peer->tx_conn->rtt->var_rtt, (float)QUIC_K_GRANULARITY_NS ) + peer->tx_conn->peer_max_ack_delay_ns ); /* RFC 9002 PTO */
     767           0 :   rv->backoff  = fd_long_min( fd_long_max( fd_long_max( 2L*rv->backoff, pto ), REWARD_VOTE_RTT_MIN_NS ), REWARD_VOTE_RTT_MAX_NS ); /* double per unacked send, clamp between RTT_MIN, RTT_MAX */
     768           0 :   rv->retry_ts = now + rv->backoff;
     769           0 : }
     770             : 
     771             : /* QUIC callback */
     772             : 
     773             : static void
     774             : quic_sign( void *      signer_ctx,
     775             :            uchar       sig[ static FD_ED25519_SIG_SZ ],
     776           0 :            uchar const msg[ static FD_TLS_CV_SIGN_SZ ] ) {
     777           0 :   fd_votor_tile_t * ctx = signer_ctx;
     778           0 :   fd_keyguard_client_sign( ctx->keyguard_client, sig, msg, FD_TLS_CV_SIGN_SZ, FD_KEYGUARD_SIGN_TYPE_ED25519 );
     779           0 : }
     780             : 
     781             : /* QUIC callback */
     782             : 
     783             : /* votor_wfs_is_green returns whether WFS permits votor to run.  The
     784             :    classifier is a pure function of config, so it takes the configured
     785             :    shred version, never the one ipecho observed.  ERROR is replay's
     786             :    fatal to report, so just hold here. */
     787             : 
     788             : static int
     789           0 : votor_wfs_is_green( fd_votor_tile_t const * ctx ) {
     790           0 :   int mode = fd_wfs_mode( ctx->wfs_slot, ctx->wfs_hash_is_zero,
     791           0 :                           (ulong)ctx->wfs_shred_version, ctx->boot_block_id.slot );
     792           0 :   switch( mode ) {
     793           0 :     case FD_WFS_MODE_DISABLED:
     794           0 :     case FD_WFS_MODE_NOOP:       return 1;
     795           0 :     case FD_WFS_MODE_MATCH:      return ctx->wfs_signalled;
     796           0 :     case FD_WFS_MODE_UNRESOLVED:
     797           0 :     case FD_WFS_MODE_ERROR:      return 0;
     798           0 :     default: FD_LOG_CRIT(( "unexpected WFS mode %d", mode ));
     799           0 :   }
     800           0 : }
     801             : 
     802             : static int
     803             : quic_aio_tx( void *                    _ctx,
     804             :              fd_aio_pkt_info_t const * batch,
     805             :              ulong                     batch_cnt,
     806             :              ulong *                   opt_batch_idx,
     807           0 :              int                       flush ) {
     808           0 :   (void)flush;
     809             : 
     810           0 :   fd_votor_tile_t * ctx = _ctx;
     811             : 
     812           0 :   for( ulong i=0UL; i<batch_cnt; i++ ) {
     813           0 :     if( FD_UNLIKELY( ctx->net_tx_cnt==FD_VOTOR_NET_BURST ) ) {
     814           0 :       if( FD_LIKELY( opt_batch_idx ) ) *opt_batch_idx = i;
     815           0 :       return FD_AIO_ERR_AGAIN;
     816           0 :     }
     817           0 :     if( FD_UNLIKELY( batch[ i ].buf_sz<FD_NETMUX_SIG_MIN_HDR_SZ ) ) continue;
     818             : 
     819           0 :     ulong const sz_l2 = sizeof(fd_eth_hdr_t) + batch[ i ].buf_sz;
     820           0 :     if( FD_UNLIKELY( sz_l2>FD_ETH_PAYLOAD_MAX ) ) continue;
     821             : 
     822           0 :     uint const ip_dst = FD_LOAD( uint, batch[ i ].buf+offsetof( fd_ip4_hdr_t, daddr_c ) );
     823           0 :     uchar * packet_l2 = fd_chunk_to_laddr( ctx->net_out_mem, ctx->net_out_chunk );
     824           0 :     uchar * packet_l3 = packet_l2 + sizeof(fd_eth_hdr_t);
     825           0 :     memset( packet_l2, 0, 12 );
     826           0 :     FD_STORE( ushort, packet_l2+offsetof( fd_eth_hdr_t, net_type ), fd_ushort_bswap( FD_ETH_HDR_TYPE_IP ) );
     827           0 :     fd_memcpy( packet_l3, batch[ i ].buf, batch[ i ].buf_sz );
     828             : 
     829           0 :     ctx->net_tx[ ctx->net_tx_cnt ].chunk = ctx->net_out_chunk;
     830           0 :     ctx->net_tx[ ctx->net_tx_cnt ].sz    = sz_l2;
     831           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 );
     832           0 :     ctx->net_tx_cnt++;
     833             : 
     834           0 :     ctx->net_out_chunk = fd_dcache_compact_next( ctx->net_out_chunk, FD_NET_MTU, ctx->net_out_chunk0, ctx->net_out_wmark );
     835           0 :   }
     836             : 
     837           0 :   if( FD_LIKELY( opt_batch_idx ) ) *opt_batch_idx = batch_cnt;
     838             : 
     839           0 :   return FD_AIO_SUCCESS;
     840           0 : }
     841             : 
     842             : /* QUIC callback */
     843             : 
     844             : static void
     845             : quic_client_conn_final( fd_quic_conn_t * conn,
     846           0 :                         void *           quic_ctx ) {
     847           0 :   fd_votor_tile_t *   ctx    = quic_ctx;
     848           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn ); /* NULL for conns we closed ourselves, which are not reconnected */
     849           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     850           0 :   peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     851           0 :   if( FD_UNLIKELY( !peer ) ) return;
     852             : 
     853           0 :   long now = fd_clock_tile_now( ctx->clock );
     854           0 :   long pto = (long)( conn->rtt->smoothed_rtt + fmaxf( 4.0f*conn->rtt->var_rtt, (float)QUIC_K_GRANULARITY_NS ) + conn->peer_max_ack_delay_ns ); /* RFC 9002 PTO */
     855           0 :   peer->tx_conn      = NULL;
     856           0 :   peer->conn_backoff = fd_long_min( fd_long_max( 2L*peer->conn_backoff, pto ), QUIC_CLIENT_CONN_BACKOFF_MAX_NS );
     857           0 :   peer->conn_ts      = now;
     858           0 :   if( FD_LIKELY( !peer->reconn_pending ) ) {
     859           0 :     reconn_t reconn = { .timeout = now+peer->conn_backoff, .id_key = peer->id_key };
     860           0 :     reconn_prq_insert( ctx->reconn_prq, &reconn );
     861           0 :     peer->reconn_pending = 1;
     862           0 :   }
     863           0 : }
     864             : 
     865             : /* QUIC callback */
     866             : 
     867             : static void
     868             : quic_client_conn_hs_complete( fd_quic_conn_t * conn,
     869           0 :                               void *           quic_ctx ) {
     870           0 :   fd_votor_tile_t *   ctx    = quic_ctx;
     871           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     872           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     873             : 
     874           0 :   if( FD_LIKELY( !conn->tls_hs || memcmp( conn->tls_hs->hs.cli.server_pubkey, id_key->uc, sizeof(fd_pubkey_t) ) ) ) {
     875           0 :     fd_quic_conn_close( conn, QUIC_CLOSE_CODE_INVALID_IDENTITY );
     876           0 :     return;
     877           0 :   }
     878             : 
     879           0 :   peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     880           0 :   if( FD_LIKELY( peer ) ) peer->conn_backoff = 0L;
     881           0 : }
     882             : 
     883             : /* QUIC callback */
     884             : 
     885             : static void
     886             : quic_client_ack_range( fd_quic_conn_t * conn,
     887             :                        ulong            pkt_num_lo,
     888             :                        ulong            pkt_num_hi,
     889           0 :                        void *           quic_ctx ) {
     890           0 :   fd_votor_tile_t * ctx = quic_ctx;
     891           0 :   for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) {
     892           0 :     reward_vote_t * rv = &ctx->reward_votes[ i ];
     893           0 :     if( FD_LIKELY( rv->slot==ULONG_MAX || rv->conn!=conn ) ) continue;
     894           0 :     for( ulong j=0UL; j<fd_ulong_min( rv->tx_cnt, REWARD_VOTE_TX_MAX ); j++ ) {
     895           0 :       if( FD_LIKELY(    rv->pkt_num[ j ]<pkt_num_lo
     896           0 :                      || rv->pkt_num[ j ]>pkt_num_hi ) ) continue;
     897           0 :       report_alpenglow_vote( ctx, NULL, &rv->vote, (uchar)( AG_VOTE_SERDE_TAG_NOTAR+rv->vote.kind ), FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_ACCEPTED, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD_ACKED, 0L, 0L, NULL );
     898           0 :       rv->slot = ULONG_MAX;
     899           0 :       break;
     900           0 :     }
     901           0 :   }
     902           0 : }
     903             : 
     904             : /* QUIC callback */
     905             : 
     906             : static void
     907             : quic_server_conn_new( fd_quic_conn_t * conn,
     908           0 :                       void *           _ctx ) {
     909           0 :   if( FD_UNLIKELY( !conn->tls_hs ) ) return; /* no authenticated identity, so no votes will be attributed */
     910           0 :   fd_pubkey_t const * id_key = (fd_pubkey_t const *)fd_type_pun_const( conn->tls_hs->hs.srv.client_pubkey );
     911             : 
     912           0 :   fd_votor_tile_t * ctx  = _ctx;
     913           0 :   peer_t *          peer = peers_query( ctx->peers, *id_key, NULL );
     914           0 :   if( FD_LIKELY( ctx->curr_epoch_info ) && FD_UNLIKELY( !peer ) ) {
     915           0 :     fd_quic_conn_close( conn, 0U );
     916           0 :     return;
     917           0 :   }
     918           0 :   if( FD_UNLIKELY( peer && fd_clock_tile_now( ctx->clock )<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     919           0 :     fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED );
     920           0 :     return;
     921           0 :   }
     922           0 :   ulong idx = 0UL;
     923           0 :   while( peer && idx<QUIC_SERVER_CONN_PER_PEER_MAX && peer->rx_conn[ idx ] ) idx++;
     924           0 :   if( FD_UNLIKELY( idx==QUIC_SERVER_CONN_PER_PEER_MAX ) ) { /* never the incumbents: another node's contact info can make the peer dial us */
     925           0 :     fd_quic_conn_close( conn, QUIC_CLOSE_CODE_TOO_MANY_CONNECTIONS );
     926           0 :     return;
     927           0 :   }
     928           0 :   ctx->server_peer_id_keys[ conn->conn_idx ] = *id_key;
     929           0 :   fd_quic_conn_set_context( conn, &ctx->server_peer_id_keys[ conn->conn_idx ] );
     930           0 :   if( FD_LIKELY( peer ) ) peer->rx_conn[ idx ] = conn;
     931           0 : }
     932             : 
     933             : /* QUIC callback */
     934             : 
     935             : static void
     936             : quic_server_conn_final( fd_quic_conn_t * conn,
     937           0 :                         void *           _ctx ) {
     938           0 :   fd_votor_tile_t *   ctx    = _ctx;
     939           0 :   fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     940           0 :   fd_quic_conn_set_context( conn, NULL ); /* a reused conn slot must not carry this peer's key */
     941           0 :   if( FD_UNLIKELY( !id_key ) ) return;
     942           0 :   peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     943           0 :   if( FD_UNLIKELY( !peer ) ) return;
     944           0 :   for( ulong i=0UL; i<QUIC_SERVER_CONN_PER_PEER_MAX; i++ ) if( FD_UNLIKELY( peer->rx_conn[ i ]==conn ) ) peer->rx_conn[ i ] = NULL;
     945           0 : }
     946             : 
     947             : /* QUIC callback */
     948             : 
     949             : static void
     950             : quic_server_datagram_rx( fd_quic_conn_t * conn,
     951             :                          uchar const *    data,
     952             :                          ulong            data_sz,
     953           0 :                          void *           _ctx ) {
     954             : 
     955           0 :   fd_votor_tile_t * ctx = _ctx;
     956           0 :   if( FD_UNLIKELY( !ctx->init  ) ) { ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_NOT_READY_IDX ]++; return; }
     957           0 :   if( FD_UNLIKELY( conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) { ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_NOT_ADMITTED_IDX ]++; return; }
     958           0 :   if( FD_UNLIKELY( data_sz<2UL ) ) { ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_TOO_SMALL_IDX ]++; return; }
     959           0 :   uchar kind = data[ 1 ];
     960             : 
     961           0 :   switch( kind ) {
     962           0 :   case AG_VOTE_SERDE_TAG_NOTAR:
     963           0 :   case AG_VOTE_SERDE_TAG_FINAL:
     964           0 :   case AG_VOTE_SERDE_TAG_SKIP:
     965           0 :   case AG_VOTE_SERDE_TAG_NOTAR_FALLBACK:
     966           0 :   case AG_VOTE_SERDE_TAG_SKIP_FALLBACK: {
     967           0 :     ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_VOTE_IDX ]++;
     968           0 :     fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
     969           0 :     if( FD_UNLIKELY( !id_key ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_UNKNOWN_SIGNER_IDX ]++; report_alpenglow_vote( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_UNKNOWN_PEER, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL ); return; }
     970           0 :     peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
     971           0 :     if( FD_UNLIKELY( !peer ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_NOT_PEER_IDX ]++; report_alpenglow_vote( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_UNKNOWN_PEER, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL ); return; }
     972           0 :     if( FD_UNLIKELY( fd_clock_tile_now( ctx->clock )<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) {
     973           0 :       ctx->metrics.vote_rx[FD_METRICS_ENUM_VOTE_RX_RESULT_V_BANNED_IDX]++;
     974           0 :       report_alpenglow_vote( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_BANNED_PEER, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL );
     975           0 :       fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED );
     976           0 :       return;
     977           0 :     }
     978             : 
     979           0 :     int err = ag_vote_de( &ctx->scratch.vote, data, data_sz );
     980           0 :     switch( err ) {
     981           0 :     case AG_VOTE_DE_SUCCESS:   break;
     982           0 :     case AG_VOTE_DE_ERR_SZ:    ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_BAD_SIZE_IDX         ]++;                                              return;
     983           0 :     case AG_VOTE_DE_ERR_INVAL: ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_BAD_ENCODING_IDX     ]++;                                              return;
     984           0 :     case AG_VOTE_DE_ERR_SIG:   ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SIGNATURE_FAILED_IDX ]++; ban_peer( ctx, peer, fd_clock_tile_now( ctx->clock ) ); return;
     985           0 :     default:                   FD_LOG_CRIT(( "unhandled ag_vote_de err %d", err ));
     986           0 :     }
     987           0 :     ag_vote_t * vote      = &ctx->scratch.vote;
     988           0 :     ulong       vote_slot = ag_vote_slot( vote );
     989           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 ) );
     990           0 :     switch( vote->kind ) {
     991           0 :     case AG_VOTE_KIND_NOTAR:          vote->notar.rank          = rank; break;
     992           0 :     case AG_VOTE_KIND_FINAL:          vote->final.rank          = rank; break;
     993           0 :     case AG_VOTE_KIND_SKIP:           vote->skip.rank           = rank; break;
     994           0 :     case AG_VOTE_KIND_NOTAR_FALLBACK: vote->notar_fallback.rank = rank; break;
     995           0 :     case AG_VOTE_KIND_SKIP_FALLBACK:  vote->skip_fallback.rank  = rank; break;
     996           0 :     default:                          FD_LOG_CRIT(( "unreachable" ));
     997           0 :     }
     998           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_MISMATCHED_IDX ]++; report_alpenglow_vote( ctx, conn, vote, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SHRED_VERSION_MISMATCH, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL ); return; }
     999           0 :     uchar const * block_hash = ag_vote_block_hash( vote );
    1000           0 :     if( FD_UNLIKELY( block_hash && !memcmp( block_hash, ag_block_hash_null, sizeof(ag_block_hash_t) ) ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_NULL_BLOCK_HASH_IDX ]++; return; } /* null is never a valid block hash */
    1001             : 
    1002           0 :     if( FD_UNLIKELY( rank==USHORT_MAX ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_UNRANKED_IDX ]++; report_alpenglow_vote( ctx, conn, vote, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_UNRANKED_PEER, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL ); return; } /* peer is not ranked in their vote slot's epoch */
    1003           0 :     if( FD_UNLIKELY( vote_slot>fd_ulong_max( ag_pool_finalized_slot( ctx->pool ), ctx->highest_parent_ready_slot )+VOTE_TOO_NEW ) ) { ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLOT_OUT_OF_RANGE_IDX ]++; report_alpenglow_vote( ctx, conn, vote, kind, FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLOT_TOO_NEW, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, 0L, 0L, NULL ); return; }
    1004             : 
    1005           0 :     long  aggregation_start_time = fd_clock_tile_now( ctx->clock );
    1006           0 :     uchar quorum_reached;
    1007           0 :     err = ag_pool_add_vote( ctx->pool, &ctx->scratch.vote, 0, ctx->scratch.bad, &quorum_reached );
    1008           0 :     int result;
    1009           0 :     switch( err ) {
    1010           0 :     case AG_POOL_SUCCESS:                ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SUCCESS_IDX           ]++; result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_ACCEPTED;                                                                                                                         break;
    1011           0 :     case AG_POOL_ERR_SLOT_TOO_OLD:       ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLOT_OUT_OF_RANGE_IDX ]++; result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLOT_TOO_OLD;                                                                                                                    break;
    1012           0 :     case AG_POOL_ERR_SLOT_TOO_NEW:       ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLOT_OUT_OF_RANGE_IDX ]++; result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLOT_TOO_NEW;                                                                                                                    break;
    1013           0 :     case AG_POOL_ERR_DUPLICATE:          ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_DUPLICATE_IDX         ]++; result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_DUPLICATE;                                                                                                                        break;
    1014           0 :     case AG_POOL_ERR_SLASHABLE:          ctx->metrics.vote_rx[ FD_METRICS_ENUM_VOTE_RX_RESULT_V_SLASHABLE_IDX         ]++; result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLASHABLE;                                                                                                                        break;
    1015           0 :     default:
    1016           0 :       FD_LOG_CRIT(( "unhandled kind" ));
    1017           0 :     }
    1018           0 :     report_alpenglow_vote( ctx, conn, vote, kind, result, quorum_reached, FD_EVENT_ALPENGLOW_VOTE_REASON_NOT_OUR_VOTE, aggregation_start_time, 0L, NULL );
    1019           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, vote_slot );
    1020           0 :     reward_cert_t * rc = &ctx->reward_certs[ vote_slot%AG_SLOTS_PER_WINDOW ];
    1021           0 :     if( FD_UNLIKELY( err==AG_POOL_SUCCESS && rc->slot==vote_slot && ( vote->kind==AG_VOTE_KIND_NOTAR || vote->kind==AG_VOTE_KIND_SKIP ) ) ) rc->dirty = 1;
    1022           0 :     return;
    1023           0 :   }
    1024           0 :   case AG_CERT_SERDE_TAG_FINAL:
    1025           0 :   case AG_CERT_SERDE_TAG_FAST_FINAL:
    1026           0 :   case AG_CERT_SERDE_TAG_NOTAR:
    1027           0 :   case AG_CERT_SERDE_TAG_NOTAR_FALLBACK:
    1028           0 :   case AG_CERT_SERDE_TAG_SKIP: {
    1029           0 :     ctx->metrics.datagram_rx[ FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_CERT_IDX ]++;
    1030           0 :     fd_pubkey_t const * id_key = fd_quic_conn_get_context( conn );
    1031           0 :     if( FD_UNLIKELY( !id_key ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_UNKNOWN_SIGNER_IDX ]++; report_alpenglow_cert( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_UNKNOWN_PEER, 0L, 0L ); return; }
    1032           0 :     peer_t * peer = peers_query( ctx->peers, *id_key, NULL );
    1033           0 :     if( FD_UNLIKELY( !peer ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_NOT_PEER_IDX ]++; report_alpenglow_cert( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_UNKNOWN_PEER, 0L, 0L ); return; }
    1034           0 :     if( FD_UNLIKELY( fd_clock_tile_now( ctx->clock )<peer->ban_ts+QUIC_BAN_TIMEOUT_NS ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BANNED_IDX ]++; report_alpenglow_cert( ctx, conn, NULL, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_BANNED_PEER, 0L, 0L ); fd_quic_conn_close( conn, QUIC_CLOSE_CODE_BANNED ); return; }
    1035             : 
    1036           0 :     ulong bit_cnt;
    1037           0 :     int   err = ag_cert_de( &ctx->scratch.cert, &bit_cnt, data, data_sz );
    1038           0 :     if( FD_UNLIKELY( err ) ) {
    1039           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BAD_SIZE_IDX     ] += (ulong)(err==AG_CERT_DE_ERR_SZ   );
    1040           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BAD_ENCODING_IDX ] += (ulong)(err==AG_CERT_DE_ERR_INVAL);
    1041           0 :       return;
    1042           0 :     }
    1043           0 :     ag_cert_t * cert = &ctx->scratch.cert;
    1044           0 :     if( FD_UNLIKELY( ag_cert_shred_version( cert )!=ctx->shred_version ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SHRED_VERSION_MISMATCHED_IDX ]++; report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_SHRED_VERSION_MISMATCH, 0L, 0L ); return; }
    1045             : 
    1046           0 :     ulong  cert_slot = ag_cert_slot( cert );
    1047           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 ) );
    1048           0 :     if( FD_UNLIKELY( rank==USHORT_MAX ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_UNRANKED_IDX ]++; report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_UNRANKED_PEER, 0L, 0L ); return; } /* peer is not ranked in this cert slot's epoch */
    1049           0 :     ag_epoch_info_t const * epoch_info = fd_ptr_if( cert_slot>=ctx->next_epoch_slot, ctx->next_epoch_info, fd_ptr_if( cert_slot>=ctx->curr_epoch_slot, ctx->curr_epoch_info, ctx->prev_epoch_info ) );
    1050           0 :     if( FD_UNLIKELY( epoch_info && bit_cnt>epoch_info->validator_cnt ) ) { ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_BAD_ENCODING_IDX ]++; return; }
    1051             : 
    1052           0 :     int  final             = cert->kind==AG_CERT_KIND_FINAL || cert->kind==AG_CERT_KIND_FAST_FINAL;
    1053           0 :     long verify_start_time = fd_clock_tile_now( ctx->clock );
    1054           0 :     switch( ag_pool_add_cert( ctx->pool, &ctx->scratch.cert, 0, ctx->scratch.bad ) ) {
    1055           0 :     case AG_POOL_SUCCESS:
    1056           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SUCCESS_IDX ]++;
    1057           0 :       report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_ACCEPTED, verify_start_time, 0L );
    1058           0 :       break;
    1059           0 :     case AG_POOL_ERR_SLOT_TOO_OLD: ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SLOT_OUT_OF_RANGE_IDX ]++; break;
    1060           0 :     case AG_POOL_ERR_SLOT_TOO_NEW:
    1061           0 :       if( FD_LIKELY( ( cert_slot<=ctx->catchup_slot && ( !final || cert_slot<=ctx->catchup_final_slot ) ) || cert_slot>=ctx->next_epoch_slot+ctx->slots_per_epoch ) ) break;
    1062           0 :       ctx->metrics.bls_verify++;
    1063           0 :       if( FD_UNLIKELY( ag_cert_verify( cert, epoch_info )!=AG_CERT_VERIFY_SUCCESS ) ) {
    1064           0 :         ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SIGNATURE_FAILED_IDX ]++;
    1065           0 :         report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_FAILED_BLS_VERIFY, verify_start_time, 0L );
    1066           0 :         ban_peer( ctx, peer, fd_clock_tile_now( ctx->clock ) );
    1067           0 :         break;
    1068           0 :       }
    1069           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SLOT_OUT_OF_RANGE_IDX ]++;
    1070           0 :       report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_SLOT_TOO_NEW, verify_start_time, 0L );
    1071           0 :       if( FD_UNLIKELY( final ) ) ctx->catchup_final_slot = cert_slot;
    1072           0 :       ulong vote_slot = fd_ulong_max( fd_ulong_sat_sub( cert_slot+1UL, ctx->slot_max ), fd_ulong_if( final, fd_ulong_sat_sub( cert_slot+1UL, AG_REWARD_SLOT_DELTA ), 0UL ) );
    1073           0 :       if( FD_UNLIKELY( vote_slot>ctx->catchup_vote_slot ) ) {
    1074           0 :         ctx->catchup_vote_slot = vote_slot;
    1075           0 :         ag_votor_set_catchup_vote_slot( ctx->votor, vote_slot );
    1076           0 :         for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) {
    1077           0 :           if( FD_UNLIKELY( ctx->reward_votes[ i ].slot<vote_slot ) ) ctx->reward_votes[ i ].slot = ULONG_MAX;
    1078           0 :         }
    1079           0 :       }
    1080           0 :       if( FD_UNLIKELY( cert_slot<=ctx->catchup_slot ) ) break;
    1081           0 :       ctx->catchup_slot = cert_slot;
    1082           0 :       fd_votor_msg_t * chunk = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    1083           0 :       chunk->repair = (fd_votor_repair_t){ .slot = cert_slot };
    1084           0 :       fd_stem_publish( ctx->stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_REPAIR, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    1085           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 );
    1086           0 :       break;
    1087           0 :     case AG_POOL_ERR_DUPLICATE:
    1088           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_DUPLICATE_IDX ]++;
    1089           0 :       if( FD_LIKELY( ctx->scratch.cert.kind==AG_CERT_KIND_NOTAR ) ) report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_DUPLICATE, 0L, 0L );
    1090           0 :       break;
    1091           0 :     case AG_POOL_ERR_CERT_VERIFY:
    1092           0 :       ctx->metrics.cert_rx[ FD_METRICS_ENUM_CERT_RX_RESULT_V_SIGNATURE_FAILED_IDX ]++;
    1093           0 :       report_alpenglow_cert( ctx, conn, &ctx->scratch.cert, kind, FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_FAILED_BLS_VERIFY, verify_start_time, 0L );
    1094           0 :       ban_peer( ctx, peer, fd_clock_tile_now( ctx->clock ) );
    1095           0 :       break;
    1096           0 :     default:
    1097           0 :       FD_LOG_CRIT(( "unhandled kind" ));
    1098           0 :     }
    1099           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, cert_slot );
    1100           0 :     return;
    1101           0 :   }
    1102           0 :   default:
    1103           0 :     ctx->metrics.datagram_rx[FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_UNKNOWN_TAG_IDX]++;
    1104           0 :     break;
    1105           0 :   }
    1106           0 : }
    1107             : 
    1108             : static void
    1109             : load_keys( fd_votor_tile_t * ctx,
    1110           0 :            ulong             path_cnt ) {
    1111             : 
    1112             :   /* special case the identity.  We already know the bls pubkey */
    1113             : 
    1114           0 :   auth_vtr_key_t bls_key;
    1115           0 :   fd_keyguard_client_bls_pubkey( ctx->keyguard_client, bls_key.uc, ULONG_MAX );
    1116           0 :   auth_vtr_clear( ctx->auth_vtr );
    1117           0 :   auth_vtr_insert( ctx->auth_vtr, bls_key )->paths_idx = ULONG_MAX;
    1118             : 
    1119             :   /* For any of the other authorized voters, we need to request the BLS
    1120             :      pubkey from the sign tile. */
    1121             : 
    1122           0 :   for( ulong i=0UL; i<path_cnt; i++ ) {
    1123           0 :     fd_keyguard_client_bls_pubkey( ctx->keyguard_client, bls_key.uc, i );
    1124           0 :     auth_vtr_t const * exists = auth_vtr_query_const( ctx->auth_vtr, bls_key, NULL );
    1125           0 :     if( FD_UNLIKELY( exists ) ) {
    1126           0 :       if( FD_LIKELY( exists->paths_idx==ULONG_MAX ) ) continue; /* the identity is also an authorized voter */
    1127           0 :       FD_LOG_ERR(( "authorized voter key duplicate at index %lu", i ));
    1128           0 :     }
    1129           0 :     auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, bls_key );
    1130           0 :     auth_vtr->paths_idx = i;
    1131           0 :   }
    1132           0 :   ctx->auth_vtr_path_cnt = path_cnt;
    1133           0 : }
    1134             : 
    1135             : static void
    1136           0 : update_own_rank( fd_votor_tile_t * ctx ) {
    1137           0 :   ag_epoch_info_t const * epoch_infos[3] = { ctx->prev_epoch_info, ctx->curr_epoch_info, ctx->next_epoch_info };
    1138           0 :   ulong                   epoch_slots[3] = { ctx->prev_epoch_slot, ctx->curr_epoch_slot, ctx->next_epoch_slot };
    1139           0 :   for( ulong i=0UL; i<3UL; i++ ) {
    1140           0 :     ag_epoch_info_t const * epoch_info = epoch_infos[ i ];
    1141           0 :     if( FD_UNLIKELY( !epoch_info ) ) continue;
    1142           0 :     ushort own_rank = USHORT_MAX;
    1143           0 :     for( ulong rank=0UL; rank<epoch_info->validator_cnt; rank++ ) {
    1144           0 :       if( FD_UNLIKELY( !memcmp( epoch_info->validators[ rank ].id_key, ctx->id_key.uc, sizeof(ag_id_key_t) ) ) ) own_rank = (ushort)rank;
    1145           0 :     }
    1146           0 :     ag_pool_set_rank ( ctx->pool,  epoch_slots[ i ], own_rank );
    1147           0 :     ag_votor_set_rank( ctx->votor, epoch_slots[ i ], own_rank );
    1148           0 :   }
    1149           0 : }
    1150             : 
    1151             : static void
    1152           0 : update_own_bls_key( fd_votor_tile_t * ctx ) {
    1153           0 :   ag_epoch_info_t const * epoch_infos[3] = { ctx->prev_epoch_info, ctx->curr_epoch_info, ctx->next_epoch_info };
    1154           0 :   ulong                   epoch_slots[3] = { ctx->prev_epoch_slot, ctx->curr_epoch_slot, ctx->next_epoch_slot };
    1155           0 :   for( ulong i=0UL; i<3UL; i++ ) {
    1156           0 :     ag_epoch_info_t const * epoch_info = epoch_infos[ i ];
    1157           0 :     if( FD_UNLIKELY( !epoch_info ) ) continue;
    1158           0 :     ushort own_rank = USHORT_MAX;
    1159           0 :     for( ulong rank=0UL; rank<epoch_info->validator_cnt; rank++ ) {
    1160           0 :       if( FD_UNLIKELY( !memcmp( epoch_info->validators[ rank ].id_key, ctx->id_key.uc, sizeof(ag_id_key_t) ) ) ) own_rank = (ushort)rank;
    1161           0 :     }
    1162           0 :     uchar const * own_bls_key = NULL; /* NULL if we are not ranked or cannot sign with our key, in which case we do not vote */
    1163           0 :     if( FD_LIKELY( own_rank!=USHORT_MAX ) ) {
    1164           0 :       ag_validator_info_t const * validator = &epoch_info->validators[ own_rank ];
    1165           0 :       FD_BASE58_ENCODE_32_BYTES( validator->vote_key, vote_key_b58 );
    1166           0 :       if( FD_LIKELY( auth_vtr_query_const( ctx->auth_vtr, *(auth_vtr_key_t const *)fd_type_pun_const( validator->bls_key ), NULL ) ) ) {
    1167           0 :         own_bls_key = validator->bls_key;
    1168           0 :         FD_LOG_INFO(( "voting as %s in the epoch starting at slot %lu", vote_key_b58, epoch_slots[ i ] ));
    1169           0 :       } else {
    1170           0 :         FD_LOG_WARNING(( "no identity or authorized voter keypair matches the BLS key of vote account %s, unable to vote", vote_key_b58 ));
    1171           0 :       }
    1172           0 :     }
    1173           0 :     ag_votor_set_bls_key( ctx->votor, epoch_slots[ i ], own_bls_key );
    1174           0 :   }
    1175           0 : }
    1176             : 
    1177             : static void
    1178             : sign_vote( void *         signer_ctx,
    1179             :            fd_bls_sig_t * sig,
    1180             :            uchar const *  public_key,
    1181             :            uchar const *  payload,
    1182           0 :            ulong          payload_sz ) {
    1183           0 :   fd_votor_tile_t * ctx = signer_ctx;
    1184           0 :   if( FD_UNLIKELY( ctx->halt_signing ) ) {
    1185           0 :     memset( sig, 0, sizeof(fd_bls_sig_t) );
    1186           0 :     return;
    1187           0 :   }
    1188           0 :   auth_vtr_t const * auth_vtr = auth_vtr_query_const( ctx->auth_vtr, *(auth_vtr_key_t const *)fd_type_pun_const( public_key ), NULL );
    1189           0 :   if( FD_UNLIKELY( !auth_vtr ) ) FD_LOG_CRIT(( "no key derives the requested BLS public key" ));
    1190           0 :   uchar sig_bytes[ FD_BLS_SIG_SZ ];
    1191           0 :   fd_keyguard_client_ag_vote_sign( ctx->keyguard_client, sig_bytes, auth_vtr->paths_idx, payload, payload_sz );
    1192           0 :   if( FD_UNLIKELY( fd_bls_sig_de( sig, sig_bytes ) ) ) FD_LOG_CRIT(( "sign tile returned an invalid BLS signature" ));
    1193           0 : }
    1194             : 
    1195             : /* write_vote_history writes out an Agave-compatible vote history file.
    1196             :    Firedancer only writes out a single slot, the wait-to-vote slot.
    1197             : 
    1198             :    Examples:
    1199             : 
    1200             :    vote_history-A.bin          live
    1201             :    vote_history-A.bin.swp      staging, swapped with live on each write
    1202             :    vote_history-B.bin.bak      B as of the switch away from it, the spare
    1203             :                                the next switch reuses */
    1204             : 
    1205             : static void
    1206           0 : write_vote_history( fd_votor_tile_t * ctx ) {
    1207           0 :   FD_BASE58_ENCODE_32_BYTES( ctx->id_key.uc, id_key_b58 );
    1208           0 :   char swp[ PATH_MAX ];
    1209           0 :   char bin[ PATH_MAX ];
    1210           0 :   FD_TEST( fd_cstr_printf_check( swp, PATH_MAX, NULL, "vote_history-%s.bin.swp", id_key_b58 ) );
    1211           0 :   FD_TEST( fd_cstr_printf_check( bin, PATH_MAX, NULL, "vote_history-%s.bin",     id_key_b58 ) );
    1212             : 
    1213           0 :   uchar * buf = ctx->scratch.vote_history;
    1214           0 :   ulong   sz  = AG_VOTE_HISTORY_SER_SZ;
    1215           0 :   ag_vote_history_ser( ctx->id_key.uc, ag_first_slot_in_window( ctx->highest_vote_or_lead_slot )+AG_SLOTS_PER_WINDOW-1UL, buf );
    1216           0 :   fd_keyguard_client_sign( ctx->keyguard_client, buf+AG_VOTE_HISTORY_SERDE_SIG_OFF, buf+AG_VOTE_HISTORY_SERDE_DATA_OFF, sz-AG_VOTE_HISTORY_SERDE_DATA_OFF, FD_KEYGUARD_SIGN_TYPE_ED25519 );
    1217             : 
    1218           0 :   for( ulong off=0UL; off<sz; ) {
    1219           0 :     long n = pwritev2( ctx->vote_history_fd_swp, &(struct iovec){ .iov_base=buf+off, .iov_len=sz-off }, 1, (long)off, 0 );
    1220           0 :     if( FD_UNLIKELY( -1==n && errno==EINTR ) ) continue;
    1221           0 :     if( FD_UNLIKELY( n<=0 ) ) FD_LOG_ERR(( "pwritev2(%s) failed (%i-%s)", swp, errno, fd_io_strerror( errno ) ));
    1222           0 :     off += (ulong)n;
    1223           0 :   }
    1224           0 :   while( FD_UNLIKELY( ftruncate( ctx->vote_history_fd_swp, (long)sz ) ) ) {
    1225           0 :     if( FD_UNLIKELY( errno!=EINTR ) ) FD_LOG_ERR(( "ftruncate(%s) failed (%i-%s)", swp, errno, fd_io_strerror( errno ) ));
    1226           0 :   }
    1227             : 
    1228           0 :   if( FD_UNLIKELY( ctx->vote_history_pending ) ) {
    1229           0 :     FD_BASE58_ENCODE_32_BYTES( ctx->id_key_bak.uc, bak_b58 );
    1230           0 :     char bak_swp[ PATH_MAX ];
    1231           0 :     FD_TEST( fd_cstr_printf_check( bak_swp, PATH_MAX, NULL, "vote_history-%s.bin.swp", bak_b58 ) );
    1232           0 :     if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, swp, ctx->vote_history_fd_dir, bin, RENAME_NOREPLACE ) && errno==EEXIST ) )
    1233           0 :       FD_LOG_WARNING(( "replacing vote history file %s, which was copied in.  set-identity takes the vote history from --vote-history-file", bin ));
    1234           0 :     if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, swp, ctx->vote_history_fd_dir, bin, 0 ) && errno!=ENOENT ) )
    1235           0 :       FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", swp, bin, errno, fd_io_strerror( errno ) ));
    1236           0 :     if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, bak_swp, ctx->vote_history_fd_dir, swp, 0 ) ) )
    1237           0 :       FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", bak_swp, swp, errno, fd_io_strerror( errno ) ));
    1238           0 :     ctx->vote_history_pending = 0;
    1239           0 :   } else if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, swp, ctx->vote_history_fd_dir, bin, RENAME_EXCHANGE ) ) ) {
    1240           0 :     FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", swp, bin, errno, fd_io_strerror( errno ) ));
    1241           0 :   }
    1242           0 :   int fd = ctx->vote_history_fd_swp; ctx->vote_history_fd_swp = ctx->vote_history_fd; ctx->vote_history_fd = fd;
    1243             : 
    1244           0 :   ctx->metrics.vote_history_write++;
    1245           0 : }
    1246             : 
    1247             : /* read_vote_history returns the wait-to-vote slot of an Agave-compatible
    1248             :    vote history file, 0 if it is missing or empty. */
    1249             : 
    1250             : static ulong
    1251             : read_vote_history( fd_votor_tile_t * ctx,
    1252             :                    char const *      path,
    1253           0 :                    char const *      name ) {
    1254           0 :   int fd = openat( ctx->vote_history_fd_dir, name, O_RDONLY );
    1255           0 :   if( FD_UNLIKELY( -1==fd && errno==ENOENT ) ) return 0UL;
    1256           0 :   if( FD_UNLIKELY( -1==fd ) ) FD_LOG_ERR(( "open(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1257           0 :   struct stat st;
    1258           0 :   if( FD_UNLIKELY( -1==fstat( fd, &st ) ) ) FD_LOG_ERR(( "fstat(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1259           0 :   if( FD_UNLIKELY( !st.st_size ) ) { /* never written, the identity never voted from here */
    1260           0 :     if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1261           0 :     return 0UL;
    1262           0 :   }
    1263             : 
    1264           0 :   ulong         sz   = (ulong)st.st_size;
    1265           0 :   uchar const * file = mmap( NULL, sz, PROT_READ, MAP_PRIVATE, fd, 0 );
    1266           0 :   if( FD_UNLIKELY( file==MAP_FAILED ) ) FD_LOG_ERR(( "mmap(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1267           0 :   if( FD_UNLIKELY( close( fd ) ) ) FD_LOG_ERR(( "close(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1268           0 :   ulong vote_history_slot;
    1269           0 :   int   err = ag_vote_history_de( file, sz, ctx->id_key.uc, &vote_history_slot );
    1270           0 :   if( FD_UNLIKELY( munmap( (void *)file, sz ) ) ) FD_LOG_ERR(( "munmap(`%s/%s`) failed (%i-%s)", path, name, errno, fd_io_strerror( errno ) ));
    1271           0 :   if( FD_UNLIKELY( err ) ) FD_LOG_ERR(( "vote history file `%s/%s` is invalid (%i) or not for this identity. Please copy this identity's latest %s into %s.", path, name, err, name, path ));
    1272           0 :   return vote_history_slot;
    1273           0 : }
    1274             : 
    1275             : static ag_epoch_info_t *
    1276             : rank_voters( ag_epoch_info_t *              epoch_info,
    1277             :              fd_vote_stake_weight_t const * stakes,
    1278             :              ulong                          stake_cnt,
    1279             :              ag_epoch_info_t const *        known0,
    1280           0 :              ag_epoch_info_t const *        known1 ) {
    1281             : 
    1282           0 :   FD_TEST( stake_cnt<=AG_VAT_MAX );
    1283             : 
    1284           0 :   known_key_t known[ 2UL*AG_VAT_MAX ];
    1285           0 :   ulong       known_cnt = 0UL;
    1286           0 :   ag_epoch_info_t const * src[2] = { known0, known1 };
    1287           0 :   for( ulong j=0UL; j<2UL; j++ ) {
    1288           0 :     if( !src[ j ] || src[ j ]==epoch_info ) continue; /* epoch_info is overwritten below */
    1289           0 :     for( ulong r=0UL; r<src[ j ]->validator_cnt; r++ ) known[ known_cnt++ ] = (known_key_t){ .bls = src[ j ]->validators[ r ].bls_key, .pub = &src[ j ]->pubkeys[ r ] };
    1290           0 :   }
    1291           0 :   sort_known_key_inplace( known, known_cnt );
    1292             : 
    1293           0 :   sort_voter_t voters[AG_VAT_MAX];
    1294           0 :   ulong        voter_cnt = 0UL;
    1295           0 :   for( ulong i=0UL; i<stake_cnt; i++ ) {
    1296           0 :     if( FD_UNLIKELY( !stakes[i].stake ) ) continue;
    1297           0 :     ulong k = sort_known_key_split( known, known_cnt, (known_key_t){ .bls = stakes[i].bls_key } );
    1298           0 :     if( FD_LIKELY( k<known_cnt && !memcmp( known[ k ].bls, stakes[i].bls_key, FD_BLS_PUB_COMPRESSED_SZ ) ) ) voters[voter_cnt].pub = *known[ k ].pub;
    1299           0 :     else if( FD_UNLIKELY( fd_bls_pub_de( &voters[voter_cnt].pub, stakes[i].bls_key, FD_BLS_PUB_COMPRESSED_SZ ) ) ) continue;
    1300           0 :     voters[voter_cnt].bls   = stakes[i].bls_key;
    1301           0 :     voters[voter_cnt].id    = stakes[i].id_key.uc;
    1302           0 :     voters[voter_cnt].stake = stakes[i].stake;
    1303           0 :     voters[voter_cnt].idx   = i;
    1304           0 :     voters[voter_cnt].dup   = 0;
    1305           0 :     voter_cnt++;
    1306           0 :   }
    1307             : 
    1308           0 :   sort_voter_bls_inplace( voters, voter_cnt );
    1309           0 :   for( ulong i=1UL; i<voter_cnt; i++ ) if( FD_UNLIKELY( !memcmp( voters[i-1UL].bls, voters[i].bls, FD_BLS_PUB_COMPRESSED_SZ ) ) ) voters[i-1UL].dup = voters[i].dup = 1;
    1310           0 :   sort_voter_id_inplace( voters, voter_cnt );
    1311           0 :   for( ulong i=1UL; i<voter_cnt; i++ ) if( FD_UNLIKELY( !memcmp( voters[i-1UL].id,  voters[i].id,  sizeof(fd_pubkey_t)      ) ) ) voters[i-1UL].dup = voters[i].dup = 1;
    1312           0 :   epoch_info->validator_cnt = 0;
    1313           0 :   for( ulong i=0UL; i<voter_cnt; i++ ) if( FD_LIKELY( !voters[i].dup ) ) voters[epoch_info->validator_cnt++] = voters[i];
    1314           0 :   sort_voter_stake_inplace( voters, epoch_info->validator_cnt );
    1315             : 
    1316           0 :   epoch_info->total_stake = 0UL;
    1317           0 :   for( ulong i=0UL; i<epoch_info->validator_cnt; i++ ) {
    1318           0 :     ulong                 idx            = voters[i].idx;
    1319           0 :     ag_validator_info_t * validator_info = epoch_info->validators + i;
    1320           0 :     memset( validator_info, 0, sizeof(ag_validator_info_t) );
    1321           0 :     validator_info->id    = i;
    1322           0 :     validator_info->stake = stakes[idx].stake;
    1323           0 :     memcpy( validator_info->id_key,   stakes[idx].id_key.uc,   sizeof(ag_id_key_t)   );
    1324           0 :     memcpy( validator_info->vote_key, stakes[idx].vote_key.uc, sizeof(ag_vote_key_t) );
    1325           0 :     memcpy( validator_info->bls_key,  stakes[idx].bls_key,     sizeof(ag_bls_key_t)  );
    1326           0 :     epoch_info->pubkeys[i]   = voters[i].pub;
    1327           0 :     epoch_info->total_stake += validator_info->stake;
    1328           0 :   }
    1329           0 :   return epoch_info;
    1330           0 : }
    1331             : 
    1332             : static void
    1333             : handle_epoch( fd_votor_tile_t *           ctx,
    1334           0 :               fd_epoch_info_msg_t const * msg ) {
    1335             : 
    1336           0 :   ag_epoch_info_t * epoch_info;
    1337           0 :   if     ( FD_UNLIKELY( !ctx->curr_epoch_info ) ) epoch_info = &ctx->scratch.curr_epoch_info;
    1338           0 :   else if( FD_UNLIKELY( !ctx->next_epoch_info ) ) epoch_info = &ctx->scratch.next_epoch_info;
    1339           0 :   else if( FD_UNLIKELY( !ctx->prev_epoch_info ) ) epoch_info = &ctx->scratch.prev_epoch_info;
    1340           0 :   else                                            epoch_info = ctx->prev_epoch_info;
    1341           0 :   rank_voters( epoch_info, fd_epoch_info_msg_stake_weights( msg ), msg->staked_vote_cnt, ctx->curr_epoch_info, ctx->next_epoch_info );
    1342             : 
    1343             :   /* swap pointers */
    1344             : 
    1345           0 :   if( FD_UNLIKELY( !ctx->curr_epoch_info ) ) {
    1346           0 :     ctx->curr_epoch_info = epoch_info;
    1347           0 :     ctx->curr_epoch_slot = msg->start_slot;
    1348           0 :   } else {
    1349           0 :     if( FD_LIKELY( ctx->next_epoch_info ) ) {
    1350           0 :       ctx->prev_epoch_info = ctx->curr_epoch_info;
    1351           0 :       ctx->prev_epoch_slot = ctx->curr_epoch_slot;
    1352           0 :       ctx->curr_epoch_info = ctx->next_epoch_info;
    1353           0 :       ctx->curr_epoch_slot = ctx->next_epoch_slot;
    1354           0 :     }
    1355           0 :     ctx->next_epoch_info = epoch_info;
    1356           0 :     ctx->next_epoch_slot = msg->start_slot;
    1357           0 :   }
    1358             : 
    1359             :   /* mark all for deletion */
    1360             : 
    1361           0 :   for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1362           0 :     peer_t * peer = &ctx->peers[ slot ];
    1363           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
    1364           0 :     peer->prev_rank = USHORT_MAX;
    1365           0 :     peer->curr_rank = USHORT_MAX;
    1366           0 :     peer->next_rank = USHORT_MAX;
    1367           0 :   }
    1368             : 
    1369             :   /* unmark all ranked in prev epoch */
    1370             : 
    1371           0 :   ulong prev_cnt = ctx->prev_epoch_info ? ctx->prev_epoch_info->validator_cnt : 0UL;
    1372           0 :   for( ulong rank=0UL; rank<prev_cnt; rank++ ) {
    1373           0 :     fd_pubkey_t id_key;
    1374           0 :     memcpy( id_key.uc, ctx->prev_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
    1375             : 
    1376           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
    1377           0 :     if( FD_UNLIKELY( !peer ) ) {
    1378           0 :       peer                 = peers_insert( ctx->peers, id_key );
    1379           0 :       peer->curr_rank      = USHORT_MAX;
    1380           0 :       peer->next_rank      = USHORT_MAX;
    1381           0 :       peer->tx_conn        = NULL;
    1382           0 :       memset( peer->rx_conn, 0, sizeof(peer->rx_conn) );
    1383           0 :       peer->ban_ts         = 0L;
    1384           0 :       peer->conn_ts        = 0L;
    1385           0 :       peer->conn_backoff   = 0L;
    1386           0 :       peer->reconn_pending = 0;
    1387           0 :     }
    1388           0 :     peer->prev_rank = (ushort)rank;
    1389           0 :   }
    1390             : 
    1391             :   /* unmark all ranked in curr epoch */
    1392             : 
    1393           0 :   ushort own_rank = USHORT_MAX; /* our own rank in the new epoch */
    1394           0 :   for( ulong rank=0UL; rank<ctx->curr_epoch_info->validator_cnt; rank++ ) {
    1395           0 :     fd_pubkey_t id_key;
    1396           0 :     memcpy( id_key.uc, ctx->curr_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
    1397           0 :     if( FD_UNLIKELY( ctx->curr_epoch_info==epoch_info && fd_pubkey_eq( &id_key, &ctx->id_key ) ) ) own_rank = (ushort)rank;
    1398             : 
    1399           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
    1400           0 :     if( FD_UNLIKELY( !peer ) ) {
    1401           0 :       peer                 = peers_insert( ctx->peers, id_key );
    1402           0 :       peer->prev_rank      = USHORT_MAX;
    1403           0 :       peer->next_rank      = USHORT_MAX;
    1404           0 :       peer->tx_conn        = NULL;
    1405           0 :       memset( peer->rx_conn, 0, sizeof(peer->rx_conn) );
    1406           0 :       peer->ban_ts         = 0L;
    1407           0 :       peer->conn_ts        = 0L;
    1408           0 :       peer->conn_backoff   = 0L;
    1409           0 :       peer->reconn_pending = 0;
    1410           0 :     }
    1411           0 :     peer->curr_rank = (ushort)rank;
    1412           0 :   }
    1413             : 
    1414             :   /* unmark all ranked in next epoch */
    1415             : 
    1416           0 :   ulong next_cnt = ctx->next_epoch_info ? ctx->next_epoch_info->validator_cnt : 0UL;
    1417           0 :   for( ulong rank=0UL; rank<next_cnt; rank++ ) {
    1418           0 :     fd_pubkey_t id_key;
    1419           0 :     memcpy( id_key.uc, ctx->next_epoch_info->validators[ rank ].id_key, sizeof(ag_id_key_t) );
    1420           0 :     if( FD_UNLIKELY( ctx->next_epoch_info==epoch_info && fd_pubkey_eq( &id_key, &ctx->id_key ) ) ) own_rank = (ushort)rank;
    1421             : 
    1422           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
    1423           0 :     if( FD_UNLIKELY( !peer ) ) {
    1424           0 :       peer                 = peers_insert( ctx->peers, id_key );
    1425           0 :       peer->prev_rank      = USHORT_MAX;
    1426           0 :       peer->curr_rank      = USHORT_MAX;
    1427           0 :       peer->tx_conn        = NULL;
    1428           0 :       memset( peer->rx_conn, 0, sizeof(peer->rx_conn) );
    1429           0 :       peer->ban_ts         = 0L;
    1430           0 :       peer->conn_ts        = 0L;
    1431           0 :       peer->conn_backoff   = 0L;
    1432           0 :       peer->reconn_pending = 0;
    1433           0 :     }
    1434           0 :     peer->next_rank = (ushort)rank;
    1435           0 :   }
    1436             : 
    1437             :   /* quic_connect new peers */
    1438             : 
    1439           0 :   for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1440           0 :     peer_t * peer = &ctx->peers[ slot ];
    1441           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
    1442           0 :     contact_info_t const * ci = contact_infos_query( ctx->contact_infos, peer->id_key, NULL );
    1443           0 :     if( FD_LIKELY( ci ) ) quic_client_connect( ctx, peer, ci, fd_clock_tile_now( ctx->clock ) );
    1444           0 :   }
    1445             : 
    1446             :   /* quic_conn_close evicted peers */
    1447             : 
    1448           0 :   for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); ) {
    1449           0 :     peer_t * peer = &ctx->peers[ slot ];
    1450           0 :     if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) {
    1451           0 :       slot++;
    1452           0 :       continue;
    1453           0 :     }
    1454           0 :     if( FD_LIKELY( peer->prev_rank != USHORT_MAX || peer->curr_rank != USHORT_MAX || peer->next_rank != USHORT_MAX ) ) {
    1455           0 :       slot++;
    1456           0 :       continue;
    1457           0 :     }
    1458           0 :     if( FD_LIKELY( peer->tx_conn ) ) {
    1459           0 :       fd_quic_conn_set_context( peer->tx_conn, NULL );
    1460           0 :       fd_quic_conn_close( peer->tx_conn, QUIC_CLOSE_CODE_NOT_ADMITTED );
    1461           0 :       peer->tx_conn = NULL;
    1462           0 :     }
    1463           0 :     for( ulong i=0UL; i<QUIC_SERVER_CONN_PER_PEER_MAX; i++ ) {
    1464           0 :       if( FD_UNLIKELY( !peer->rx_conn[ i ] ) ) continue;
    1465           0 :       fd_quic_conn_set_context( peer->rx_conn[ i ], NULL );
    1466           0 :       fd_quic_conn_close( peer->rx_conn[ i ], QUIC_CLOSE_CODE_NOT_ADMITTED );
    1467           0 :       peer->rx_conn[ i ] = NULL;
    1468           0 :     }
    1469           0 :     if( FD_UNLIKELY( peer->reconn_pending ) ) {
    1470           0 :       for( ulong i=0UL; i<reconn_prq_cnt( ctx->reconn_prq ); i++ ) {
    1471           0 :         if( FD_UNLIKELY( fd_pubkey_eq( &ctx->reconn_prq[i].id_key, &peer->id_key ) ) ) {
    1472           0 :           reconn_prq_remove( ctx->reconn_prq, i );
    1473           0 :           break;
    1474           0 :         }
    1475           0 :       }
    1476           0 :     }
    1477           0 :     contact_info_t * ci = contact_infos_query( ctx->contact_infos, peer->id_key, NULL );
    1478           0 :     if( FD_UNLIKELY( ci && ci->removed ) ) contact_infos_remove( ctx->contact_infos, ci );
    1479           0 :     peers_remove( ctx->peers, peer ); /* relocates, so reconsider the freed slot */
    1480           0 :   }
    1481             : 
    1482             :   /* The latest epoch's slot time, only for the connect-ahead estimate:
    1483             :      votor times each window's skip deadlines by its own epoch's. */
    1484             : 
    1485           0 :   ctx->ns_per_slot     = (long)msg->ns_per_slot;
    1486           0 :   ctx->slots_per_epoch = msg->epoch_schedule.slots_per_epoch;
    1487             : 
    1488           0 :   ag_pool_advance_epoch ( ctx->pool,  epoch_info,       own_rank, msg->start_slot );
    1489           0 :   ag_votor_advance_epoch( ctx->votor, ctx->ns_per_slot, own_rank, msg->start_slot, NULL );
    1490           0 :   update_own_bls_key( ctx );
    1491             : 
    1492             :   /* update our leader schedule */
    1493             : 
    1494           0 :   fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, msg );
    1495           0 :   fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
    1496             :   /* Slot 0 is genesis.  Slot 1 is ParentReady on genesis (see
    1497             :      ag_parent_ready_tracker_mark_root) even though it does not start a
    1498             :      window. */
    1499           0 :   if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX ) ) ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, fd_ulong_max( fd_ulong_max( msg->start_slot, 1UL ), ctx->vote_history_slot ), &ctx->id_key );
    1500             : 
    1501           0 :   ctx->init = !!ctx->next_epoch_info && ag_pool_finalized_slot( ctx->pool )!=ULONG_MAX && !!ctx->shred_version && votor_wfs_is_green( ctx );
    1502           0 : }
    1503             : 
    1504             : static void
    1505             : handle_gossip( fd_votor_tile_t *                  ctx,
    1506             :                ulong                              sig,
    1507           0 :                fd_gossip_update_message_t const * msg ) {
    1508             : 
    1509           0 :   fd_pubkey_t id_key;
    1510           0 :   memcpy( id_key.uc, msg->origin, sizeof(fd_pubkey_t) );
    1511           0 :   if( FD_UNLIKELY( peers_key_inval( id_key ) ) ) return;
    1512             : 
    1513           0 :   contact_info_t * ci   = contact_infos_query( ctx->contact_infos, id_key, NULL );
    1514           0 :   peer_t *         peer = peers_query        ( ctx->peers,         id_key, NULL );
    1515             : 
    1516           0 :   switch( sig ) {
    1517           0 :   case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
    1518           0 :     fd_gossip_socket_t const * socket = &msg->contact_info->value->sockets[FD_GOSSIP_CONTACT_INFO_SOCKET_ALPENGLOW];
    1519           0 :     uint                       ip4    = fd_uint_if( !socket->is_ipv6, socket->ip4, 0U );
    1520           0 :     ushort                     port   = fd_ushort_bswap( socket->port );
    1521             : 
    1522             :     /* ip 0 or port 0 is invalid to connect to, remove them */
    1523             : 
    1524           0 :     if( FD_UNLIKELY( !ip4 || !port ) ) {
    1525           0 :       if( FD_LIKELY( ci ) ) contact_infos_remove( ctx->contact_infos, ci );
    1526           0 :       return;
    1527           0 :     }
    1528             : 
    1529             :     /* short-circuit ci, if it didn't change (common case) */
    1530             : 
    1531           0 :     if( FD_LIKELY( ci && ci->ip4==ip4 && ci->port==port ) ) { ci->removed = 0; return; }
    1532             : 
    1533             :     /* upsert the ci */
    1534             : 
    1535           0 :     if( FD_UNLIKELY( !ci ) ) ci = contact_infos_insert( ctx->contact_infos, id_key );
    1536           0 :     ci->ip4     = ip4;
    1537           0 :     ci->port    = port;
    1538           0 :     ci->removed = 0;
    1539           0 :     if( FD_LIKELY( !peer ) ) return;
    1540           0 :     if( FD_UNLIKELY( peer->tx_conn ) ) {
    1541           0 :       fd_quic_conn_set_context( peer->tx_conn, NULL );
    1542           0 :       fd_quic_conn_close( peer->tx_conn, 0U );
    1543           0 :       peer->tx_conn = NULL;
    1544           0 :     }
    1545           0 :     peer->conn_backoff = 0L;
    1546           0 :     quic_client_connect( ctx, peer, ci, fd_clock_tile_now( ctx->clock ) );
    1547           0 :     break;
    1548           0 :   }
    1549           0 :   case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE:
    1550           0 :     if( FD_LIKELY  ( ci && !peer ) ) contact_infos_remove( ctx->contact_infos, ci );
    1551           0 :     if( FD_UNLIKELY( ci &&  peer ) ) ci->removed = 1; /* a peer's is kept, to redial its last address */
    1552           0 :     break;
    1553           0 :   default:
    1554           0 :     FD_LOG_ERR(( "unexpected gossip sig %lu", sig ));
    1555           0 :   }
    1556           0 : }
    1557             : 
    1558             : static void
    1559             : handle_replay( fd_votor_tile_t *           ctx,
    1560             :                ulong                       sig,
    1561           0 :                fd_replay_message_t const * replay ) {
    1562             : 
    1563           0 :   fd_block_footer_t const * footer = NULL;
    1564           0 :   switch( sig ) {
    1565           0 :   case REPLAY_SIG_SLOT_COMPLETED: {
    1566           0 :     fd_replay_slot_completed_t const * slot_completed  = &replay->slot_completed;
    1567           0 :     ag_block_id_t                      block_id        = ag_block_id( slot_completed->slot,        slot_completed->block_id.uc        );
    1568           0 :     ag_block_id_t                      parent_block_id = ag_block_id( slot_completed->parent_slot, slot_completed->parent_block_id.uc );
    1569           0 :     if( FD_UNLIKELY( ag_pool_finalized_slot( ctx->pool )==ULONG_MAX ) ) {
    1570           0 :       ctx->boot_block_id = block_id;
    1571             : 
    1572             :       /* A cluster restart, booting at the wait-for-supermajority slot,
    1573             :          starts a new fork, so votes before it no longer bind, as in
    1574             :          Agave's maybe_cluster_restart_with_hard_fork. */
    1575             : 
    1576           0 :       if( FD_UNLIKELY( fd_wfs_mode( ctx->wfs_slot, ctx->wfs_hash_is_zero, (ulong)ctx->wfs_shred_version, block_id.slot )==FD_WFS_MODE_MATCH && ctx->vote_history_slot ) ) {
    1577           0 :         FD_LOG_WARNING(( "cluster restart at slot %lu, discarding the vote history floor %lu", block_id.slot, ctx->vote_history_slot ));
    1578           0 :         ctx->vote_history_slot         = 0UL;
    1579           0 :         ctx->highest_vote_or_lead_slot = 0UL;
    1580           0 :         ctx->next_leader_slot          = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, fd_ulong_max( ctx->curr_epoch_slot, 1UL ), &ctx->id_key );
    1581           0 :       }
    1582             : 
    1583           0 :       if( block_id.slot==0UL ) ag_pool_from_genesis ( ctx->pool, &block_id );
    1584           0 :       else                     ag_pool_from_snapshot( ctx->pool, &block_id );
    1585           0 :       int wfs_green = votor_wfs_is_green( ctx );
    1586             :       /* Without ipecho's shred version, init must be deferred. */
    1587           0 :       if( FD_LIKELY( ctx->shred_version ) ) {
    1588           0 :         long now = fd_clock_tile_now( ctx->clock );
    1589           0 :         ag_votor_init( ctx->votor, &block_id, now, ctx->ns_per_slot, ctx->shred_version, sign_vote, ctx );
    1590           0 :         ctx->highest_vote_or_lead_slot = fd_ulong_max( ctx->highest_vote_or_lead_slot, block_id.slot );
    1591           0 :         if( FD_LIKELY( wfs_green ) ) ag_votor_arm_skip_timeouts( ctx->votor, now );
    1592           0 :       }
    1593           0 :       ctx->init = !!ctx->next_epoch_info && !!ctx->shred_version && wfs_green;
    1594           0 :     } else if( FD_UNLIKELY( block_id.slot!=0 ) ) {
    1595           0 :       ag_pool_add_block( ctx->pool, &block_id, &parent_block_id, ctx->scratch.bad );
    1596           0 :       if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, block_id.slot );
    1597           0 :     }
    1598           0 :     ag_block_info_t block_info = { .parent = parent_block_id };
    1599           0 :     memcpy( block_info.hash, block_id.hash, sizeof(ag_block_hash_t) );
    1600           0 :     if( FD_LIKELY( ctx->init ) ) ag_votor_process_replay( ctx->votor, block_id.slot, &block_info );
    1601             : 
    1602           0 :     ulong           reward_slot = block_id.slot-AG_REWARD_SLOT_DELTA;
    1603           0 :     reward_vote_t * rv          = &ctx->reward_votes[ reward_slot%REWARD_VOTE_MAX ];
    1604           0 :     if( FD_UNLIKELY( block_id.slot>=AG_REWARD_SLOT_DELTA && rv->slot==reward_slot ) ) rv->slot = ULONG_MAX;
    1605             : 
    1606           0 :     footer = &slot_completed->footer;
    1607           0 :     break;
    1608           0 :   }
    1609           0 :   case REPLAY_SIG_SLOT_DEAD:
    1610           0 :     footer = &replay->slot_dead.footer;
    1611           0 :     break;
    1612           0 :   default:
    1613           0 :     FD_LOG_ERR(( "unexpected replay sig %lu", sig ));
    1614           0 :   }
    1615             : 
    1616           0 :   if( FD_UNLIKELY( !ctx->init ) ) return;
    1617             : 
    1618             :   /* Replay verified these certs before publishing the footer (or, as
    1619             :      leader, built them from ours), so skip re-verifying them. */
    1620           0 :   ag_cert_t *  cert = &ctx->scratch.cert;
    1621           0 :   fd_bls_sig_t sig_aff[1];
    1622           0 :   if( footer->has_fast_final_cert ) {
    1623           0 :     *cert = (ag_cert_t){ .kind = AG_CERT_KIND_FAST_FINAL, .fast_final = { .slot = footer->fast_final_cert.slot, .shred_version = ctx->shred_version } };
    1624           0 :     memcpy( cert->fast_final.block_hash, footer->fast_final_cert.block_id.uc, sizeof(ag_block_hash_t) );
    1625           0 :     fd_bls_set_copy( cert->fast_final.agg.set, footer->fast_final_cert.signer_set );
    1626           0 :     blst_p2_uncompress( sig_aff, footer->fast_final_cert.sig );
    1627           0 :     blst_p2_from_affine( &cert->fast_final.agg.sig, sig_aff );
    1628           0 :     ag_pool_add_cert( ctx->pool, cert, 1, ctx->scratch.bad );
    1629           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, cert->fast_final.slot );
    1630           0 :   } else if( footer->has_final_cert ) {
    1631           0 :     *cert = (ag_cert_t){ .kind = AG_CERT_KIND_FINAL, .final = { .slot = footer->final_cert.slot, .shred_version = ctx->shred_version } };
    1632           0 :     fd_bls_set_copy( cert->final.agg.set, footer->final_cert.signer_set );
    1633           0 :     blst_p2_uncompress( sig_aff, footer->final_cert.sig );
    1634           0 :     blst_p2_from_affine( &cert->final.agg.sig, sig_aff );
    1635           0 :     ag_pool_add_cert( ctx->pool, cert, 1, ctx->scratch.bad );
    1636           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, cert->final.slot );
    1637             : 
    1638           0 :     *cert = (ag_cert_t){ .kind = AG_CERT_KIND_NOTAR, .notar = { .slot = footer->notar_cert.slot, .shred_version = ctx->shred_version } };
    1639           0 :     memcpy( cert->notar.block_hash, footer->notar_cert.block_id.uc, sizeof(ag_block_hash_t) );
    1640           0 :     fd_bls_set_copy( cert->notar.agg.set, footer->notar_cert.signer_set );
    1641           0 :     blst_p2_uncompress( sig_aff, footer->notar_cert.sig );
    1642           0 :     blst_p2_from_affine( &cert->notar.agg.sig, sig_aff );
    1643           0 :     ag_pool_add_cert( ctx->pool, cert, 1, ctx->scratch.bad );
    1644           0 :     if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, cert->notar.slot );
    1645           0 :   }
    1646           0 : }
    1647             : 
    1648             : FD_FN_CONST static inline ulong
    1649           0 : scratch_align( void ) {
    1650           0 :   return fd_ulong_max( alignof(fd_votor_tile_t), fd_quic_align() );
    1651           0 : }
    1652             : 
    1653             : FD_FN_PURE static inline ulong
    1654           0 : scratch_footprint( fd_topo_tile_t const * tile ) {
    1655           0 :   ulong l = FD_LAYOUT_INIT;
    1656           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_votor_tile_t),       sizeof(fd_votor_tile_t)                                           );
    1657           0 :   l = FD_LAYOUT_APPEND( l, auth_vtr_align(),               auth_vtr_footprint()                                              );
    1658           0 :   l = FD_LAYOUT_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_client_limits )                          );
    1659           0 :   l = FD_LAYOUT_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_server_limits )                          );
    1660           0 :   l = FD_LAYOUT_APPEND( l, ag_pool_align(),                ag_pool_footprint( tile->votor.max_live_slots, QUIC_DGRAM_BURST ) );
    1661           0 :   l = FD_LAYOUT_APPEND( l, ag_votor_align(),               ag_votor_footprint( tile->votor.max_live_slots )                  );
    1662           0 :   l = FD_LAYOUT_APPEND( l, peers_align(),                  peers_footprint( PEERS_LG_SLOT_CNT )                              );
    1663           0 :   l = FD_LAYOUT_APPEND( l, contact_infos_align(),          contact_infos_footprint( CONTACT_INFOS_LG_SLOT_CNT )              );
    1664           0 :   l = FD_LAYOUT_APPEND( l, reconn_prq_align(),             reconn_prq_footprint( RECONN_MAX )                                );
    1665           0 :   l = FD_LAYOUT_APPEND( l, fd_multi_epoch_leaders_align(), fd_multi_epoch_leaders_footprint()                                );
    1666           0 :   return FD_LAYOUT_FINI( l, scratch_align() );
    1667           0 : }
    1668             : 
    1669             : static void
    1670           0 : during_housekeeping( fd_votor_tile_t * ctx ) {
    1671           0 :   if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock );
    1672             : 
    1673             :   /* Spread handshakes to newly ranked peers across 10 seconds. */
    1674             : 
    1675           0 :   if( FD_UNLIKELY( ctx->conn_ahead_slot!=ctx->next_epoch_slot ) ) {
    1676           0 :     peer_t const * self = peers_query( ctx->peers, ctx->id_key, NULL );
    1677           0 :     ulong          root = ag_pool_finalized_slot( ctx->pool );
    1678           0 :     if( FD_UNLIKELY( !self || self->curr_rank!=USHORT_MAX || self->next_rank==USHORT_MAX ) ) {
    1679           0 :       ctx->conn_ahead_slot = ctx->next_epoch_slot;
    1680           0 :     } else if( FD_UNLIKELY( root!=ULONG_MAX && ( root>=ctx->next_epoch_slot || (long)( ctx->next_epoch_slot-root )*ctx->ns_per_slot<=QUIC_CLIENT_CONN_AHEAD_NS ) ) ) {
    1681           0 :       ctx->conn_ahead_slot = ctx->next_epoch_slot;
    1682           0 :       ulong cnt = 0UL;
    1683           0 :       for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1684           0 :         peer_t const * peer = &ctx->peers[ slot ];
    1685           0 :         cnt += (ulong)( !peers_key_inval( peer->id_key ) && peer!=self && !peer->tx_conn && !peer->reconn_pending && contact_infos_query( ctx->contact_infos, peer->id_key, NULL ) );
    1686           0 :       }
    1687           0 :       long  now = fd_clock_tile_now( ctx->clock );
    1688           0 :       ulong idx = 0UL;
    1689           0 :       for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1690           0 :         peer_t * peer = &ctx->peers[ slot ];
    1691           0 :         if( FD_UNLIKELY( peers_key_inval( peer->id_key ) || peer==self || peer->tx_conn || peer->reconn_pending || !contact_infos_query( ctx->contact_infos, peer->id_key, NULL ) ) ) continue;
    1692           0 :         reconn_t reconn = { .timeout = now+(long)idx*(QUIC_CLIENT_CONN_AHEAD_NS/2L)/(long)cnt, .id_key = peer->id_key };
    1693           0 :         reconn_prq_insert( ctx->reconn_prq, &reconn );
    1694           0 :         peer->reconn_pending = 1;
    1695           0 :         idx++;
    1696           0 :       }
    1697           0 :     }
    1698           0 :   }
    1699             : 
    1700           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1701           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED );
    1702           0 :   }
    1703             : 
    1704             :   /* Important invariant is that the sign tile has already been updated
    1705             :      and that the votor is fully in sync with the sign tile. */
    1706             : 
    1707           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1708           0 :     ulong          param = fd_keyswitch_param_query( ctx->auth_vtr_keyswitch );
    1709           0 :     auth_vtr_key_t bls_key;
    1710           0 :     if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_ADD ) ) {
    1711             : 
    1712             :       /* For a new authorized voter, derive the BLS pubkey from the sign
    1713             :          tile and add to the auth voter map. */
    1714             : 
    1715           0 :       if( FD_UNLIKELY( ctx->auth_vtr_path_cnt==FD_KEYGUARD_AUTH_VOTERS_MAX ) ) FD_LOG_CRIT(( "keyswitch: too many authorized voters, keys not synced up with sign tile" ));
    1716           0 :       fd_keyguard_client_bls_pubkey( ctx->keyguard_client, bls_key.uc, ctx->auth_vtr_path_cnt );
    1717           0 :       auth_vtr_t const * exists = auth_vtr_query_const( ctx->auth_vtr, bls_key, NULL );
    1718           0 :       if( FD_UNLIKELY( exists && exists->paths_idx!=ULONG_MAX ) ) FD_LOG_CRIT(( "keyswitch: duplicate authorized voter key, keys not synced up with sign tile" ));
    1719           0 :       if( FD_LIKELY( !exists ) ) auth_vtr_insert( ctx->auth_vtr, bls_key )->paths_idx = ctx->auth_vtr_path_cnt;
    1720           0 :       ctx->auth_vtr_path_cnt++;
    1721             : 
    1722           0 :     } else if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_CLEAR ) ) {
    1723             : 
    1724             :       /* Nuke the entire set of auth voters and just rederive the BLS
    1725             :          pubkey for the identity from the sign tile. */
    1726             : 
    1727           0 :       load_keys( ctx, 0UL );
    1728           0 :     } else {
    1729           0 :       FD_LOG_CRIT(( "keyswitch: unexpected authorized voter operation %lu", param ));
    1730           0 :     }
    1731             : 
    1732             :     /* The epochs votor holds chose their keys when they advanced, so
    1733             :        choose them again for each of them. */
    1734             : 
    1735           0 :     FD_LOG_INFO(( "keyswitch: authorized voter count is now %lu", ctx->auth_vtr_path_cnt ));
    1736           0 :     update_own_bls_key( ctx );
    1737           0 :     fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1738           0 :   }
    1739             : 
    1740             :   /* Identity key switch.  The admin tile halts votor right after replay
    1741             :      but there may be frags in flight.  Votor needs to consume up to and
    1742             :      including the latest recorded replay message.  Votor also needs to
    1743             :      wait for all outgoing votes to drain as well, and for queued pool
    1744             :      events, which the pool decided with the old identity's rank. */
    1745             : 
    1746           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->id_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
    1747           0 :     if( FD_UNLIKELY( fd_seq_lt( ctx->replay_in_seq, fd_keyswitch_param_query( ctx->id_keyswitch ) ) ) ) return;
    1748             : 
    1749             :     /* Halt signing at first. */
    1750             : 
    1751           0 :     ctx->halt_signing = 1;
    1752             : 
    1753             :     /* If votes and pool events drained close quic conns and update
    1754             :        leader tracking.  The pool queue drains only under ctx->init,
    1755             :        so waiting on it while held never completes: a late ipecho
    1756             :        stalls there, and under WFS the boot ParentReady sits in
    1757             :        it for the whole wait.  Skipping it is safe because no vote
    1758             :        built while halted is signed, so none decided under the old
    1759             :        identity goes out. */
    1760             : 
    1761           0 :     if( FD_LIKELY( !ag_votor_metrics( ctx->votor ).vote_events_cnt &&
    1762           0 :                    ( !ctx->init || !ag_pool_metrics( ctx->pool ).pool_events_cnt ) ) ) {
    1763             : 
    1764             :       /* Save the votes drained since the halt as the old identity.  The
    1765             :          admin tile switches the sign tile only after this completes. */
    1766             : 
    1767           0 :       if( FD_LIKELY( ctx->vote_history_fd_dir!=-1 ) ) write_vote_history( ctx );
    1768             :       /* Leaving an identity saves its file as its .bak, and hands the
    1769             :          spare, the .bak votor holds, to the new identity as its staging
    1770             :          file.  The new identity's file is named only once it is written
    1771             :          at unhalt, so a restart before then finds no file and refuses to
    1772             :          boot rather than reading an empty one as never voted. */
    1773             : 
    1774           0 :       if( FD_LIKELY( ctx->vote_history_fd_dir!=-1 && memcmp( ctx->id_key.uc, ctx->id_keyswitch->bytes, sizeof(fd_pubkey_t) ) ) ) {
    1775           0 :         FD_BASE58_ENCODE_32_BYTES( ctx->id_key.uc,           id_key_b58 );
    1776           0 :         FD_BASE58_ENCODE_32_BYTES( ctx->id_keyswitch->bytes, switch_b58 );
    1777           0 :         FD_BASE58_ENCODE_32_BYTES( ctx->id_key_bak.uc,       spare_b58  );
    1778           0 :         char swp       [ PATH_MAX ];
    1779           0 :         char bin       [ PATH_MAX ];
    1780           0 :         char bak       [ PATH_MAX ];
    1781           0 :         char spare     [ PATH_MAX ];
    1782           0 :         char switch_swp[ PATH_MAX ];
    1783           0 :         char switch_bin[ PATH_MAX ];
    1784           0 :         char switch_bak[ PATH_MAX ];
    1785           0 :         FD_TEST( fd_cstr_printf_check( swp,        PATH_MAX, NULL, "vote_history-%s.bin.swp", id_key_b58 ) );
    1786           0 :         FD_TEST( fd_cstr_printf_check( bin,        PATH_MAX, NULL, "vote_history-%s.bin",     id_key_b58 ) );
    1787           0 :         FD_TEST( fd_cstr_printf_check( bak,        PATH_MAX, NULL, "vote_history-%s.bin.bak", id_key_b58 ) );
    1788           0 :         FD_TEST( fd_cstr_printf_check( spare,      PATH_MAX, NULL, "vote_history-%s.bin.bak", spare_b58  ) );
    1789           0 :         FD_TEST( fd_cstr_printf_check( switch_swp, PATH_MAX, NULL, "vote_history-%s.bin.swp", switch_b58 ) );
    1790           0 :         FD_TEST( fd_cstr_printf_check( switch_bin, PATH_MAX, NULL, "vote_history-%s.bin",     switch_b58 ) );
    1791           0 :         FD_TEST( fd_cstr_printf_check( switch_bak, PATH_MAX, NULL, "vote_history-%s.bin.bak", switch_b58 ) );
    1792           0 :         int spare_mine   = fd_pubkey_eq( &ctx->id_key_bak, &ctx->id_key );
    1793           0 :         int spare_switch = !memcmp( ctx->id_key_bak.uc, ctx->id_keyswitch->bytes, sizeof(fd_pubkey_t) );
    1794           0 :         if( FD_UNLIKELY( ctx->vote_history_bak_full && !spare_mine && !spare_switch ) ) {
    1795           0 :           FD_LOG_WARNING(( "reusing vote history file %s for %s.  Copy it out first to keep that identity's vote history", spare, switch_bin ));
    1796           0 :         }
    1797           0 :         FD_LOG_NOTICE(( "vote history file %s saved as %s", bin, bak ));
    1798           0 :         if( FD_UNLIKELY( !spare_switch && syscall( SYS_renameat2, ctx->vote_history_fd_dir, switch_bin, ctx->vote_history_fd_dir, switch_bak, 0 ) && errno!=ENOENT ) )
    1799           0 :           FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", switch_bin, switch_bak, errno, fd_io_strerror( errno ) ));
    1800           0 :         if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, spare, ctx->vote_history_fd_dir, switch_swp, 0 ) ) )
    1801           0 :           FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", spare, switch_swp, errno, fd_io_strerror( errno ) ));
    1802           0 :         if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->vote_history_fd_dir, bin, ctx->vote_history_fd_dir, bak, 0 ) ) )
    1803           0 :           FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", bin, bak, errno, fd_io_strerror( errno ) ));
    1804           0 :         int fd = ctx->vote_history_fd_bak;
    1805           0 :         ctx->vote_history_fd_bak   = ctx->vote_history_fd;
    1806           0 :         ctx->vote_history_fd       = ctx->vote_history_fd_swp;
    1807           0 :         ctx->vote_history_fd_swp   = fd;
    1808           0 :         ctx->id_key_bak            = ctx->id_key;
    1809           0 :         ctx->vote_history_bak_full = 1;
    1810           0 :         ctx->vote_history_pending  = 1;
    1811           0 :       }
    1812           0 :       memcpy( ctx->id_key.uc, ctx->id_keyswitch->bytes, sizeof(fd_pubkey_t) );
    1813           0 :       ctx->vote_history_slot         = fd_ulong_max( FD_LOAD( ulong, ctx->id_keyswitch->bytes+32UL ), ag_first_slot_in_window( ctx->highest_vote_or_lead_slot )+AG_SLOTS_PER_WINDOW );
    1814           0 :       ctx->highest_vote_or_lead_slot = fd_ulong_max( ctx->highest_vote_or_lead_slot, fd_ulong_sat_sub( ctx->vote_history_slot, 1UL ) );
    1815           0 :       fd_quic_set_identity_public_key( ctx->quic_client, ctx->id_key.uc );
    1816           0 :       fd_quic_set_identity_public_key( ctx->quic_server, ctx->id_key.uc );
    1817           0 :       for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1818           0 :         peer_t * peer = &ctx->peers[ slot ];
    1819           0 :         if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
    1820           0 :         if( FD_LIKELY( peer->tx_conn ) ) {
    1821           0 :           fd_quic_conn_set_context( peer->tx_conn, NULL );
    1822           0 :           fd_quic_conn_close( peer->tx_conn, 0U );
    1823           0 :           peer->tx_conn = NULL;
    1824           0 :         }
    1825           0 :       }
    1826             : 
    1827             :       /* Keep established server conns, which identify the peer, not us.
    1828             :          Close handshakes in progress, which are not yet rx_conns and
    1829             :          would complete as the old identity. */
    1830             : 
    1831           0 :       fd_quic_state_t * server_state = fd_quic_get_state( ctx->quic_server );
    1832           0 :       for( ulong i=0UL; i<ctx->quic_server->limits.conn_cnt; i++ ) {
    1833           0 :         fd_quic_conn_t * conn = fd_quic_conn_at_idx( server_state, i );
    1834           0 :         if( FD_LIKELY( conn->state!=FD_QUIC_CONN_STATE_HANDSHAKE && conn->state!=FD_QUIC_CONN_STATE_HANDSHAKE_COMPLETE ) ) continue;
    1835           0 :         fd_quic_conn_set_context( conn, NULL );
    1836           0 :         fd_quic_conn_close( conn, 0U );
    1837           0 :       }
    1838           0 :       for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) ctx->reward_votes[ i ].slot = ULONG_MAX;
    1839           0 :       for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) ctx->reward_certs[ i ] = (reward_cert_t){ .slot = ULONG_MAX };
    1840             : 
    1841             :       /* Skip the window in progress, it may already have a leader, and
    1842             :          those below the floor, which the identity may have voted or led
    1843             :          in here or on another machine. */
    1844             : 
    1845           0 :       ulong next_window = ag_first_slot_in_window( ctx->highest_parent_ready_slot )+AG_SLOTS_PER_WINDOW;
    1846           0 :       ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, fd_ulong_max( fd_ulong_max( ctx->curr_epoch_slot, next_window ), ctx->vote_history_slot ), &ctx->id_key );
    1847           0 :       fd_keyswitch_state( ctx->id_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1848           0 :     }
    1849           0 :   }
    1850             : 
    1851             :   /* Once we can resume signing load auth voters and reconn to peers.
    1852             :      Votor signs nothing in slots it already voted in. */
    1853             : 
    1854           0 :   if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->id_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
    1855           0 :     FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" );
    1856           0 :     load_keys( ctx, ctx->auth_vtr_path_cnt );
    1857           0 :     ctx->halt_signing = 0;
    1858           0 :     ctx->vote_history_slot = fd_ulong_max( ctx->vote_history_slot, ag_first_slot_in_window( ctx->highest_vote_or_lead_slot )+AG_SLOTS_PER_WINDOW );
    1859           0 :     if( FD_LIKELY( ctx->vote_history_fd_dir!=-1 ) ) write_vote_history( ctx ); /* the sign tile has the new key, so its floor is on disk before it votes */
    1860           0 :     update_own_rank( ctx );
    1861           0 :     update_own_bls_key( ctx );
    1862           0 :     ag_pool_standstill_iter_init( ctx->standstill_iter, ctx->pool ); /* its next vote was copied as the old identity */
    1863           0 :     for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    1864           0 :       peer_t * peer = &ctx->peers[ slot ];
    1865           0 :       if( FD_LIKELY( peers_key_inval( peer->id_key ) ) ) continue;
    1866           0 :       contact_info_t const * ci = contact_infos_query( ctx->contact_infos, peer->id_key, NULL );
    1867           0 :       if( FD_LIKELY( ci ) ) quic_client_connect( ctx, peer, ci, fd_clock_tile_now( ctx->clock ) );
    1868           0 :     }
    1869           0 :     fd_keyswitch_state( ctx->id_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
    1870           0 :   }
    1871           0 : }
    1872             : 
    1873             : static inline long
    1874           0 : next_deadline( fd_votor_tile_t * ctx ) {
    1875           0 :   long reconn = reconn_prq_cnt( ctx->reconn_prq ) ? ctx->reconn_prq[ 0 ].timeout : LONG_MAX;
    1876           0 :   long next   = fd_long_min( fd_long_min( fd_quic_get_next_wakeup( ctx->quic_client ), fd_quic_get_next_wakeup( ctx->quic_server ) ), reconn );
    1877           0 :   if( FD_LIKELY( ctx->init ) ) {
    1878           0 :     next = fd_long_min( next, ag_votor_next_skip_timeout( ctx->votor ) );
    1879           0 :     next = fd_long_min( next, ctx->standstill_ts );
    1880           0 :     for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) {
    1881           0 :       reward_vote_t const * rv = &ctx->reward_votes[ i ];
    1882           0 :       if( FD_LIKELY( rv->slot==ULONG_MAX || rv->retry_ts>=next ) ) continue;
    1883           0 :       next = rv->retry_ts;
    1884           0 :     }
    1885           0 :     for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) {
    1886           0 :       reward_cert_t const * rc = &ctx->reward_certs[ i ];
    1887           0 :       if( FD_LIKELY( rc->dirty ) ) next = fd_long_min( next, rc->publish_ts+REWARD_REPUBLISH_NS );
    1888           0 :     }
    1889           0 :   }
    1890           0 :   return next==LONG_MAX ? LONG_MAX : fd_clock_tile_wallclock_to_tickcount( ctx->clock, next );
    1891           0 : }
    1892             : 
    1893             : static void
    1894           0 : metrics_write( fd_votor_tile_t * ctx ) {
    1895           0 :   FD_MCNT_ENUM_COPY( VOTOR, DATAGRAM_RX, ctx->metrics.datagram_rx );
    1896           0 :   FD_MCNT_ENUM_COPY( VOTOR, VOTE_RX,     ctx->metrics.vote_rx     );
    1897           0 :   FD_MCNT_ENUM_COPY( VOTOR, CERT_RX,     ctx->metrics.cert_rx     );
    1898             : 
    1899           0 :   FD_MCNT_SET  ( VOTOR, VOTE_HISTORY_FILE_WRITTEN,    ctx->metrics.vote_history_write     );
    1900           0 :   FD_MGAUGE_SET( VOTOR, WAIT_TO_VOTE_SLOT,            ctx->vote_history_slot              );
    1901             : 
    1902           0 :   ag_votor_metrics_t votor_metrics = ag_votor_metrics( ctx->votor );
    1903           0 :   FD_MGAUGE_SET( VOTOR, VOTING_SLOT_USED,        votor_metrics.slot_state_pool_used );
    1904           0 :   FD_MGAUGE_SET( VOTOR, VOTING_SLOT_FREE,        votor_metrics.slot_state_pool_free );
    1905           0 :   FD_MGAUGE_SET( VOTOR, HIGHEST_FINAL_CERT_SLOT, fd_ulong_if( votor_metrics.highest_final_cert_slot!=ULONG_MAX, votor_metrics.highest_final_cert_slot, 0UL ) );
    1906             : 
    1907           0 :   ag_pool_metrics_t pool_metrics = ag_pool_metrics( ctx->pool );
    1908           0 :   FD_MGAUGE_SET( VOTOR, POOL_SLOT_USED,                    pool_metrics.slot_state_pool_used );
    1909           0 :   FD_MGAUGE_SET( VOTOR, POOL_SLOT_FREE,                    pool_metrics.slot_state_pool_free );
    1910           0 :   FD_MGAUGE_SET( VOTOR, HIGHEST_POOL_FINALIZED_SLOT,       fd_ulong_if( pool_metrics.finalized_slot!=ULONG_MAX, pool_metrics.finalized_slot, 0UL ) );
    1911           0 :   FD_MCNT_SET  ( VOTOR, BLS_SIGNATURE_CHECKED,             pool_metrics.bls_verify_cnt+ctx->metrics.bls_verify );
    1912           0 :   FD_MCNT_SET  ( VOTOR, LEADER_WINDOW_SUPERSEDED,          ctx->metrics.leader_window_superseded );
    1913           0 :   FD_MCNT_SET  ( VOTOR, CERT_TX_UNSTAKED_SKIPPED,          ctx->metrics.cert_tx_unstaked );
    1914             : 
    1915           0 :   peer_t const * self = peers_query( ctx->peers, ctx->id_key, NULL );
    1916           0 :   ulong rank = self && self->curr_rank!=USHORT_MAX ? self->curr_rank : ULONG_MAX;
    1917           0 :   FD_MGAUGE_SET( VOTOR, RANK, rank );
    1918             : 
    1919             : 
    1920             :   /* QUIC client (outbound conns) */
    1921             : 
    1922           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_PKT_RX_BYTES,                 ctx->quic_client->metrics.net_rx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic_client->metrics.net_rx_pkt_cnt );
    1923           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_FRAME_RX,                     ctx->quic_client->metrics.frame_rx_cnt            );
    1924           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_PKT_RX,                       ctx->quic_client->metrics.net_rx_pkt_cnt          );
    1925           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_STREAM_RX_BYTES,              ctx->quic_client->metrics.stream_rx_byte_cnt      );
    1926           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_STREAM_RX,                    ctx->quic_client->metrics.stream_rx_event_cnt     );
    1927           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_PKT_TX,                       ctx->quic_client->metrics.net_tx_pkt_cnt          );
    1928           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_PKT_TX_BYTES,                 ctx->quic_client->metrics.net_tx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic_client->metrics.net_tx_pkt_cnt );
    1929           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_ACK_PROCESSED,            ctx->quic_client->metrics.ack_tx                  );
    1930           0 :   FD_MGAUGE_ENUM_COPY( VOTOR, CLIENT_CONN_USED,                    (ctx->quic_client->metrics.conn_state_cnt+1)      ); /* skip FD_QUIC_CONN_STATE_INVALID (free slots) */
    1931           0 :   FD_MGAUGE_SET      ( VOTOR, CLIENT_CONN_FREE,                    ctx->quic_client->limits.conn_cnt-ctx->quic_client->metrics.conn_alloc_cnt );
    1932           0 :   FD_MGAUGE_SET      ( VOTOR, CLIENT_CONN_CAPACITY,                ctx->quic_client->limits.conn_cnt                 );
    1933           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_CREATED,                 ctx->quic_client->metrics.conn_created_cnt        );
    1934           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_CLOSED,                  ctx->quic_client->metrics.conn_closed_cnt         );
    1935           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_ABORTED,                 ctx->quic_client->metrics.conn_aborted_cnt        );
    1936           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_TIMED_OUT,               ctx->quic_client->metrics.conn_timeout_cnt        );
    1937           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_NO_SLOT_REJECTED,        ctx->quic_client->metrics.conn_err_no_slots_cnt   );
    1938           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_CONN_RETRY_REJECTED,          ctx->quic_client->metrics.conn_err_retry_fail_cnt );
    1939           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_DECRYPT_FAILED,           ctx->quic_client->metrics.pkt_decrypt_fail_cnt    );
    1940           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_NO_KEY_DROPPED,           ctx->quic_client->metrics.pkt_no_key_cnt          );
    1941           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_NO_CONN_DROPPED,          ctx->quic_client->metrics.pkt_no_conn_cnt         );
    1942           0 :   ulong pkt_drop_client[ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_CNT ] = {
    1943           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_UNDERSIZE_IDX           ] = ctx->quic_client->metrics.pkt_undersz_cnt,
    1944           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_OVERSIZE_IDX            ] = ctx->quic_client->metrics.pkt_oversz_cnt,
    1945           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_SRC_IP_MISMATCH_IDX     ] = ctx->quic_client->metrics.pkt_wrong_src_cnt,
    1946           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_HEADER_INVALID_IDX      ] = ctx->quic_client->metrics.pkt_quic_hdr_err_cnt,
    1947           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_NET_HEADER_INVALID_IDX  ] = ctx->quic_client->metrics.pkt_net_hdr_err_cnt,
    1948           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_VERSION_UNSUPPORTED_IDX ] = ctx->quic_client->metrics.pkt_verneg_cnt,
    1949           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_DUPLICATE_IDX           ] = ctx->quic_client->metrics.pkt_dup_cnt,
    1950           0 :   };
    1951           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_DROPPED,                  pkt_drop_client                             );
    1952           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_META_ACQUIRE_ATTEMPTED,   ctx->quic_client->metrics.frame_tx_alloc_cnt      );
    1953           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, CLIENT_PKT_RETRANSMITTED,            ctx->quic_client->metrics.pkt_retransmissions_cnt );
    1954           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_HANDSHAKE_CREATED,            ctx->quic_client->metrics.hs_created_cnt          );
    1955           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_HANDSHAKE_POOL_FULL_REJECTED, ctx->quic_client->metrics.hs_err_alloc_fail_cnt   );
    1956           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_HANDSHAKE_EVICTED,            ctx->quic_client->metrics.hs_evicted_cnt          );
    1957           0 :   FD_MCNT_SET        ( VOTOR, CLIENT_FRAME_PARSE_FAILED,           ctx->quic_client->metrics.frame_rx_err_cnt        );
    1958           0 :   FD_MHIST_COPY      ( VOTOR, CLIENT_SERVICE_DURATION_NANOS,       ctx->quic_client->metrics.service_duration        );
    1959           0 :   FD_MHIST_COPY      ( VOTOR, CLIENT_PKT_RX_DURATION_NANOS,        ctx->quic_client->metrics.receive_duration        );
    1960             : 
    1961             :   /* QUIC server (inbound conns) */
    1962             : 
    1963           0 :   FD_MCNT_SET        ( VOTOR, SERVER_PKT_RX_BYTES,                 ctx->quic_server->metrics.net_rx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic_server->metrics.net_rx_pkt_cnt );
    1964           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_FRAME_RX,                     ctx->quic_server->metrics.frame_rx_cnt            );
    1965           0 :   FD_MCNT_SET        ( VOTOR, SERVER_PKT_RX,                       ctx->quic_server->metrics.net_rx_pkt_cnt          );
    1966           0 :   FD_MCNT_SET        ( VOTOR, SERVER_STREAM_RX_BYTES,              ctx->quic_server->metrics.stream_rx_byte_cnt      );
    1967           0 :   FD_MCNT_SET        ( VOTOR, SERVER_STREAM_RX,                    ctx->quic_server->metrics.stream_rx_event_cnt     );
    1968           0 :   FD_MCNT_SET        ( VOTOR, SERVER_PKT_TX,                       ctx->quic_server->metrics.net_tx_pkt_cnt          );
    1969           0 :   FD_MCNT_SET        ( VOTOR, SERVER_PKT_TX_BYTES,                 ctx->quic_server->metrics.net_tx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic_server->metrics.net_tx_pkt_cnt );
    1970           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_ACK_PROCESSED,            ctx->quic_server->metrics.ack_tx                  );
    1971           0 :   FD_MGAUGE_ENUM_COPY( VOTOR, SERVER_CONN_USED,                    (ctx->quic_server->metrics.conn_state_cnt+1)      ); /* skip FD_QUIC_CONN_STATE_INVALID (free slots) */
    1972           0 :   FD_MGAUGE_SET      ( VOTOR, SERVER_CONN_FREE,                    ctx->quic_server->limits.conn_cnt-ctx->quic_server->metrics.conn_alloc_cnt );
    1973           0 :   FD_MGAUGE_SET      ( VOTOR, SERVER_CONN_CAPACITY,                ctx->quic_server->limits.conn_cnt                 );
    1974           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_CREATED,                 ctx->quic_server->metrics.conn_created_cnt        );
    1975           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_CLOSED,                  ctx->quic_server->metrics.conn_closed_cnt         );
    1976           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_ABORTED,                 ctx->quic_server->metrics.conn_aborted_cnt        );
    1977           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_TIMED_OUT,               ctx->quic_server->metrics.conn_timeout_cnt        );
    1978           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_NO_SLOT_REJECTED,        ctx->quic_server->metrics.conn_err_no_slots_cnt   );
    1979           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_RETRY_REJECTED,          ctx->quic_server->metrics.conn_err_retry_fail_cnt );
    1980           0 :   FD_MCNT_SET        ( VOTOR, SERVER_CONN_RETRY_EXPIRED_REJECTED,  ctx->quic_server->metrics.conn_err_retry_expired_cnt );
    1981           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_DECRYPT_FAILED,           ctx->quic_server->metrics.pkt_decrypt_fail_cnt    );
    1982           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_NO_KEY_DROPPED,           ctx->quic_server->metrics.pkt_no_key_cnt          );
    1983           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_NO_CONN_DROPPED,          ctx->quic_server->metrics.pkt_no_conn_cnt         );
    1984           0 :   ulong pkt_drop_server[ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_CNT ] = {
    1985           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_UNDERSIZE_IDX           ] = ctx->quic_server->metrics.pkt_undersz_cnt,
    1986           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_OVERSIZE_IDX            ] = ctx->quic_server->metrics.pkt_oversz_cnt,
    1987           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_SRC_IP_MISMATCH_IDX     ] = ctx->quic_server->metrics.pkt_wrong_src_cnt,
    1988           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_HEADER_INVALID_IDX      ] = ctx->quic_server->metrics.pkt_quic_hdr_err_cnt,
    1989           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_NET_HEADER_INVALID_IDX  ] = ctx->quic_server->metrics.pkt_net_hdr_err_cnt,
    1990           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_VERSION_UNSUPPORTED_IDX ] = ctx->quic_server->metrics.pkt_verneg_cnt,
    1991           0 :     [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_DUPLICATE_IDX           ] = ctx->quic_server->metrics.pkt_dup_cnt,
    1992           0 :   };
    1993           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_DROPPED,                  pkt_drop_server                             );
    1994           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_META_ACQUIRE_ATTEMPTED,   ctx->quic_server->metrics.frame_tx_alloc_cnt      );
    1995           0 :   FD_MCNT_ENUM_COPY  ( VOTOR, SERVER_PKT_RETRANSMITTED,            ctx->quic_server->metrics.pkt_retransmissions_cnt );
    1996           0 :   FD_MCNT_SET        ( VOTOR, SERVER_HANDSHAKE_CREATED,            ctx->quic_server->metrics.hs_created_cnt          );
    1997           0 :   FD_MCNT_SET        ( VOTOR, SERVER_HANDSHAKE_POOL_FULL_REJECTED, ctx->quic_server->metrics.hs_err_alloc_fail_cnt   );
    1998           0 :   FD_MCNT_SET        ( VOTOR, SERVER_HANDSHAKE_EVICTED,            ctx->quic_server->metrics.hs_evicted_cnt          );
    1999           0 :   FD_MCNT_SET        ( VOTOR, SERVER_FRAME_PARSE_FAILED,           ctx->quic_server->metrics.frame_rx_err_cnt        );
    2000           0 :   FD_MHIST_COPY      ( VOTOR, SERVER_SERVICE_DURATION_NANOS,       ctx->quic_server->metrics.service_duration        );
    2001           0 :   FD_MHIST_COPY      ( VOTOR, SERVER_PKT_RX_DURATION_NANOS,        ctx->quic_server->metrics.receive_duration        );
    2002           0 : }
    2003             : 
    2004             : /* cert_tx_skip returns 1 if a cert or standstill vote for slot should
    2005             :    not be sent: stale while catching up, or we are unstaked in its epoch
    2006             :    (peers only accept staked conns). */
    2007             : 
    2008             : static int
    2009             : cert_tx_skip( fd_votor_tile_t * ctx,
    2010           0 :               ulong             slot ) {
    2011           0 :   if( FD_UNLIKELY( slot<ctx->catchup_vote_slot ) ) return 1;
    2012           0 :   peer_t const * self = peers_query( ctx->peers, ctx->id_key, NULL );
    2013           0 :   if( FD_UNLIKELY( !self || USHORT_MAX==fd_ushort_if( slot>=ctx->next_epoch_slot, self->next_rank, fd_ushort_if( slot>=ctx->curr_epoch_slot, self->curr_rank, self->prev_rank ) ) ) ) {
    2014           0 :     ctx->metrics.cert_tx_unstaked++;
    2015           0 :     return 1;
    2016           0 :   }
    2017           0 :   return 0;
    2018           0 : }
    2019             : 
    2020             : /* after_credit is the tile's event loop.  Each step handles at most
    2021             :    one item per pass, at most 9 votor_out frags in all:
    2022             : 
    2023             :    1. notify if we can become leader
    2024             :    2. retry un-acked reward votes
    2025             :    3. republish reward certs new votes widened (one per tile loop)
    2026             :    4. poll pool, repair, timeout events (can result in new votes, one per tile loop)
    2027             :    5. broadcast new votes (one per tile loop)
    2028             :    6. standstill re-broadcast */
    2029             : 
    2030             : static inline void
    2031             : after_credit( fd_votor_tile_t *   ctx,
    2032             :               fd_stem_context_t * stem,
    2033             :               int *               opt_poll_in,
    2034           0 :               int *               charge_busy ) {
    2035             : 
    2036           0 :   long now = fd_clock_tile_now( ctx->clock );
    2037             : 
    2038             :   /* Reconnect any dropped QUIC conns. */
    2039             : 
    2040           0 :   while( FD_UNLIKELY( reconn_prq_cnt( ctx->reconn_prq ) && ctx->reconn_prq[ 0 ].timeout<=now ) ) {
    2041           0 :     fd_pubkey_t id_key = ctx->reconn_prq[ 0 ].id_key;
    2042           0 :     reconn_prq_remove_min( ctx->reconn_prq );
    2043           0 :     peer_t * peer = peers_query( ctx->peers, id_key, NULL );
    2044           0 :     if( FD_UNLIKELY( !peer ) ) continue;
    2045           0 :     peer->reconn_pending = 0;
    2046           0 :     contact_info_t const * ci = contact_infos_query( ctx->contact_infos, id_key, NULL );
    2047           0 :     if( FD_UNLIKELY( !ci ) ) continue;
    2048           0 :     if( FD_UNLIKELY( now<peer->conn_ts+peer->conn_backoff ) ) {
    2049           0 :       reconn_t reconn = { .timeout = peer->conn_ts+peer->conn_backoff, .id_key = id_key };
    2050           0 :       reconn_prq_insert( ctx->reconn_prq, &reconn );
    2051           0 :       peer->reconn_pending = 1;
    2052           0 :       continue;
    2053           0 :     }
    2054           0 :     quic_client_connect( ctx, peer, ci, now );
    2055           0 :     *charge_busy = 1;
    2056           0 :   }
    2057             : 
    2058             :   /* Service both QUIC client and server. */
    2059             : 
    2060           0 :   *charge_busy = fd_quic_service( ctx->quic_client, now ) | fd_quic_service( ctx->quic_server, now );
    2061           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 );
    2062           0 :   ctx->net_tx_cnt = 0UL;
    2063             : 
    2064             :   /* Not initialized. */
    2065             : 
    2066           0 :   if( FD_UNLIKELY( !ctx->init ) ) return;
    2067             : 
    2068             :   /* 1. Notify if we can become leader. */
    2069             : 
    2070           0 :   while( FD_UNLIKELY(    ctx->next_leader_slot!=ULONG_MAX
    2071           0 :                       && ( ctx->next_leader_slot<=ag_pool_finalized_slot( ctx->pool )
    2072           0 :                         || ctx->next_leader_slot+ctx->slot_max<=ctx->catchup_slot
    2073           0 :                         || ctx->next_leader_slot<ctx->vote_history_slot
    2074           0 :                         || ctx->next_leader_slot<ag_pool_highest_parent_ready( ctx->pool ) ) ) ) {
    2075           0 :     ctx->metrics.leader_window_superseded += (ulong)( ctx->next_leader_slot<ag_pool_highest_parent_ready( ctx->pool ) );
    2076           0 :     ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, ag_first_slot_in_window( ctx->next_leader_slot )+AG_SLOTS_PER_WINDOW, &ctx->id_key );
    2077           0 :   }
    2078           0 :   ag_block_id_t parent = { .slot = ULONG_MAX }; /* never will be leader, schedule exhausted, or the pool has not granted parent ready yet */
    2079           0 :   if( FD_LIKELY( ctx->next_leader_slot!=ULONG_MAX ) ) parent = ag_pool_wait_for_parent_ready( ctx->pool, ctx->next_leader_slot );
    2080           0 :   if( FD_UNLIKELY( parent.slot!=ULONG_MAX && !ctx->halt_signing ) ) {
    2081           0 :     ctx->highest_vote_or_lead_slot = fd_ulong_max( ctx->highest_vote_or_lead_slot, ag_first_slot_in_window( ctx->next_leader_slot )+AG_SLOTS_PER_WINDOW-1UL );
    2082           0 :     if( FD_LIKELY( ctx->vote_history_fd_dir!=-1 ) ) write_vote_history( ctx ); /* a restart does not lead the window again */
    2083           0 :     fd_votor_msg_t * chunk = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    2084           0 :     chunk->leader = (fd_votor_leader_t){ .slot = ctx->next_leader_slot, .parent_slot = parent.slot, .parent_block_id = FD_LOAD( fd_hash_t, parent.hash ), .parent_ready_ns = now };
    2085           0 :     fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_LEADER, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2086           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 );
    2087             : 
    2088           0 :     ulong reward_slot = fd_ulong_sat_sub( ctx->next_leader_slot, FD_NUM_SLOTS_FOR_REWARD );
    2089           0 :     for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) publish_reward_cert( ctx, stem, reward_slot+i );
    2090             : 
    2091           0 :     ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, ag_first_slot_in_window( ctx->next_leader_slot )+AG_SLOTS_PER_WINDOW, &ctx->id_key );
    2092           0 :     *charge_busy = 1;
    2093           0 :   }
    2094             : 
    2095             :   /* 2. Retry un-acked reward votes. */
    2096             : 
    2097           0 :   for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) {
    2098           0 :     reward_vote_t * rv = &ctx->reward_votes[ i ];
    2099           0 :     if( FD_LIKELY( rv->slot==ULONG_MAX || now<rv->retry_ts ) ) continue;
    2100           0 :     if( FD_UNLIKELY( rv->tx_cnt==REWARD_VOTE_TX_MAX ) ) { rv->slot = ULONG_MAX; continue; } /* slot+8's footer has sealed */
    2101           0 :     peer_t const * peer = query_reward_leader( ctx, rv );
    2102           0 :     if( FD_UNLIKELY( !peer ) ) {
    2103           0 :       fd_pubkey_t const *    leader_key = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, rv->slot+AG_REWARD_SLOT_DELTA );
    2104           0 :       peer_t *               leader     = leader_key ? peers_query( ctx->peers, *leader_key, NULL ) : NULL;
    2105           0 :       contact_info_t const * ci         = leader ? contact_infos_query( ctx->contact_infos, leader->id_key, NULL ) : NULL;
    2106           0 :       if( FD_LIKELY( ci && now>=leader->conn_ts+REWARD_VOTE_RTT_MAX_NS ) ) quic_client_connect( ctx, leader, ci, now ); /* skip the reconnect backoff, the reward vote is only useful until we replay slot+8 */
    2107           0 :       rv->retry_ts = now+REWARD_VOTE_RTT_MIN_NS;
    2108           0 :       continue;
    2109           0 :     }
    2110           0 :     reward_vote_tx( ctx, stem, rv, peer, now );
    2111           0 :     *charge_busy = 1;
    2112           0 :   }
    2113             : 
    2114             :   /* 3. Republish reward certs new votes widened. */
    2115             : 
    2116           0 :   for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) {
    2117           0 :     reward_cert_t * rc = &ctx->reward_certs[ i ];
    2118           0 :     if( FD_LIKELY( !rc->dirty || now<rc->publish_ts+REWARD_REPUBLISH_NS ) ) continue;
    2119           0 :     if( FD_UNLIKELY( ctx->highest_parent_ready_slot>=ag_first_slot_in_window( rc->slot+AG_REWARD_SLOT_DELTA )+AG_SLOTS_PER_WINDOW ) ) { rc->slot = ULONG_MAX; rc->dirty = 0; continue; } /* our leader window is over */
    2120           0 :     publish_reward_cert( ctx, stem, rc->slot );
    2121           0 :     *charge_busy = 1;
    2122           0 :     break;
    2123           0 :   }
    2124             : 
    2125             :   /* 4a. Poll pool events. */
    2126             : 
    2127           0 :   int pool_event = ag_pool_poll_pool_event( ctx->pool, &ctx->scratch.pool_event );
    2128           0 :   if( FD_UNLIKELY( pool_event ) ) {
    2129           0 :     ag_pool_event_t const * event = &ctx->scratch.pool_event;
    2130           0 :     ag_cert_t const * broadcast = NULL;
    2131           0 :     if( FD_LIKELY( event->kind!=AG_POOL_EVENT_IMPLICITLY_FINALIZED && event->kind!=AG_POOL_EVENT_IMPLICITLY_SKIPPED ) ) broadcast = ag_votor_handle_pool_event( ctx->votor, event, now );
    2132           0 :     if( FD_UNLIKELY( broadcast && !cert_tx_skip( ctx, ag_cert_slot( broadcast ) ) ) ) {
    2133           0 :       ulong ser_sz               = ag_cert_ser( broadcast, ctx->scratch.ser );
    2134           0 :       long  broadcast_start_time = fd_clock_tile_now( ctx->clock );
    2135           0 :       for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    2136           0 :         peer_t const * peer = &ctx->peers[ slot ];
    2137           0 :         if( FD_LIKELY( peers_key_inval( peer->id_key ) || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) continue;
    2138           0 :         quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
    2139           0 :       }
    2140             : 
    2141           0 :       ulong stake;
    2142           0 :       switch( broadcast->kind ) {
    2143           0 :       case AG_CERT_KIND_FINAL:          stake = broadcast->final.stake;          break;
    2144           0 :       case AG_CERT_KIND_FAST_FINAL:     stake = broadcast->fast_final.stake;     break;
    2145           0 :       case AG_CERT_KIND_NOTAR:          stake = broadcast->notar.stake;          break;
    2146           0 :       case AG_CERT_KIND_NOTAR_FALLBACK: stake = broadcast->notar_fallback.stake; break;
    2147           0 :       case AG_CERT_KIND_SKIP:           stake = broadcast->skip.stake;           break;
    2148           0 :       default:                          FD_LOG_CRIT(( "unreachable" ));
    2149           0 :       }
    2150           0 :       if( FD_UNLIKELY( stake ) ) report_alpenglow_cert( ctx, NULL, broadcast, ctx->scratch.ser[ 1 ], FD_EVENT_ALPENGLOW_CERT_PROCESSING_RESULT_ACCEPTED, 0L, broadcast_start_time );
    2151           0 :     }
    2152             : 
    2153           0 :     if( FD_UNLIKELY( event->kind==AG_POOL_EVENT_SAFE_TO_NOTAR || event->kind==AG_POOL_EVENT_IMPLICITLY_FINALIZED || event->kind==AG_POOL_EVENT_IMPLICITLY_SKIPPED ) ) {
    2154           0 :       fd_votor_quorum_t * quorum = &((fd_votor_msg_t *)fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk ))->quorum;
    2155           0 :       *quorum = (fd_votor_quorum_t){0};
    2156           0 :       switch( event->kind ) {
    2157           0 :       case AG_POOL_EVENT_SAFE_TO_NOTAR:
    2158           0 :         quorum->kind     = FD_VOTOR_QUORUM_KIND_SAFE_TO_NOTAR;
    2159           0 :         quorum->slot     = event->safe_to_notar.slot;
    2160           0 :         quorum->block_id = FD_LOAD( fd_hash_t, event->safe_to_notar.hash );
    2161           0 :         break;
    2162           0 :       case AG_POOL_EVENT_IMPLICITLY_FINALIZED:
    2163           0 :         quorum->kind     = FD_VOTOR_QUORUM_KIND_IMPLICITLY_FINALIZED;
    2164           0 :         quorum->slot     = event->implicitly_finalized.slot;
    2165           0 :         quorum->block_id = FD_LOAD( fd_hash_t, event->implicitly_finalized.hash );
    2166           0 :         break;
    2167           0 :       case AG_POOL_EVENT_IMPLICITLY_SKIPPED:
    2168           0 :         quorum->kind = FD_VOTOR_QUORUM_KIND_IMPLICITLY_SKIPPED;
    2169           0 :         quorum->slot = event->implicitly_skipped;
    2170           0 :         break;
    2171           0 :       default:
    2172           0 :         FD_LOG_CRIT(( "unreachable" ));
    2173           0 :       }
    2174           0 :       fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_QUORUM, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2175           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 );
    2176           0 :     }
    2177           0 :     if( FD_UNLIKELY( ctx->scratch.pool_event.kind==AG_POOL_EVENT_PARENT_READY && ctx->scratch.pool_event.parent_ready.slot>ctx->highest_parent_ready_slot ) ) {
    2178           0 :       ctx->highest_parent_ready_slot = ctx->scratch.pool_event.parent_ready.slot;
    2179           0 :       fd_votor_msg_t * msg = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    2180           0 :       msg->parent_ready = (fd_votor_parent_ready_t){ .slot = ctx->highest_parent_ready_slot }; /* a LEADER already sent may now be stale */
    2181           0 :       fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_PARENT_READY, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2182           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 );
    2183           0 :     }
    2184             : 
    2185           0 :     ag_cert_t const * cert = &ctx->scratch.pool_event.cert_created;
    2186           0 :     if( FD_UNLIKELY( ctx->scratch.pool_event.kind==AG_POOL_EVENT_CERT_CREATED ) ) {
    2187           0 :       ulong                   slot  = ag_cert_slot( cert );
    2188           0 :       ag_slot_state_t const * state = ag_pool_slot_state( ctx->pool, slot );
    2189           0 :       fd_votor_msg_t *        chunk = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    2190           0 :       fd_votor_certed_t *     certed = &chunk->certed;
    2191           0 :       switch( cert->kind ) {
    2192           0 :       case AG_CERT_KIND_FINAL:
    2193           0 :         if( FD_LIKELY( state && state->certs.notar.slot != ULONG_MAX && slot>ctx->highest_certed_final_slot ) ) {
    2194           0 :           *certed = (fd_votor_certed_t){ .kind = cert->kind, .slot = slot, .block_id = FD_LOAD( fd_hash_t, state->certs.notar.block_hash ), .agg = state->certs.finalize.agg, .agg2 = state->certs.notar.agg };
    2195           0 :           fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2196           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 );
    2197           0 :           ctx->highest_certed_final_slot = slot;
    2198           0 :         }
    2199           0 :         break;
    2200           0 :       case AG_CERT_KIND_FAST_FINAL:
    2201           0 :         *certed = (fd_votor_certed_t){ .kind = cert->kind, .slot = slot, .block_id = FD_LOAD( fd_hash_t, cert->fast_final.block_hash ), .agg = cert->fast_final.agg };
    2202           0 :         fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2203           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 );
    2204           0 :         break;
    2205           0 :       case AG_CERT_KIND_NOTAR:
    2206           0 :         *certed = (fd_votor_certed_t){ .kind = cert->kind, .slot = slot, .block_id = FD_LOAD( fd_hash_t, cert->notar.block_hash ), .agg = cert->notar.agg };
    2207           0 :         fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2208           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 );
    2209             : 
    2210             : 
    2211           0 :         if( FD_UNLIKELY( state && state->certs.finalize.slot != ULONG_MAX && slot>ctx->highest_certed_final_slot ) ) {
    2212           0 :           chunk   = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    2213           0 :           certed  = &chunk->certed;
    2214           0 :           *certed = (fd_votor_certed_t){ .kind = AG_CERT_KIND_FINAL, .slot = slot, .block_id = FD_LOAD( fd_hash_t, cert->notar.block_hash ), .agg = state->certs.finalize.agg, .agg2 = cert->notar.agg };
    2215           0 :           fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2216           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 );
    2217           0 :           ctx->highest_certed_final_slot = slot;
    2218           0 :         }
    2219           0 :         break;
    2220           0 :       case AG_CERT_KIND_NOTAR_FALLBACK:
    2221           0 :         *certed = (fd_votor_certed_t){ .kind = cert->kind, .slot = slot, .block_id = FD_LOAD( fd_hash_t, cert->notar_fallback.block_hash ), .agg = cert->notar_fallback.agg_notar, .agg2 = cert->notar_fallback.agg_notar_fallback };
    2222           0 :         fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2223           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 );
    2224           0 :         break;
    2225           0 :       case AG_CERT_KIND_SKIP:
    2226           0 :         *certed = (fd_votor_certed_t){ .kind = cert->kind, .slot = slot, .agg = cert->skip.agg_skip, .agg2 = cert->skip.agg_skip_fallback };
    2227           0 :         fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_CERTED, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2228           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 );
    2229           0 :         break;
    2230           0 :       default:
    2231           0 :         FD_LOG_CRIT(( "unreachable" ));
    2232           0 :       }
    2233           0 :     }
    2234           0 :     *opt_poll_in = 0;
    2235           0 :     *charge_busy = 1;
    2236           0 :   }
    2237             : 
    2238             :   /* 4b. Poll repair events. */
    2239             : 
    2240           0 :   int repair_event = ag_pool_poll_repair_event( ctx->pool, &ctx->scratch.repair_event );
    2241           0 :   if( FD_UNLIKELY( repair_event ) ) {
    2242           0 :     fd_votor_msg_t * chunk = fd_chunk_to_laddr( ctx->votor_out_mem, ctx->votor_out_chunk );
    2243           0 :     chunk->repair = (fd_votor_repair_t){ .slot = ctx->scratch.repair_event.slot, .block_id = FD_LOAD( fd_hash_t, ctx->scratch.repair_event.hash ) };
    2244           0 :     fd_stem_publish( stem, OUT_IDX_VOTOR, FD_VOTOR_SIG_REPAIR, ctx->votor_out_chunk, sizeof(fd_votor_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
    2245           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 );
    2246           0 :     *opt_poll_in = 0;
    2247           0 :     *charge_busy = 1;
    2248           0 :   }
    2249             : 
    2250             : 
    2251             :   /* 4c. Poll skip timeouts. */
    2252             : 
    2253           0 :   ulong timeout_slot;
    2254           0 :   if( FD_UNLIKELY( ag_votor_poll_skip_timeout( ctx->votor, now, &timeout_slot ) ) ) { /* a timeout we set on ParentReady */
    2255           0 :     ag_votor_handle_skip_timeout( ctx->votor, timeout_slot );
    2256           0 :     *opt_poll_in = 0;
    2257           0 :     *charge_busy = 1;
    2258           0 :   }
    2259             : 
    2260             :   /* 5. Broadcast new votes. */
    2261             : 
    2262           0 :   uchar reason;
    2263           0 :   if( FD_UNLIKELY( !pool_event && !repair_event && ag_votor_poll_vote( ctx->votor, &ctx->scratch.vote, &reason ) ) ) {
    2264           0 :     ulong                   vote_slot  = ag_vote_slot( &ctx->scratch.vote );
    2265           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 ) );
    2266           0 :     ulong                   rank       = ag_vote_rank( &ctx->scratch.vote );
    2267           0 :     ctx->highest_vote_or_lead_slot = fd_ulong_max( ctx->highest_vote_or_lead_slot, vote_slot );
    2268           0 :     if( FD_LIKELY( epoch_info && rank<epoch_info->validator_cnt && vote_slot>=ctx->vote_history_slot && vote_slot>=ctx->catchup_vote_slot && !blst_p2_affine_is_inf( ag_vote_sig( &ctx->scratch.vote ) ) ) ) {
    2269           0 :       if( FD_LIKELY( ctx->vote_history_fd_dir!=-1 ) ) write_vote_history( ctx );
    2270           0 :       ulong ser_sz               = ag_vote_ser( &ctx->scratch.vote, ctx->scratch.ser );
    2271           0 :       long  broadcast_start_time = fd_clock_tile_now( ctx->clock );
    2272             : 
    2273           0 :       uint                kind                 = ctx->scratch.vote.kind;
    2274           0 :       fd_pubkey_t const * reward_leader_pubkey = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, vote_slot+AG_REWARD_SLOT_DELTA );
    2275           0 :       peer_t const *      reward_leader        = NULL;
    2276           0 :       if( FD_LIKELY(    reward_leader_pubkey
    2277           0 :                      && !fd_pubkey_eq( reward_leader_pubkey, &ctx->id_key )
    2278           0 :                      && (kind==AG_VOTE_KIND_NOTAR || kind==AG_VOTE_KIND_SKIP) ) ) {
    2279           0 :         reward_leader = peers_query( ctx->peers, *reward_leader_pubkey, NULL );
    2280           0 :       }
    2281           0 :       if( FD_LIKELY( reward_leader ) ) {
    2282           0 :         reward_vote_t * rv = &ctx->reward_votes[ vote_slot%REWARD_VOTE_MAX ];
    2283           0 :         rv->slot     = vote_slot;
    2284           0 :         rv->retry_ts = now;
    2285           0 :         rv->backoff  = 0L;
    2286           0 :         rv->tx_cnt   = 0UL;
    2287           0 :         rv->conn     = NULL;
    2288           0 :         rv->vote     = ctx->scratch.vote;
    2289           0 :         peer_t const * peer = query_reward_leader( ctx, rv );
    2290           0 :         if( FD_LIKELY( peer ) ) reward_vote_tx( ctx, stem, rv, peer, now ); /* the reward leader first, tracked so its ACK clears the retry */
    2291           0 :       }
    2292             : 
    2293           0 :       for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    2294           0 :         peer_t const * peer = &ctx->peers[ slot ];
    2295           0 :         if( FD_LIKELY(    peers_key_inval( peer->id_key )
    2296           0 :                        || !peer->tx_conn
    2297           0 :                        || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE
    2298           0 :                        || peer==reward_leader ) ) continue;
    2299           0 :         quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
    2300           0 :       }
    2301             : 
    2302           0 :       long  aggregation_start_time = fd_clock_tile_now( ctx->clock );
    2303           0 :       uchar quorum_reached;
    2304           0 :       int   err = ag_pool_add_vote( ctx->pool, &ctx->scratch.vote, 1, ctx->scratch.bad, &quorum_reached );
    2305           0 :       if( FD_UNLIKELY( !fd_bls_set_is_null( ctx->scratch.bad ) ) ) ban_bad_ranks( ctx, ctx->scratch.bad, vote_slot );
    2306           0 :       reward_cert_t * rc = &ctx->reward_certs[ vote_slot%AG_SLOTS_PER_WINDOW ];
    2307           0 :       if( FD_UNLIKELY( err==AG_POOL_SUCCESS && rc->slot==vote_slot && ( ctx->scratch.vote.kind==AG_VOTE_KIND_NOTAR || ctx->scratch.vote.kind==AG_VOTE_KIND_SKIP ) ) ) rc->dirty = 1;
    2308             : 
    2309           0 :       int result;
    2310           0 :       switch( err ) {
    2311           0 :       case AG_POOL_SUCCESS:                result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_ACCEPTED;                                                                                                                         break;
    2312           0 :       case AG_POOL_ERR_SLOT_TOO_OLD:       result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLOT_TOO_OLD;                                                                                                                    break;
    2313           0 :       case AG_POOL_ERR_SLOT_TOO_NEW:       result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLOT_TOO_NEW;                                                                                                                    break;
    2314           0 :       case AG_POOL_ERR_DUPLICATE:          result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_DUPLICATE;                                                                                                                        break;
    2315           0 :       case AG_POOL_ERR_SLASHABLE:          result = FD_EVENT_ALPENGLOW_VOTE_PROCESSING_RESULT_SLASHABLE;                                                                                                                        break;
    2316           0 :       default:
    2317           0 :         FD_LOG_CRIT(( "unhandled kind" ));
    2318           0 :       }
    2319           0 :       report_alpenglow_vote( ctx, NULL, &ctx->scratch.vote, ctx->scratch.ser[ 1 ], result, quorum_reached, FD_EVENT_ALPENGLOW_VOTE_REASON_BLOCK_REPLAYED+reason, aggregation_start_time, broadcast_start_time, reward_leader );
    2320           0 :       if( FD_UNLIKELY( reward_leader && ( !reward_leader->tx_conn || reward_leader->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) ) report_alpenglow_vote( ctx, NULL, &ctx->scratch.vote, ctx->scratch.ser[ 1 ], result, 0, FD_EVENT_ALPENGLOW_VOTE_REASON_REWARD_NO_CONNECTION, 0L, 0L, reward_leader );
    2321           0 :     }
    2322           0 :     *opt_poll_in = 0;
    2323           0 :     *charge_busy = 1;
    2324           0 :   }
    2325             : 
    2326             :   /* 6. Standstill re-broadcast.  Unarmed until the first pass votor
    2327             :      runs: under WFS that is the restart, not the boot. */
    2328             : 
    2329           0 :   ulong finalized_slot = ag_pool_finalized_slot( ctx->pool );
    2330           0 :   if( FD_UNLIKELY( finalized_slot>ctx->standstill_slot || ctx->standstill_ts==LONG_MAX ) ) {
    2331           0 :     ctx->standstill_slot = finalized_slot;
    2332           0 :     ctx->standstill_ts   = now+AG_DELTA_STANDSTILL_NS;
    2333           0 :   }
    2334           0 :   if( FD_LIKELY( now<ctx->standstill_ts ) ) return;
    2335             : 
    2336           0 :   ag_pool_standstill_iter_t * standstill_iter = ctx->standstill_iter;
    2337           0 :   if( FD_UNLIKELY( standstill_iter->finalized!=finalized_slot ) ) FD_LOG_WARNING(( "standstill at finalized slot %lu, re-broadcasting", finalized_slot ));
    2338           0 :   if( FD_UNLIKELY( ag_pool_standstill_iter_done( standstill_iter ) || standstill_iter->finalized!=finalized_slot ) ) ag_pool_standstill_iter_init( standstill_iter, ctx->pool );
    2339           0 :   if( FD_LIKELY( !ag_pool_standstill_iter_done( standstill_iter ) && cert_tx_skip( ctx, standstill_iter->is_vote ? ag_vote_slot( &standstill_iter->vote ) : ag_cert_slot( &standstill_iter->cert ) ) ) ) {
    2340           0 :     ag_pool_standstill_iter_next( standstill_iter, ctx->pool );
    2341           0 :   } else if( FD_LIKELY( !ag_pool_standstill_iter_done( standstill_iter ) ) ) {
    2342           0 :     ulong ser_sz = standstill_iter->is_vote ? ag_vote_ser( &standstill_iter->vote, ctx->scratch.ser ) : ag_cert_ser( &standstill_iter->cert, ctx->scratch.ser );
    2343           0 :     for( ulong slot=0UL; slot<peers_slot_cnt( ctx->peers ); slot++ ) {
    2344           0 :       peer_t const * peer = &ctx->peers[ slot ];
    2345           0 :       if( FD_LIKELY( peers_key_inval( peer->id_key ) || !peer->tx_conn || peer->tx_conn->state!=FD_QUIC_CONN_STATE_ACTIVE ) ) continue;
    2346           0 :       quic_client_datagram_tx( ctx, stem, peer->tx_conn, ctx->scratch.ser, ser_sz );
    2347           0 :     }
    2348           0 :     ag_pool_standstill_iter_next( standstill_iter, ctx->pool );
    2349           0 :     *charge_busy = 1;
    2350           0 :   }
    2351           0 :   ctx->standstill_ts = now+fd_long_if( ag_pool_standstill_iter_done( standstill_iter ), STANDSTILL_CYCLE_INTERVAL_NS, STANDSTILL_TX_INTERVAL_NS );
    2352           0 : }
    2353             : 
    2354             : static int
    2355             : before_frag( fd_votor_tile_t * ctx,
    2356             :              ulong             in_idx,
    2357             :              ulong             seq,
    2358           0 :              ulong             sig ) {
    2359           0 :   switch( ctx->in_kind[ in_idx ] ) {
    2360           0 :   case IN_KIND_EPOCH:
    2361           0 :     return 0;
    2362           0 :   case IN_KIND_GOSSIP:
    2363           0 :     return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
    2364           0 :   case IN_KIND_IPECHO:
    2365           0 :     return 0;
    2366           0 :   case IN_KIND_NET:
    2367           0 :     if( FD_UNLIKELY( !ctx->curr_epoch_info || ctx->halt_signing ) ) return 1; /* halted, no TLS handshake may sign */
    2368           0 :     return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_VOTOR;
    2369           0 :   case IN_KIND_REPLAY:
    2370           0 :     if( FD_UNLIKELY( sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_SLOT_DEAD ) ) {
    2371             :       /* Handled here, not accepted: do not backpressure replay_slot. */
    2372           0 :       if( FD_UNLIKELY( sig==REPLAY_SIG_WFS_DONE && !ctx->wfs_signalled ) ) {
    2373           0 :         ctx->wfs_signalled = 1;
    2374           0 :         FD_LOG_NOTICE(( "wait for supermajority complete, resuming" ));
    2375           0 :         if( FD_LIKELY( votor_wfs_is_green( ctx ) && ag_pool_finalized_slot( ctx->pool )!=ULONG_MAX && ctx->shred_version ) ) {
    2376           0 :           ag_votor_arm_skip_timeouts( ctx->votor, fd_clock_tile_now( ctx->clock ) );
    2377           0 :           ctx->init = !!ctx->next_epoch_info;
    2378           0 :         }
    2379           0 :       }
    2380           0 :       ctx->replay_in_seq = seq+1UL;
    2381           0 :       return 1;
    2382           0 :     }
    2383           0 :     if( FD_UNLIKELY( !ctx->curr_epoch_info ) ) return -1;
    2384           0 :     ctx->replay_in_seq = seq+1UL;
    2385           0 :     return 0;
    2386           0 :   default:
    2387           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    2388           0 :   }
    2389           0 : }
    2390             : 
    2391             : static void
    2392             : during_frag( fd_votor_tile_t * ctx,
    2393             :              ulong             in_idx,
    2394             :              ulong             seq,
    2395             :              ulong             sig,
    2396             :              ulong             chunk,
    2397             :              ulong             sz,
    2398           0 :              ulong             ctl ) {
    2399           0 :   (void)seq;
    2400             : 
    2401           0 :   switch( ctx->in_kind[ in_idx ] ) {
    2402           0 :   case IN_KIND_EPOCH:
    2403           0 :     handle_epoch( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    2404           0 :     break;
    2405           0 :   case IN_KIND_GOSSIP:
    2406           0 :     handle_gossip( ctx, sig, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    2407           0 :     break;
    2408           0 :   case IN_KIND_IPECHO:
    2409             :     /* unreliable link, handled in after_frag */
    2410           0 :     break;
    2411           0 :   case IN_KIND_NET:
    2412           0 :     fd_memcpy( ctx->net_buf, fd_net_rx_translate_frag( &ctx->net_in_bounds[ in_idx ], chunk, ctl, sz ), sz );
    2413           0 :     break;
    2414           0 :   case IN_KIND_REPLAY:
    2415           0 :     handle_replay( ctx, sig, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
    2416           0 :     break;
    2417           0 :   default:
    2418           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    2419           0 :   }
    2420           0 : }
    2421             : 
    2422             : static void
    2423             : after_frag( fd_votor_tile_t *   ctx,
    2424             :             ulong               in_idx,
    2425             :             ulong               seq,
    2426             :             ulong               sig,
    2427             :             ulong               sz,
    2428             :             ulong               tsorig,
    2429             :             ulong               tspub,
    2430           0 :             fd_stem_context_t * stem ) {
    2431           0 :   (void)seq; (void)tsorig; (void)tspub;
    2432             : 
    2433           0 :   switch( ctx->in_kind[ in_idx ] ) {
    2434           0 :   case IN_KIND_EPOCH:
    2435             :     /* reliable link, handled in during_frag */
    2436           0 :     break;
    2437           0 :   case IN_KIND_GOSSIP:
    2438             :     /* reliable link, handled in during_frag */
    2439           0 :     break;
    2440           0 :   case IN_KIND_IPECHO:
    2441           0 :     FD_TEST( sig && sig<=USHORT_MAX );
    2442           0 :     int wfs_green = votor_wfs_is_green( ctx ); /* config only, so the assignment below cannot change it */
    2443           0 :     if( FD_UNLIKELY( !ctx->shred_version && ag_pool_finalized_slot( ctx->pool )!=ULONG_MAX ) ) {
    2444           0 :       long now = fd_clock_tile_now( ctx->clock );
    2445           0 :       ag_votor_init( ctx->votor, &ctx->boot_block_id, now, ctx->ns_per_slot, (ushort)sig, sign_vote, ctx );
    2446           0 :       ctx->highest_vote_or_lead_slot = fd_ulong_max( ctx->highest_vote_or_lead_slot, ctx->boot_block_id.slot );
    2447           0 :       if( FD_LIKELY( wfs_green ) ) ag_votor_arm_skip_timeouts( ctx->votor, now );
    2448           0 :     }
    2449           0 :     ctx->shred_version = (ushort)sig;
    2450           0 :     ctx->init = !!ctx->next_epoch_info && ag_pool_finalized_slot( ctx->pool )!=ULONG_MAX && wfs_green;
    2451           0 :     break;
    2452           0 :   case IN_KIND_NET: {
    2453           0 :     if( FD_UNLIKELY( sz<sizeof(fd_eth_hdr_t)+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t) ) ) break;
    2454           0 :     fd_ip4_hdr_t const * ip4   = (fd_ip4_hdr_t const *)fd_type_pun_const( ctx->net_buf+sizeof(fd_eth_hdr_t) );
    2455           0 :     ulong                iplen = FD_IP4_GET_LEN( *ip4 );
    2456           0 :     if( FD_UNLIKELY( iplen<sizeof(fd_ip4_hdr_t) || sz<sizeof(fd_eth_hdr_t)+iplen+sizeof(fd_udp_hdr_t) ) ) break;
    2457           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 );
    2458           0 :     ushort               dport = fd_ushort_bswap( udp->net_dport );
    2459           0 :     if( FD_UNLIKELY( dport!=ctx->quic_client_listen_port && dport!=ctx->quic_server_listen_port ) ) break;
    2460           0 :     fd_quic_t * quic = fd_ptr_if( dport==ctx->quic_client_listen_port, ctx->quic_client, ctx->quic_server );
    2461           0 :     ctx->stem = stem;
    2462           0 :     fd_quic_process_packet( quic, ctx->net_buf+sizeof(fd_eth_hdr_t), sz-sizeof(fd_eth_hdr_t), fd_clock_tile_now( ctx->clock ) );
    2463           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 );
    2464           0 :     ctx->net_tx_cnt = 0UL;
    2465           0 :     break;
    2466           0 :   }
    2467           0 :   case IN_KIND_REPLAY:
    2468             :     /* reliable link, handled in during_frag */
    2469           0 :     break;
    2470           0 :   default:
    2471           0 :     FD_LOG_ERR(( "unexpected in_kind %d", ctx->in_kind[ in_idx ] ));
    2472           0 :   }
    2473           0 : }
    2474             : 
    2475             : static void
    2476             : privileged_init( fd_topo_t const *      topo,
    2477           0 :                  fd_topo_tile_t const * tile ) {
    2478           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2479             : 
    2480           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2481           0 :   fd_votor_tile_t * ctx      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t), sizeof(fd_votor_tile_t) );
    2482           0 :   void *            auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(),         auth_vtr_footprint()    );
    2483             : 
    2484           0 :   if( FD_UNLIKELY( !strcmp( tile->votor.identity_key_path, "" ) ) )
    2485           0 :     FD_LOG_ERR(( "identity_key_path not set" ));
    2486             : 
    2487           0 :   ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) );
    2488           0 :   ctx->id_key   = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->votor.identity_key_path, /* pubkey only: */ 1 ) );
    2489             : 
    2490           0 :   ctx->vote_history_fd_dir       = -1;
    2491           0 :   ctx->vote_history_fd           = -1;
    2492           0 :   ctx->vote_history_fd_swp       = -1;
    2493           0 :   ctx->vote_history_fd_bak       = -1;
    2494           0 :   ctx->id_key_bak                = ctx->id_key;
    2495           0 :   ctx->vote_history_bak_full     = 0;
    2496           0 :   ctx->vote_history_pending      = 0;
    2497           0 :   ctx->vote_history_slot         = 0UL;
    2498           0 :   ctx->highest_vote_or_lead_slot = 0UL;
    2499           0 :   if( FD_LIKELY( tile->votor.vote_history_path[ 0 ] ) ) {
    2500           0 :     FD_BASE58_ENCODE_32_BYTES( ctx->id_key.uc, id_key_b58 );
    2501           0 :     char bin[ PATH_MAX ];
    2502           0 :     char swp[ PATH_MAX ];
    2503           0 :     char bak[ PATH_MAX ];
    2504           0 :     FD_TEST( fd_cstr_printf_check( bin, PATH_MAX, NULL, "vote_history-%s.bin",     id_key_b58 ) );
    2505           0 :     FD_TEST( fd_cstr_printf_check( swp, PATH_MAX, NULL, "vote_history-%s.bin.swp", id_key_b58 ) );
    2506           0 :     FD_TEST( fd_cstr_printf_check( bak, PATH_MAX, NULL, "vote_history-%s.bin.bak", id_key_b58 ) );
    2507             : 
    2508           0 :     ctx->vote_history_fd_dir = open( tile->votor.vote_history_path, O_RDONLY|O_DIRECTORY );
    2509           0 :     if( FD_UNLIKELY( -1==ctx->vote_history_fd_dir ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", tile->votor.vote_history_path, errno, fd_io_strerror( errno ) ));
    2510             : 
    2511             :     /* Without the identity's file, the directory must hold no other
    2512             :        vote history file, which may hold the identity's votes under
    2513             :        another name, like the .bak a set-identity away from it saves, or
    2514             :        the file an earlier version renamed to the new identity. */
    2515             : 
    2516           0 :     if( FD_UNLIKELY( -1==faccessat( ctx->vote_history_fd_dir, bin, F_OK, 0 ) ) ) {
    2517           0 :       if( FD_UNLIKELY( errno!=ENOENT ) ) FD_LOG_ERR(( "faccessat(`%s/%s`) failed (%i-%s)", tile->votor.vote_history_path, bin, errno, fd_io_strerror( errno ) ));
    2518           0 :       DIR * dir = opendir( tile->votor.vote_history_path );
    2519           0 :       if( FD_UNLIKELY( !dir ) ) FD_LOG_ERR(( "opendir(`%s`) failed (%i-%s)", tile->votor.vote_history_path, errno, fd_io_strerror( errno ) ));
    2520           0 :       errno = 0;
    2521           0 :       for( struct dirent * ent; (ent = readdir( dir )); ) {
    2522           0 :         if( FD_LIKELY( strncmp( ent->d_name, "vote_history-", sizeof("vote_history-")-1UL ) ) ) continue;
    2523           0 :         FD_LOG_ERR(( "Booting as %s, but detected %s in %s, which is missing %s. Please either copy %s into %s or remove the vote_history-* files there if you wish to boot without a vote history file.",
    2524           0 :                      id_key_b58, ent->d_name, tile->votor.vote_history_path, bin, bin, tile->votor.vote_history_path ));
    2525           0 :       }
    2526           0 :       if( FD_UNLIKELY( errno ) ) FD_LOG_ERR(( "readdir(`%s`) failed (%i-%s)", tile->votor.vote_history_path, errno, fd_io_strerror( errno ) ));
    2527           0 :       if( FD_UNLIKELY( closedir( dir ) ) ) FD_LOG_ERR(( "closedir(`%s`) failed (%i-%s)", tile->votor.vote_history_path, errno, fd_io_strerror( errno ) ));
    2528           0 :       FD_LOG_WARNING(( "vote history file `%s/%s` does not exist, creating it.  This identity is assumed to have never voted.", tile->votor.vote_history_path, bin ));
    2529           0 :     }
    2530           0 :     ctx->vote_history_slot         = read_vote_history( ctx, tile->votor.vote_history_path, bin );
    2531           0 :     ctx->highest_vote_or_lead_slot = fd_ulong_sat_sub( ctx->vote_history_slot, 1UL );
    2532           0 :     if( FD_LIKELY( ctx->vote_history_slot ) ) FD_LOG_NOTICE(( "vote history file `%s/%s` has votes, waiting to vote until slot %lu", tile->votor.vote_history_path, bin, ctx->vote_history_slot ));
    2533             : 
    2534             :     /* Switch to non-root uid/gid for file creation. */
    2535           0 :     struct stat st;
    2536           0 :     gid_t gid = getgid();
    2537           0 :     uid_t uid = getuid();
    2538           0 :     if( FD_LIKELY( !gid && -1==syscall( __NR_setresgid, -1, tile->votor.target_gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    2539           0 :     if( FD_LIKELY( !uid && -1==syscall( __NR_setresuid, -1, tile->votor.target_uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    2540             : 
    2541           0 :     ctx->vote_history_fd = openat( ctx->vote_history_fd_dir, bin, O_WRONLY|O_CREAT, 0644 );
    2542           0 :     if( FD_UNLIKELY( -1==ctx->vote_history_fd ) ) FD_LOG_ERR(( "open(`%s/%s`) failed (%i-%s), make sure the configured user owns it", tile->votor.vote_history_path, bin, errno, fd_io_strerror( errno ) ));
    2543           0 :     if( FD_UNLIKELY( -1==fstat( ctx->vote_history_fd, &st ) ) ) FD_LOG_ERR(( "fstat(`%s/%s`) failed (%i-%s)", tile->votor.vote_history_path, bin, errno, fd_io_strerror( errno ) ));
    2544           0 :     if( FD_UNLIKELY( st.st_uid!=tile->votor.target_uid ) ) {
    2545           0 :       FD_LOG_ERR(( "vote history file `%s/%s` is owned by uid %u but the configured user is uid %u, chown it to the configured user", tile->votor.vote_history_path, bin, st.st_uid, tile->votor.target_uid ));
    2546           0 :     }
    2547           0 :     ctx->vote_history_fd_swp = openat( ctx->vote_history_fd_dir, swp, O_WRONLY|O_CREAT, 0644 );
    2548           0 :     if( FD_UNLIKELY( -1==ctx->vote_history_fd_swp ) ) FD_LOG_ERR(( "open(`%s/%s`) failed (%i-%s), make sure the configured user owns it", tile->votor.vote_history_path, swp, errno, fd_io_strerror( errno ) ));
    2549           0 :     if( FD_UNLIKELY( -1==fstat( ctx->vote_history_fd_swp, &st ) ) ) FD_LOG_ERR(( "fstat(`%s/%s`) failed (%i-%s)", tile->votor.vote_history_path, swp, errno, fd_io_strerror( errno ) ));
    2550           0 :     if( FD_UNLIKELY( st.st_uid!=tile->votor.target_uid ) ) {
    2551           0 :       FD_LOG_ERR(( "vote history file `%s/%s` is owned by uid %u but the configured user is uid %u, chown it to the configured user", tile->votor.vote_history_path, swp, st.st_uid, tile->votor.target_uid ));
    2552           0 :     }
    2553           0 :     ctx->vote_history_fd_bak = openat( ctx->vote_history_fd_dir, bak, O_WRONLY|O_CREAT, 0644 );
    2554           0 :     if( FD_UNLIKELY( -1==ctx->vote_history_fd_bak ) ) FD_LOG_ERR(( "open(`%s/%s`) failed (%i-%s), make sure the configured user owns it", tile->votor.vote_history_path, bak, errno, fd_io_strerror( errno ) ));
    2555           0 :     if( FD_UNLIKELY( -1==fstat( ctx->vote_history_fd_bak, &st ) ) ) FD_LOG_ERR(( "fstat(`%s/%s`) failed (%i-%s)", tile->votor.vote_history_path, bak, errno, fd_io_strerror( errno ) ));
    2556           0 :     ctx->vote_history_bak_full = !!st.st_size;
    2557           0 :     if( FD_UNLIKELY( st.st_uid!=tile->votor.target_uid ) ) {
    2558           0 :       FD_LOG_ERR(( "vote history file `%s/%s` is owned by uid %u but the configured user is uid %u, chown it to the configured user", tile->votor.vote_history_path, bak, st.st_uid, tile->votor.target_uid ));
    2559           0 :     }
    2560             : 
    2561           0 :     if( FD_UNLIKELY( -1==syscall( __NR_setresuid, -1, uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    2562           0 :     if( FD_UNLIKELY( -1==syscall( __NR_setresgid, -1, gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) ));
    2563           0 :   }
    2564             : 
    2565           0 :   fd_log_wallclock();
    2566           0 : }
    2567             : 
    2568             : static void
    2569             : unprivileged_init( fd_topo_t const *      topo,
    2570           0 :                    fd_topo_tile_t const * tile ) {
    2571             : 
    2572           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2573             : 
    2574           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2575           0 :   fd_votor_tile_t * ctx           = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t),       sizeof(fd_votor_tile_t)                                           );
    2576           0 :   void *            auth_vtr      = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(),               auth_vtr_footprint()                                              );
    2577           0 :   void *            quic_client   = FD_SCRATCH_ALLOC_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_client_limits )                          );
    2578           0 :   void *            quic_server   = FD_SCRATCH_ALLOC_APPEND( l, fd_quic_align(),                fd_quic_footprint( &quic_server_limits )                          );
    2579           0 :   void *            pool          = FD_SCRATCH_ALLOC_APPEND( l, ag_pool_align(),                ag_pool_footprint( tile->votor.max_live_slots, QUIC_DGRAM_BURST ) );
    2580           0 :   void *            votor         = FD_SCRATCH_ALLOC_APPEND( l, ag_votor_align(),               ag_votor_footprint( tile->votor.max_live_slots )                  );
    2581           0 :   void *            peers         = FD_SCRATCH_ALLOC_APPEND( l, peers_align(),                  peers_footprint( PEERS_LG_SLOT_CNT )                              );
    2582           0 :   void *            contact_infos = FD_SCRATCH_ALLOC_APPEND( l, contact_infos_align(),          contact_infos_footprint( CONTACT_INFOS_LG_SLOT_CNT )              );
    2583           0 :   void *            reconn_prq    = FD_SCRATCH_ALLOC_APPEND( l, reconn_prq_align(),             reconn_prq_footprint( RECONN_MAX )                                );
    2584           0 :   void *            mleaders      = FD_SCRATCH_ALLOC_APPEND( l, fd_multi_epoch_leaders_align(), fd_multi_epoch_leaders_footprint()                                );
    2585           0 :   ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
    2586           0 :   if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) ) {
    2587           0 :     FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
    2588           0 :   }
    2589             : 
    2590           0 :   (void)auth_vtr; /* privileged_init */
    2591             : 
    2592           0 :   ctx->shred_version = (ushort)0;
    2593             : 
    2594           0 :   memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
    2595             : 
    2596           0 :   ulong seed;
    2597           0 :   FD_TEST( fd_rng_secure( &seed, sizeof(seed) ) );
    2598             : 
    2599           0 :   ctx->pool = ag_pool_join( ag_pool_new( pool, tile->votor.max_live_slots, QUIC_DGRAM_BURST, seed ) );
    2600           0 :   FD_TEST( ctx->pool );
    2601             : 
    2602           0 :   ctx->votor = ag_votor_join( ag_votor_new( votor, tile->votor.max_live_slots, seed ) );
    2603           0 :   FD_TEST( ctx->votor );
    2604             : 
    2605           0 :   ctx->prev_epoch_info = NULL;
    2606           0 :   ctx->prev_epoch_slot = ULONG_MAX;
    2607           0 :   ctx->curr_epoch_info = NULL;
    2608           0 :   ctx->curr_epoch_slot = ULONG_MAX;
    2609           0 :   ctx->next_epoch_info = NULL;
    2610           0 :   ctx->next_epoch_slot = ULONG_MAX;
    2611           0 :   ctx->conn_ahead_slot = ULONG_MAX;
    2612           0 :   memset( ctx->client_peer_id_keys, 0, sizeof(ctx->client_peer_id_keys) );
    2613           0 :   memset( ctx->server_peer_id_keys, 0, sizeof(ctx->server_peer_id_keys) );
    2614             : 
    2615           0 :   if( FD_UNLIKELY( !tile->votor.quic_client_listen_port ) )
    2616           0 :     FD_LOG_ERR(( "[development.votor.quic_client_listen_port] must be non-zero when alpenglow is enabled" ));
    2617           0 :   if( FD_UNLIKELY( tile->votor.quic_client_listen_port==tile->votor.quic_server_listen_port ) )
    2618           0 :     FD_LOG_ERR(( "[development.votor.quic_client_listen_port] %hu must differ from [development.votor.quic_server_listen_port]",
    2619           0 :                  tile->votor.quic_client_listen_port ));
    2620             : 
    2621           0 :   ctx->quic_client_listen_port = tile->votor.quic_client_listen_port;
    2622           0 :   ctx->quic_server_listen_port = tile->votor.quic_server_listen_port;
    2623           0 :   ctx->src_ip_addr             = tile->votor.ip_addr;
    2624           0 :   ctx->net_id                  = (ushort)0;
    2625           0 :   fd_ip4_udp_hdr_init( ctx->hdr, FD_NET_MTU, ctx->src_ip_addr, ctx->quic_client_listen_port );
    2626             : 
    2627             : 
    2628           0 :   ulong peers_seed;         FD_TEST( fd_rng_secure( &peers_seed,         sizeof(ulong) ) );
    2629           0 :   ulong contact_infos_seed; FD_TEST( fd_rng_secure( &contact_infos_seed, sizeof(ulong) ) );
    2630             : 
    2631           0 :   ctx->peers = peers_join( peers_new( peers, PEERS_LG_SLOT_CNT, peers_seed ) );
    2632           0 :   FD_TEST( ctx->peers );
    2633             : 
    2634           0 :   ctx->contact_infos = contact_infos_join( contact_infos_new( contact_infos, CONTACT_INFOS_LG_SLOT_CNT, contact_infos_seed ) );
    2635           0 :   FD_TEST( ctx->contact_infos );
    2636             : 
    2637           0 :   ctx->reconn_prq = reconn_prq_join( reconn_prq_new( reconn_prq, RECONN_MAX ) );
    2638           0 :   FD_TEST( ctx->reconn_prq );
    2639             : 
    2640           0 :   ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( mleaders ) );
    2641           0 :   FD_TEST( ctx->mleaders );
    2642             : 
    2643           0 :   for( ulong i=0UL; i<REWARD_VOTE_MAX; i++ ) ctx->reward_votes[ i ].slot = ULONG_MAX;
    2644           0 :   for( ulong i=0UL; i<AG_SLOTS_PER_WINDOW; i++ ) ctx->reward_certs[ i ] = (reward_cert_t){ .slot = ULONG_MAX };
    2645             : 
    2646           0 :   ctx->init                      = 0;
    2647           0 :   ctx->boot_block_id             = (ag_block_id_t){ .slot = ULONG_MAX };
    2648           0 :   ctx->wfs_slot                  = tile->votor.wait_for_supermajority_at_slot;
    2649           0 :   ctx->wfs_hash_is_zero          = tile->votor.wait_for_supermajority_hash_is_zero;
    2650           0 :   ctx->wfs_shred_version         = tile->votor.expected_shred_version;
    2651           0 :   ctx->wfs_signalled             = 0;
    2652           0 :   ctx->net_tx_cnt                = 0UL;
    2653           0 :   ctx->next_leader_slot          = ULONG_MAX;
    2654           0 :   ctx->ns_per_slot               = 400000000L; /* until epoch info */
    2655           0 :   ctx->slots_per_epoch           = 0UL;
    2656           0 :   ctx->highest_parent_ready_slot = 0UL;
    2657           0 :   ctx->highest_certed_final_slot = 0UL;
    2658           0 :   ctx->catchup_slot              = 0UL;
    2659           0 :   ctx->catchup_final_slot        = 0UL;
    2660           0 :   ctx->catchup_vote_slot         = 0UL;
    2661           0 :   ctx->slot_max                  = tile->votor.max_live_slots;
    2662           0 :   ctx->standstill_slot           = ULONG_MAX;
    2663           0 :   ctx->standstill_ts             = LONG_MAX;
    2664           0 :   ag_pool_standstill_iter_init( ctx->standstill_iter, ctx->pool );
    2665             : 
    2666           0 :   FD_TEST( tile->in_cnt<=sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
    2667           0 :   for( ulong i=0UL; i<tile->in_cnt; i++ ) {
    2668           0 :     fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
    2669             : 
    2670           0 :     if     ( FD_LIKELY( !strcmp( link->name, "replay_epoch"  ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
    2671           0 :     else if( FD_LIKELY( !strcmp( link->name, "gossip_ciaddr" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
    2672           0 :     else if( FD_LIKELY( !strcmp( link->name, "ipecho_out"    ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
    2673           0 :     else if( FD_LIKELY( !strcmp( link->name, "net_votor"     ) ) ) {
    2674           0 :       ctx->in_kind[ i ] = IN_KIND_NET;
    2675           0 :       fd_net_rx_bounds_init( &ctx->net_in_bounds[ i ], link->dcache );
    2676           0 :     }
    2677           0 :     else if( FD_LIKELY( !strcmp( link->name, "replay_slot"  ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
    2678           0 :     else if( FD_LIKELY( !strcmp( link->name, "sign_votor"   ) ) ) ctx->in_kind[ i ] = IN_KIND_SIGN;
    2679           0 :     else FD_LOG_ERR(( "votor tile has unexpected input link %lu %s", i, link->name ));
    2680             : 
    2681           0 :     if( FD_LIKELY( link->mtu ) ) ctx->in[ i ].mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp;
    2682           0 :   }
    2683             : 
    2684           0 :   FD_TEST( tile->out_cnt>OUT_IDX_NET );
    2685           0 :   fd_topo_link_t const * votor_out = &topo->links[ tile->out_link_id[ OUT_IDX_VOTOR ] ];
    2686           0 :   FD_TEST( !strcmp( votor_out->name, "votor_out" ) );
    2687           0 :   ctx->votor_out_mem    = topo->workspaces[ topo->objs[ votor_out->dcache_obj_id ].wksp_id ].wksp;
    2688           0 :   ctx->votor_out_chunk0 = fd_dcache_compact_chunk0( ctx->votor_out_mem, votor_out->dcache );
    2689           0 :   ctx->votor_out_wmark  = fd_dcache_compact_wmark ( ctx->votor_out_mem, votor_out->dcache, votor_out->mtu );
    2690           0 :   ctx->votor_out_chunk  = ctx->votor_out_chunk0;
    2691             : 
    2692           0 :   fd_topo_link_t const * net_out = &topo->links[ tile->out_link_id[ OUT_IDX_NET ] ];
    2693           0 :   FD_TEST( !strcmp( net_out->name, "votor_net" ) );
    2694           0 :   FD_TEST( net_out->burst>=FD_VOTOR_NET_BURST );
    2695           0 :   ctx->net_out_mem    = topo->workspaces[ topo->objs[ net_out->dcache_obj_id ].wksp_id ].wksp;
    2696           0 :   ctx->net_out_chunk0 = fd_dcache_compact_chunk0( ctx->net_out_mem, net_out->dcache );
    2697           0 :   ctx->net_out_wmark  = fd_dcache_compact_wmark ( ctx->net_out_mem, net_out->dcache, net_out->mtu );
    2698           0 :   ctx->net_out_chunk  = ctx->net_out_chunk0;
    2699             : 
    2700           0 :   ulong sign_in_idx  = fd_topo_find_tile_in_link ( topo, tile, "sign_votor", tile->kind_id );
    2701           0 :   ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "votor_sign", tile->kind_id );
    2702           0 :   FD_TEST( sign_in_idx !=ULONG_MAX );
    2703           0 :   FD_TEST( sign_out_idx!=ULONG_MAX );
    2704           0 :   fd_topo_link_t const * sign_in  = &topo->links[ tile->in_link_id [ sign_in_idx  ] ];
    2705           0 :   fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ];
    2706             : 
    2707           0 :   fd_sleep_t * sleep = NULL;
    2708           0 :   if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) {
    2709           0 :     sleep = fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) );
    2710           0 :     FD_TEST( sleep );
    2711           0 :   }
    2712             : 
    2713           0 :   if( FD_UNLIKELY( !fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client,
    2714           0 :           sign_out->mcache,
    2715           0 :           sign_out->dcache,
    2716           0 :           sign_in->mcache,
    2717           0 :           sign_in->dcache,
    2718           0 :           sign_out->mtu,
    2719           0 :           sign_in->mtu,
    2720           0 :           sleep,
    2721           0 :           sign_out->id,
    2722           0 :           fd_topo_find_link_consumer( topo, sign_out ) ) ) ) ) {
    2723           0 :     FD_LOG_ERR(( "failed to construct keyguard client" ));
    2724           0 :   }
    2725           0 :   load_keys( ctx, tile->votor.authorized_voter_paths_cnt );
    2726             : 
    2727           0 :   ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
    2728           0 :   FD_TEST( ctx->auth_vtr_keyswitch );
    2729           0 :   ctx->id_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
    2730           0 :   FD_TEST( ctx->id_keyswitch );
    2731           0 :   ctx->halt_signing      = 0;
    2732           0 :   ctx->replay_in_seq     = 0UL;
    2733             : 
    2734           0 :   fd_aio_t * quic_tx_aio = fd_aio_join( fd_aio_new( ctx->quic_tx_aio, ctx, quic_aio_tx ) );
    2735           0 :   FD_TEST( quic_tx_aio );
    2736             : 
    2737           0 :   ctx->quic_client = fd_quic_join( fd_quic_new( quic_client, &quic_client_limits ) );
    2738           0 :   FD_TEST( ctx->quic_client );
    2739           0 :   fd_quic_set_aio_net_tx( ctx->quic_client, quic_tx_aio );
    2740             : 
    2741           0 :   ctx->quic_client->config.role                       = FD_QUIC_ROLE_CLIENT;
    2742           0 :   ctx->quic_client->config.retry                      = 0;
    2743           0 :   ctx->quic_client->config.keep_alive                 = 1;
    2744           0 :   ctx->quic_client->config.idle_timeout               = 5L*1000L*1000L*1000L;
    2745           0 :   ctx->quic_client->config.ack_delay                  = 2L*1000L*1000L;
    2746           0 :   memcpy( ctx->quic_client->config.identity_public_key, ctx->id_key.uc, 32UL );
    2747           0 :   ctx->quic_client->config.sign                       = quic_sign;
    2748           0 :   ctx->quic_client->config.sign_ctx                   = ctx;
    2749           0 :   ctx->quic_client->config.alpn[ 0 ]                  = 0x0c;
    2750           0 :   memcpy( ctx->quic_client->config.alpn+1, "alpenglow-v1", 12UL );
    2751           0 :   ctx->quic_client->config.alpn_sz                    = 13UL;
    2752           0 :   ctx->quic_client->config.initial_rx_max_stream_data = 0UL;
    2753             : 
    2754           0 :   ctx->quic_client->cb.quic_ctx         = ctx;
    2755           0 :   ctx->quic_client->cb.conn_hs_complete = quic_client_conn_hs_complete;
    2756           0 :   ctx->quic_client->cb.conn_final       = quic_client_conn_final;
    2757           0 :   ctx->quic_client->cb.ack_range        = quic_client_ack_range;
    2758             : 
    2759           0 :   FD_TEST( fd_quic_init( ctx->quic_client ) );
    2760             : 
    2761           0 :   ctx->quic_server = fd_quic_join( fd_quic_new( quic_server, &quic_server_limits ) );
    2762           0 :   FD_TEST( ctx->quic_server );
    2763           0 :   fd_clock_tile_init( ctx->clock );
    2764           0 :   fd_quic_set_aio_net_tx( ctx->quic_server, quic_tx_aio );
    2765             : 
    2766           0 :   ctx->quic_server->config.role                       = FD_QUIC_ROLE_SERVER;
    2767           0 :   ctx->quic_server->config.retry                      = 1;
    2768           0 :   ctx->quic_server->config.idle_timeout               = 5L*1000L*1000L*1000L;
    2769           0 :   ctx->quic_server->config.ack_delay                  = 2L*1000L*1000L;
    2770           0 :   memcpy( ctx->quic_server->config.identity_public_key, ctx->id_key.uc, 32UL );
    2771           0 :   ctx->quic_server->config.sign                       = quic_sign;
    2772           0 :   ctx->quic_server->config.sign_ctx                   = ctx;
    2773           0 :   ctx->quic_server->config.req_client_cert            = 1;
    2774           0 :   ctx->quic_server->config.alpn[ 0 ]                  = 0x0c;
    2775           0 :   memcpy( ctx->quic_server->config.alpn+1, "alpenglow-v1", 12UL );
    2776           0 :   ctx->quic_server->config.alpn_sz                    = 13UL;
    2777           0 :   ctx->quic_server->config.initial_rx_max_stream_data = 0UL;
    2778           0 :   ctx->quic_server->config.max_datagram_frame_size    = 1280UL;
    2779             : 
    2780           0 :   ctx->quic_server->cb.quic_ctx    = ctx;
    2781           0 :   ctx->quic_server->cb.conn_new    = quic_server_conn_new;
    2782           0 :   ctx->quic_server->cb.conn_final  = quic_server_conn_final;
    2783           0 :   ctx->quic_server->cb.datagram_rx = quic_server_datagram_rx;
    2784             : 
    2785           0 :   FD_TEST( fd_quic_init( ctx->quic_server ) );
    2786             : 
    2787           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->quic_client->metrics.service_duration, FD_MHIST_EDGES_NS( VOTOR, CLIENT_SERVICE_DURATION_NANOS ) ) );
    2788           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->quic_client->metrics.receive_duration, FD_MHIST_EDGES_NS( VOTOR, CLIENT_PKT_RX_DURATION_NANOS  ) ) );
    2789           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->quic_server->metrics.service_duration, FD_MHIST_EDGES_NS( VOTOR, SERVER_SERVICE_DURATION_NANOS ) ) );
    2790           0 :   fd_histf_join( fd_metrics_histf_new_ns( ctx->quic_server->metrics.receive_duration, FD_MHIST_EDGES_NS( VOTOR, SERVER_PKT_RX_DURATION_NANOS  ) ) );
    2791           0 : }
    2792             : 
    2793             : static ulong
    2794             : populate_allowed_seccomp( fd_topo_t const *      topo,
    2795             :                           fd_topo_tile_t const * tile,
    2796             :                           ulong                  out_cnt,
    2797           0 :                           struct sock_filter *   out ) {
    2798           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2799           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2800           0 :   fd_votor_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t), sizeof(fd_votor_tile_t) );
    2801             : 
    2802           0 :   populate_sock_filter_policy_fd_votor_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->vote_history_fd_dir, (uint)ctx->vote_history_fd_swp, (uint)ctx->vote_history_fd, (uint)ctx->vote_history_fd_bak );
    2803           0 :   return sock_filter_policy_fd_votor_tile_instr_cnt;
    2804           0 : }
    2805             : 
    2806             : static ulong
    2807             : populate_allowed_fds( fd_topo_t const *      topo,
    2808             :                       fd_topo_tile_t const * tile,
    2809             :                       ulong                  out_fds_cnt,
    2810           0 :                       int *                  out_fds ) {
    2811           0 :   void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
    2812           0 :   FD_SCRATCH_ALLOC_INIT( l, scratch );
    2813           0 :   fd_votor_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_votor_tile_t), sizeof(fd_votor_tile_t) );
    2814             : 
    2815           0 :   if( FD_UNLIKELY( out_fds_cnt<6UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
    2816             : 
    2817           0 :   ulong out_cnt = 0UL;
    2818           0 :   out_fds[ out_cnt++ ] = 2;
    2819           0 :   if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
    2820           0 :     out_fds[ out_cnt++ ] = fd_log_private_logfile_fd();
    2821           0 :   if( FD_LIKELY( -1!=ctx->vote_history_fd_dir ) ) {
    2822           0 :     out_fds[ out_cnt++ ] = ctx->vote_history_fd_dir;
    2823           0 :     out_fds[ out_cnt++ ] = ctx->vote_history_fd_swp;
    2824           0 :     out_fds[ out_cnt++ ] = ctx->vote_history_fd;
    2825           0 :     out_fds[ out_cnt++ ] = ctx->vote_history_fd_bak;
    2826           0 :   }
    2827           0 :   return out_cnt;
    2828           0 : }
    2829             : 
    2830           0 : #define STEM_BURST FD_VOTOR_OUT_BURST /* votor_out only; EXCLUDES VOTOR_NET (has no reliable consumers) */
    2831           0 : #define STEM_LAZY  (128L*3000L)
    2832             : 
    2833           0 : #define STEM_CALLBACK_CONTEXT_TYPE  fd_votor_tile_t
    2834           0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_votor_tile_t)
    2835           0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
    2836           0 : #define STEM_CALLBACK_NEXT_DEADLINE       next_deadline
    2837           0 : #define STEM_CALLBACK_METRICS_WRITE       metrics_write
    2838           0 : #define STEM_CALLBACK_AFTER_CREDIT        after_credit
    2839           0 : #define STEM_CALLBACK_BEFORE_FRAG         before_frag
    2840           0 : #define STEM_CALLBACK_DURING_FRAG         during_frag
    2841           0 : #define STEM_CALLBACK_AFTER_FRAG          after_frag
    2842             : 
    2843             : #include "../../disco/stem/fd_stem.c"
    2844             : 
    2845             : static ulong
    2846           0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
    2847           0 :   return fd_ulong_max( sizeof(fd_event_alpenglow_vote_t), sizeof(fd_event_alpenglow_cert_t) );
    2848           0 : }
    2849             : 
    2850             : fd_topo_run_tile_t fd_tile_votor = {
    2851             :   .name                     = "votor",
    2852             :   .allow_renameat           = 1, /* write_vote_history */
    2853             :   .max_event_sz             = max_event_sz,
    2854             :   .populate_allowed_seccomp = populate_allowed_seccomp,
    2855             :   .populate_allowed_fds     = populate_allowed_fds,
    2856             :   .scratch_align            = scratch_align,
    2857             :   .scratch_footprint        = scratch_footprint,
    2858             :   .privileged_init          = privileged_init,
    2859             :   .unprivileged_init        = unprivileged_init,
    2860             :   .run                      = stem_run,
    2861             : };

Generated by: LCOV version 1.14