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