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