Line data Source code
1 : #include "fd_tower_tile.h"
2 : #include "generated/fd_tower_tile_seccomp.h"
3 :
4 : #include "../../choreo/eqvoc/fd_eqvoc.h"
5 : #include "../../choreo/ghost/fd_ghost.h"
6 : #include "../../choreo/hfork/fd_hfork.h"
7 : #include "../../choreo/notar/fd_notar.h"
8 : #include "../../choreo/tower/fd_tower.h"
9 : #include "../../choreo/tower/fd_tower_voters.h"
10 : #include "../../choreo/tower/fd_tower_blocks.h"
11 : #include "../../choreo/tower/fd_tower_serdes.h"
12 : #include "../../choreo/tower/fd_tower_stakes.h"
13 : #include "../../disco/fd_txn_p.h"
14 : #include "../../disco/keyguard/fd_keyload.h"
15 : #include "../../disco/keyguard/fd_keyswitch.h"
16 : #include "../../disco/metrics/fd_metrics.h"
17 : #include "../../disco/shred/fd_stake_ci.h"
18 : #include "../../disco/topo/fd_topo.h"
19 : #include "../../disco/fd_txn_m.h"
20 : #include "../../discof/fd_accdb_topo.h"
21 : #include "../../discof/replay/fd_replay_tile.h"
22 : #include "../../flamenco/accdb/fd_accdb_sync.h"
23 : #include "../../flamenco/accdb/fd_accdb_pipe.h"
24 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
25 : #include "../../flamenco/runtime/fd_bank.h"
26 : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
27 : #include "../../util/pod/fd_pod.h"
28 :
29 : #include <errno.h>
30 : #include <fcntl.h>
31 : #include <unistd.h>
32 :
33 : /* The tower tile is responsible for two things:
34 :
35 : 1. running the fork choice (fd_ghost) and TowerBFT (fd_tower) rules
36 : after replaying a block.
37 : 2. listening to gossip (duplicate shred and vote messages) and
38 : monitoring for duplicate or duplicate confirmed blocks (fd_notar).
39 :
40 : Tower signals to other tiles about events that occur as a result of
41 : those two above events, such as what block to vote on, what block to
42 : reset onto as leader, what block got rooted, what blocks are
43 : duplicates and what blocks are confirmed.
44 :
45 : In general, tower uses the block_id as the identifier for blocks. The
46 : block_id is the merkle root of the last FEC set for a block. This is
47 : guaranteed to be unique for a given block and is the canonical
48 : identifier over the slot number because unlike slot numbers, if a
49 : leader equivocates (produces multiple blocks for the same slot) the
50 : block_id can disambiguate the blocks.
51 :
52 : However, the block_id was only introduced into the Solana protocol
53 : recently, and TowerBFT still uses the "legacy" identifier of slot
54 : numbers for blocks. So the tile (and relevant modules) will use
55 : block_id when possible to interface with the protocol but otherwise
56 : falling back to slot number when block_id is unsupported. */
57 :
58 : #define LOGGING 0
59 :
60 0 : #define IN_KIND_DEDUP (0)
61 0 : #define IN_KIND_EPOCH (1)
62 0 : #define IN_KIND_REPLAY (2)
63 0 : #define IN_KIND_GOSSIP (3)
64 0 : #define IN_KIND_IPECHO (4)
65 0 : #define IN_KIND_SHRED (5)
66 :
67 0 : #define OUT_IDX 0 /* only a single out link tower_out */
68 0 : #define AUTH_VTR_LG_MAX (5) /* The Solana Vote Interface supports up to 32 authorized voters. */
69 : FD_STATIC_ASSERT( 1<<AUTH_VTR_LG_MAX==32, AUTH_VTR_LG_MAX );
70 :
71 : /* Tower processes at most 2 equivocating blocks for a given slot: the
72 : first block is the first one we observe for a slot, and the second
73 : block is the one that gets duplicate confirmed. Most of the time,
74 : they are the same (ie. the block we first saw is the block that gets
75 : duplicate confirmed), but we size for the worst case which is every
76 : block in slot_max equivocates and we always see 2 blocks for every
77 : slot. */
78 :
79 0 : #define EQVOC_MAX (2)
80 :
81 : struct publish {
82 : ulong sig;
83 : fd_tower_msg_t msg;
84 : };
85 : typedef struct publish publish_t;
86 :
87 : #define DEQUE_NAME publishes
88 0 : #define DEQUE_T publish_t
89 : #include "../../util/tmpl/fd_deque_dynamic.c"
90 :
91 : struct auth_vtr {
92 : fd_pubkey_t addr; /* map key, vote account address */
93 : uint hash; /* reserved for use by fd_map */
94 : ulong paths_idx; /* index in authorized voter paths */
95 : };
96 : typedef struct auth_vtr auth_vtr_t;
97 :
98 : #define MAP_NAME auth_vtr
99 0 : #define MAP_T auth_vtr_t
100 0 : #define MAP_LG_SLOT_CNT AUTH_VTR_LG_MAX
101 0 : #define MAP_KEY addr
102 0 : #define MAP_KEY_T fd_pubkey_t
103 0 : #define MAP_KEY_NULL (fd_pubkey_t){0}
104 0 : #define MAP_KEY_EQUAL(k0,k1) (!(memcmp((k0).key,(k1).key,sizeof(fd_pubkey_t))))
105 0 : #define MAP_KEY_INVAL(k) (MAP_KEY_EQUAL((k),MAP_KEY_NULL))
106 : #define MAP_KEY_EQUAL_IS_SLOW 1
107 0 : #define MAP_KEY_HASH(k) ((uint)fd_ulong_hash( fd_ulong_load_8( (k).uc ) ))
108 : #include "../../util/tmpl/fd_map.c"
109 :
110 : #define AUTH_VOTERS_MAX (16UL)
111 :
112 : typedef struct {
113 : int mcache_only;
114 : fd_wksp_t * mem;
115 : ulong chunk0;
116 : ulong wmark;
117 : ulong mtu;
118 : } in_ctx_t;
119 :
120 : struct fd_tower_tile {
121 : ulong seed; /* map seed */
122 : int checkpt_fd;
123 : int restore_fd;
124 : fd_pubkey_t identity_key[1];
125 : fd_pubkey_t vote_account[1];
126 : ulong auth_vtr_path_cnt; /* number of authorized voter paths passed to tile */
127 : uchar our_vote_acct[FD_VOTE_STATE_DATA_MAX]; /* buffer for reading back our own vote acct data */
128 : ulong our_vote_acct_sz;
129 :
130 : /* owned joins */
131 :
132 : fd_wksp_t * wksp; /* workspace */
133 : fd_keyswitch_t * identity_keyswitch;
134 : auth_vtr_t * auth_vtr;
135 : fd_keyswitch_t * auth_vtr_keyswitch; /* authorized voter keyswitch */
136 :
137 : fd_eqvoc_t * eqvoc;
138 : fd_ghost_t * ghost;
139 : fd_hfork_t * hfork;
140 : fd_notar_t * notar;
141 : fd_tower_t * tower;
142 :
143 : fd_tower_t * scratch_tower; /* spare tower used during processing */
144 : fd_tower_blocks_t * tower_blocks;
145 : fd_tower_leaves_t * tower_leaves;
146 : fd_tower_lockos_t * tower_lockos;
147 : fd_tower_stakes_t * tower_stakes; /* tracks the stakes for each voter in the epoch per fork */
148 : fd_tower_voters_t * tower_voters;
149 :
150 : publish_t * publishes; /* deque of slot_confirmed msgs queued for publishing */
151 : fd_multi_epoch_leaders_t * mleaders; /* multi-epoch leaders */
152 :
153 : /* borrowed joins */
154 :
155 : fd_banks_t banks[1];
156 : fd_accdb_user_t accdb[1];
157 :
158 : /* static structures */
159 :
160 : fd_gossip_duplicate_shred_t chunks[FD_EQVOC_CHUNK_CNT];
161 : fd_compact_tower_sync_serde_t compact_tower_sync_serde;
162 : ulong notar_reindex[FD_VOTER_MAX];
163 : fd_hash_t notar_removed[FD_VOTER_MAX];
164 : uchar vote_txn[FD_TPU_PARSED_MTU];
165 :
166 : uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ];
167 :
168 : /* metadata */
169 :
170 : int halt_signing;
171 : ushort shred_version;
172 : ulong init_slot; /* initial slot (either genesis or snapshot slot) */
173 : ulong root_slot; /* monotonically increasing contiguous root slot */
174 :
175 : /* in/out link setup */
176 :
177 : int in_kind[ 64UL ];
178 : in_ctx_t in [ 64UL ];
179 :
180 : fd_wksp_t * out_mem;
181 : ulong out_chunk0;
182 : ulong out_wmark;
183 : ulong out_chunk;
184 : ulong out_seq;
185 :
186 : /* metrics */
187 :
188 : struct {
189 :
190 : ulong slot_ignored_cnt;
191 : ulong slot_ignored_gauge;
192 : ulong slot_eqvoced_cnt;
193 : ulong slot_eqvoced_gauge;
194 :
195 : ulong replay_slot;
196 : ulong vote_slot;
197 : ulong reset_slot;
198 : ulong root_slot;
199 : ulong init_slot;
200 :
201 : ulong ancestor_rollback;
202 : ulong sibling_confirmed;
203 : ulong same_fork;
204 : ulong switch_pass;
205 : ulong switch_fail;
206 : ulong lockout_fail;
207 : ulong threshold_fail;
208 : ulong propagated_fail;
209 :
210 : ulong vote_txn_invalid;
211 : ulong vote_txn_ignored;
212 :
213 : ulong eqvoc_success_merkle;
214 : ulong eqvoc_success_meta;
215 : ulong eqvoc_success_last;
216 : ulong eqvoc_success_overlap;
217 : ulong eqvoc_success_chained;
218 :
219 : ulong eqvoc_err_serde;
220 : ulong eqvoc_err_slot;
221 : ulong eqvoc_err_version;
222 : ulong eqvoc_err_type;
223 : ulong eqvoc_err_merkle;
224 : ulong eqvoc_err_signature;
225 :
226 : ulong eqvoc_err_chunk_cnt;
227 : ulong eqvoc_err_chunk_idx;
228 : ulong eqvoc_err_chunk_len;
229 :
230 : ulong eqvoc_err_ignored_from;
231 : ulong eqvoc_err_ignored_slot;
232 :
233 : ulong eqvoc_proof_constructed;
234 : ulong eqvoc_proof_verified;
235 :
236 : fd_hfork_metrics_t hfork;
237 : } metrics;
238 : };
239 : typedef struct fd_tower_tile fd_tower_tile_t;
240 :
241 : static void
242 : confirm_block( fd_tower_tile_t * ctx,
243 : fd_notar_blk_t * notar_blk,
244 0 : ulong total_stake ) {
245 :
246 0 : fd_ghost_blk_t * ghost_blk = fd_ghost_query( ctx->ghost, ¬ar_blk->block_id );
247 0 : fd_tower_blk_t * tower_blk = fd_tower_blk_query( ctx->tower_blocks->blk_map, notar_blk->slot, NULL );
248 :
249 : /* Check if the block meets the threshold for any of the confirmation
250 : levels. Note that even if a block meets the threshold for a given
251 : confirmation level, it might not be eligible for that confirmation
252 : level if it is missing votes from voters that are in the current
253 : tower's voter set (eg. if we are missing a vote from a voter that
254 : has large stake and the block is right around the threshold, then
255 : we might not be able to confirm the block until we get that vote).
256 : So we check eligibility for each confirmation level as well, and
257 : only publish confirmations for levels that the block is both above
258 : the threshold and eligible for. */
259 :
260 0 : int const levels[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_LEVELS;
261 0 : double const ratios[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = { 1.0/3, 0.52, 2.0/3, 4.0/5 };
262 0 : for( ulong i = 0UL; i < FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; i++ ) {
263 0 : if( FD_UNLIKELY( (double)notar_blk->stake / (double)total_stake < ratios[i] ) ) return;
264 :
265 : /* If ghost and tower are missing, then we know this is a forward
266 : confirmation (ie. we haven't replayed the block yet). */
267 :
268 0 : if( FD_UNLIKELY( !ghost_blk || !tower_blk ) ) {
269 0 : if( FD_LIKELY( notar_blk->fwd_level < levels[i] ) ) {
270 0 : notar_blk->fwd_level = levels[i];
271 0 : publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 1, .slot = notar_blk->slot, .block_id = notar_blk->block_id } } } );
272 0 : }
273 0 : continue;
274 0 : }
275 :
276 : /* Otherwise if they are present, then we know this is not a forward
277 : confirmation and thus we have replayed and confirmed the block,
278 : which also implies we have replayed and confirmed its ancestry.
279 : So publish confirmations for all ancestors (short-circuit at the
280 : first ancestor that was already confirmed).
281 :
282 : We use ghost to walk up the ancestry and also mark ghost and
283 : tower blocks as confirmed as we walk if this is the duplicate
284 : confirmation level. */
285 :
286 0 : fd_ghost_blk_t * ghost_anc = ghost_blk;
287 0 : fd_notar_blk_t * notar_anc = notar_blk;
288 0 : fd_tower_blk_t * tower_anc = tower_blk;
289 0 : while( FD_LIKELY( ghost_anc && notar_anc && notar_anc->level < levels[i] ) ) {
290 0 : notar_anc = fd_notar_blk_query( ctx->notar->blk_map, ghost_anc->id, NULL );
291 0 : if( FD_UNLIKELY( !notar_anc ) ) break; /* ghost might have blocks that are not in notar */
292 :
293 0 : tower_anc = fd_tower_blocks_query( ctx->tower_blocks, ghost_anc->slot );
294 0 : FD_TEST( tower_anc ); /* if it's in ghost it must be in tower_blocks */
295 :
296 0 : publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 0, .slot = ghost_anc->slot, .block_id = ghost_anc->id } } } );
297 0 : if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) {
298 0 : ghost_anc->conf = 1;
299 0 : tower_anc->confirmed = 1;
300 0 : tower_anc->confirmed_block_id = notar_anc->block_id;
301 0 : }
302 0 : notar_anc->level = levels[i];
303 :
304 0 : ghost_anc = fd_ghost_parent( ctx->ghost, ghost_anc );
305 0 : }
306 0 : }
307 0 : }
308 :
309 : /* count_vote counts vote txns from Gossip, TPU and Replay. Note these
310 : txns have already been parsed and sigverified before they are sent to
311 : tower. Replay votes have been successfully executed. They are
312 : counted towards notar and hfork (see point 2 in the top-level
313 : documentation). */
314 :
315 : static void
316 : count_vote( fd_tower_tile_t * ctx,
317 : fd_txn_t const * txn,
318 0 : uchar const * payload ) {
319 :
320 : /* We are a little stricter than Agave here when validating the vote
321 : because we use the same validation as pack ie. is_simple_vote which
322 : includes a check that there are at most two signers, whereas
323 : Agave's gossip vote parser does not perform that same check (the
324 : only two signers are the identity key and vote authority, which may
325 : optionally be the same).
326 :
327 : Being a little stricter here is ok because even if we drop some
328 : votes with extraneous signers that Agave would consider valid
329 : (unlikely), gossip votes are in general considered unreliable and
330 : ultimately consensus is reached through replaying the vote txns.
331 :
332 : The remaining checks mirror Agave as closely as possible (and are
333 : documented throughout below). */
334 :
335 : /* TODO verify we never drop a replay vote txn that Agave wouldn't. */
336 :
337 0 : if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, payload ) ) ) { ctx->metrics.vote_txn_invalid++; return; }
338 :
339 : /* TODO check the authorized voter for this vote account (from epoch
340 : stakes) is one of the signers. */
341 :
342 : /* Filter any non-TowerSync vote txns. */
343 :
344 : /* TODO SECURITY ensure SIMD-0138 is activated */
345 :
346 0 : fd_txn_instr_t const * instr = &txn->instr[0];
347 0 : uchar const * instr_data = payload + instr->data_off;
348 0 : uint kind = fd_uint_load_4_fast( instr_data );
349 0 : if( FD_UNLIKELY( kind != FD_VOTE_IX_KIND_TOWER_SYNC && kind != FD_VOTE_IX_KIND_TOWER_SYNC_SWITCH ) ) { ctx->metrics.vote_txn_ignored++; return; };
350 :
351 : /* Deserialize the TowerSync out of the vote txn. */
352 :
353 0 : int err = fd_compact_tower_sync_de( &ctx->compact_tower_sync_serde, instr_data + sizeof(uint), instr->data_sz - sizeof(uint) );
354 0 : if( FD_UNLIKELY( err==-1 ) ) { ctx->metrics.vote_txn_invalid++; return; }
355 0 : ulong slot = ctx->compact_tower_sync_serde.root;
356 0 : fd_tower_remove_all( ctx->scratch_tower );
357 0 : for( ulong i = 0; i < ctx->compact_tower_sync_serde.lockouts_cnt; i++ ) {
358 0 : slot += ctx->compact_tower_sync_serde.lockouts[i].offset;
359 0 : fd_tower_push_tail( ctx->scratch_tower, (fd_tower_vote_t){ .slot = slot, .conf = ctx->compact_tower_sync_serde.lockouts[i].confirmation_count } );
360 0 : }
361 0 : if( FD_UNLIKELY( 0==memcmp( &ctx->compact_tower_sync_serde.block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { ctx->metrics.vote_txn_invalid++; return; };
362 :
363 0 : fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
364 0 : fd_pubkey_t const * vote_acc = NULL;
365 0 : if( FD_UNLIKELY( txn->signature_cnt==1 ) ) vote_acc = (fd_pubkey_t const *)fd_type_pun_const( &accs[1] ); /* identity and authority same, account idx 1 is the vote account address */
366 0 : else vote_acc = (fd_pubkey_t const *)fd_type_pun_const( &accs[2] ); /* identity and authority diff, account idx 2 is the vote account address */
367 :
368 : /* Return early if their tower is empty. */
369 :
370 0 : if( FD_UNLIKELY( fd_tower_empty( ctx->scratch_tower ) ) ) { ctx->metrics.vote_txn_ignored++; return; };
371 :
372 : /* The vote txn contains a block id and bank hash for their last vote
373 : slot in the tower. Agave always counts the last vote.
374 :
375 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
376 :
377 0 : fd_tower_vote_t const * their_last_vote = fd_tower_peek_tail_const( ctx->scratch_tower );
378 0 : fd_hash_t const * their_block_id = &ctx->compact_tower_sync_serde.block_id;
379 0 : fd_hash_t const * their_bank_hash = &ctx->compact_tower_sync_serde.hash;
380 :
381 : /* Similar to what Agave does in cluster_info_vote_listener, we use
382 : the stake associated with a vote account as of our current root's
383 : epoch (which could potentially be a different epoch than the vote
384 : we are counting or when we observe the vote). They default stake
385 : to 0 for voters who are not found. */
386 :
387 0 : fd_tower_stakes_vtr_xid_t xid = { .addr = *vote_acc, .slot = ctx->root_slot };
388 0 : fd_tower_stakes_vtr_t * vtr = fd_tower_stakes_vtr_map_ele_query( ctx->tower_stakes->vtr_map, &xid, NULL, ctx->tower_stakes->vtr_pool );
389 0 : if( FD_UNLIKELY( !vtr ) ) return; /* voter is not staked in current root's epoch */
390 :
391 0 : ulong their_stake = vtr->stake;
392 0 : ulong total_stake = fd_ghost_root( ctx->ghost )->total_stake;
393 :
394 0 : fd_hfork_count_vote( ctx->hfork, &ctx->metrics.hfork, vote_acc, their_block_id, their_bank_hash, their_last_vote->slot, their_stake, total_stake );
395 0 : fd_notar_blk_t * notar_blk = fd_notar_count_vote( ctx->notar, vote_acc, their_last_vote->slot, their_block_id );
396 0 : if( FD_LIKELY( notar_blk ) ) confirm_block( ctx, notar_blk, total_stake );
397 :
398 0 : fd_tower_blk_t * tower_blk = fd_tower_blk_query( ctx->tower_blocks->blk_map, their_last_vote->slot, NULL );
399 0 : if( FD_UNLIKELY( !tower_blk ) ) { ctx->metrics.vote_txn_ignored++; return; }; /* we haven't replayed this block yet */
400 :
401 0 : fd_hash_t const * our_block_id = fd_tower_blocks_canonical_block_id( ctx->tower_blocks, their_last_vote->slot );
402 0 : if( FD_UNLIKELY( 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) { ctx->metrics.vote_txn_ignored++; return; } /* we don't recognize this block id */
403 :
404 : /* Agave decides to count intermediate vote slots in the tower only if
405 : 1. they've replayed the slot and 2. their replay bank hash matches
406 : the vote's bank hash. We do the same thing, but using block_ids.
407 :
408 : It's possible we haven't yet replayed this slot being voted on
409 : because gossip votes can be ahead of our replay.
410 :
411 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */
412 :
413 0 : int skipped_last_vote = 0;
414 0 : for( fd_tower_iter_t iter = fd_tower_iter_init_rev( ctx->scratch_tower );
415 0 : !fd_tower_iter_done_rev( ctx->scratch_tower, iter );
416 0 : iter = fd_tower_iter_prev ( ctx->scratch_tower, iter ) ) {
417 0 : if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; }
418 0 : fd_tower_vote_t const * their_intermediate_vote = fd_tower_iter_ele_const( ctx->scratch_tower, iter );
419 :
420 : /* If we don't recognize an intermediate vote slot in their tower,
421 : it means their tower either:
422 :
423 : 1. Contains intermediate vote slots that are too old (older than
424 : our root) so we already pruned them for tower_forks. Normally
425 : if the descendant (last vote slot) is in tower forks, then all
426 : of its ancestors should be in there too.
427 :
428 : 2. Is invalid. Even though at this point we have successfully
429 : sigverified and deserialized their vote txn, the tower itself
430 : might still be invalid because unlike TPU vote txns, we have
431 : not plumbed through the vote program, but obviously gossip
432 : votes do not so we need to do some light validation here.
433 :
434 : We could throwaway this voter's tower, but we handle it the same
435 : way as Agave which is to just skip this intermediate vote slot:
436 :
437 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
438 :
439 0 : fd_tower_blk_t * fork = fd_tower_blocks_query( ctx->tower_blocks, their_intermediate_vote->slot );
440 0 : if( FD_UNLIKELY( !fork ) ) { ctx->metrics.vote_txn_ignored++; continue; }
441 :
442 : /* Otherwise, we count the vote using our own block id for that slot
443 : (again, mirroring what Agave does albeit with bank hashes).
444 :
445 : Agave uses the current root bank's total stake when counting vote
446 : txns from gossip / replay:
447 :
448 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */
449 :
450 0 : fd_notar_blk_t * notar_blk = fd_notar_count_vote( ctx->notar, vote_acc, their_intermediate_vote->slot, fd_tower_blocks_canonical_block_id( ctx->tower_blocks, their_intermediate_vote->slot ) );
451 0 : if( FD_LIKELY( notar_blk ) ) confirm_block( ctx, notar_blk, total_stake );
452 0 : }
453 0 : }
454 :
455 : static int
456 : deser_auth_vtr( fd_tower_tile_t * ctx,
457 : ulong epoch,
458 : int vote_acc_found,
459 : fd_pubkey_t * authority_out,
460 0 : ulong * authority_idx_out ) {
461 :
462 0 : if( FD_UNLIKELY( !vote_acc_found ) ) return 0;
463 :
464 0 : fd_bincode_decode_ctx_t decode_ctx = {
465 0 : .data = ctx->our_vote_acct,
466 0 : .dataend = ctx->our_vote_acct + ctx->our_vote_acct_sz,
467 0 : };
468 :
469 0 : uchar __attribute__((aligned(FD_VOTE_STATE_VERSIONED_ALIGN))) vote_state_versioned[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
470 :
471 0 : fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( vote_state_versioned, &decode_ctx );
472 0 : FD_CRIT( vsv, "unable to decode vote state versioned" );
473 :
474 0 : fd_pubkey_t const * auth_vtr_addr = NULL;
475 0 : switch( vsv->discriminant ) {
476 0 : case fd_vote_state_versioned_enum_v1_14_11:
477 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v1_14_11.authorized_voters.treap, vsv->inner.v1_14_11.authorized_voters.pool );
478 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
479 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v1_14_11.authorized_voters.pool ) ) {
480 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v1_14_11.authorized_voters.pool );
481 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
482 0 : auth_vtr_addr = &ele->pubkey;
483 0 : break;
484 0 : }
485 0 : }
486 0 : break;
487 0 : case fd_vote_state_versioned_enum_v3:
488 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v3.authorized_voters.treap, vsv->inner.v3.authorized_voters.pool );
489 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
490 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v3.authorized_voters.pool ) ) {
491 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v3.authorized_voters.pool );
492 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
493 0 : auth_vtr_addr = &ele->pubkey;
494 0 : break;
495 0 : }
496 0 : }
497 0 : break;
498 0 : case fd_vote_state_versioned_enum_v4:
499 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->inner.v4.authorized_voters.treap, vsv->inner.v4.authorized_voters.pool );
500 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
501 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->inner.v4.authorized_voters.pool ) ) {
502 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->inner.v4.authorized_voters.pool );
503 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
504 0 : auth_vtr_addr = &ele->pubkey;
505 0 : break;
506 0 : }
507 0 : }
508 0 : break;
509 0 : default:
510 0 : FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", vsv->discriminant ));
511 0 : }
512 :
513 0 : FD_CRIT( auth_vtr_addr, "unable to find authorized voter, likely corrupt vote account state" );
514 :
515 0 : if( fd_pubkey_eq( auth_vtr_addr, ctx->identity_key ) ) {
516 0 : *authority_idx_out = ULONG_MAX;
517 0 : *authority_out = *auth_vtr_addr;
518 0 : return 1;
519 0 : }
520 :
521 0 : auth_vtr_t * auth_vtr = auth_vtr_query( ctx->auth_vtr, *auth_vtr_addr, NULL );
522 0 : if( FD_LIKELY( auth_vtr ) ) {
523 0 : *authority_idx_out = auth_vtr->paths_idx;
524 0 : *authority_out = *auth_vtr_addr;
525 0 : return 1;
526 0 : }
527 :
528 0 : return 0;
529 0 : }
530 :
531 : static inline void
532 : record_eqvoc_metric( fd_tower_tile_t * ctx,
533 0 : int err ) {
534 0 : switch( err ) {
535 :
536 0 : case FD_EQVOC_SUCCESS: break;
537 :
538 0 : case FD_EQVOC_SUCCESS_MERKLE: ctx->metrics.eqvoc_success_merkle++; break;
539 0 : case FD_EQVOC_SUCCESS_META: ctx->metrics.eqvoc_success_meta++; break;
540 0 : case FD_EQVOC_SUCCESS_LAST: ctx->metrics.eqvoc_success_last++; break;
541 0 : case FD_EQVOC_SUCCESS_OVERLAP: ctx->metrics.eqvoc_success_overlap++; break;
542 0 : case FD_EQVOC_SUCCESS_CHAINED: ctx->metrics.eqvoc_success_chained++; break;
543 :
544 0 : case FD_EQVOC_ERR_SERDE: ctx->metrics.eqvoc_err_serde++; break;
545 0 : case FD_EQVOC_ERR_SLOT: ctx->metrics.eqvoc_err_slot++; break;
546 0 : case FD_EQVOC_ERR_VERSION: ctx->metrics.eqvoc_err_version++; break;
547 0 : case FD_EQVOC_ERR_TYPE: ctx->metrics.eqvoc_err_type++; break;
548 0 : case FD_EQVOC_ERR_MERKLE: ctx->metrics.eqvoc_err_merkle++; break;
549 0 : case FD_EQVOC_ERR_SIG: ctx->metrics.eqvoc_err_signature++; break;
550 0 : case FD_EQVOC_ERR_CHUNK_CNT: ctx->metrics.eqvoc_err_chunk_cnt++; break;
551 0 : case FD_EQVOC_ERR_CHUNK_IDX: ctx->metrics.eqvoc_err_chunk_idx++; break;
552 0 : case FD_EQVOC_ERR_CHUNK_LEN: ctx->metrics.eqvoc_err_chunk_len++; break;
553 :
554 0 : case FD_EQVOC_ERR_IGNORED_FROM: ctx->metrics.eqvoc_err_ignored_from++; break;
555 0 : case FD_EQVOC_ERR_IGNORED_SLOT: ctx->metrics.eqvoc_err_ignored_slot++; break;
556 :
557 0 : default: FD_LOG_ERR(( "unhandled eqvoc err %d", err ));
558 0 : }
559 0 : }
560 :
561 : static inline void
562 : reindex_notar( fd_tower_tile_t * ctx,
563 0 : ulong new_root ) {
564 :
565 0 : ulong * reindex = ctx->notar_reindex;
566 0 : for( ulong i = 0; i < FD_VOTER_MAX; i++ ) reindex[i] = ULONG_MAX;
567 :
568 : /* First, for all existing voters, check whether they are in the new
569 : root's epoch. If they are not, remove them, otherwise reindex them
570 : with their new bit position. */
571 :
572 0 : fd_hash_t * removed = ctx->notar_removed;
573 0 : ulong removed_cnt = 0;
574 0 : ulong reindex_cnt = 0;
575 0 : for( ulong i = 0; i < fd_notar_vtr_slot_cnt( ctx->notar->vtr_map ); i++ ) {
576 0 : fd_notar_vtr_t * notar_vtr = &ctx->notar->vtr_map[i];
577 0 : if( FD_UNLIKELY( fd_notar_vtr_key_inval( notar_vtr->addr ) ) ) continue;
578 0 : FD_TEST( notar_vtr->bit!=ULONG_MAX );
579 :
580 0 : fd_tower_stakes_vtr_xid_t stake_xid = { .addr = notar_vtr->addr, .slot = new_root };
581 0 : fd_tower_stakes_vtr_t * stake_vtr = fd_tower_stakes_vtr_map_ele_query( ctx->tower_stakes->vtr_map, &stake_xid, NULL, ctx->tower_stakes->vtr_pool );
582 0 : if( FD_UNLIKELY( !stake_vtr ) ) {
583 0 : reindex[notar_vtr->bit] = ULONG_MAX;
584 0 : notar_vtr->bit = ULONG_MAX;
585 0 : removed[removed_cnt++] = notar_vtr->addr;
586 0 : continue;
587 0 : }
588 0 : reindex[notar_vtr->bit] = reindex_cnt++;
589 0 : notar_vtr->bit = reindex[notar_vtr->bit];
590 0 : notar_vtr->stake = stake_vtr->stake;
591 0 : }
592 0 : for( ulong i = 0; i < removed_cnt; i++ ) {
593 0 : fd_notar_vtr_t * notar_vtr = fd_notar_vtr_query( ctx->notar->vtr_map, removed[i], NULL );
594 0 : FD_TEST( notar_vtr );
595 0 : FD_TEST( !fd_notar_vtr_key_inval( notar_vtr->addr ) );
596 0 : fd_notar_vtr_remove( ctx->notar->vtr_map, notar_vtr );
597 0 : }
598 :
599 : /* Second, find all the voters in the new root's epoch that weren't in
600 : the prior root's epoch, and add them to notar voters. */
601 :
602 0 : for( fd_tower_voters_iter_t iter = fd_tower_voters_iter_init( ctx->tower_voters );
603 0 : !fd_tower_voters_iter_done( ctx->tower_voters, iter );
604 0 : iter = fd_tower_voters_iter_next( ctx->tower_voters, iter ) ) {
605 0 : fd_tower_voters_t * tower_vtr = fd_tower_voters_iter_ele( ctx->tower_voters, iter );
606 0 : fd_notar_vtr_t * notar_vtr = fd_notar_vtr_query( ctx->notar->vtr_map, tower_vtr->vote_acc, NULL );
607 0 : if( FD_UNLIKELY( !notar_vtr ) ) { /* optimize for most existing voters carrying over */
608 0 : fd_tower_stakes_vtr_xid_t stake_xid = { .addr = tower_vtr->vote_acc, .slot = new_root };
609 0 : fd_tower_stakes_vtr_t * stake_vtr = fd_tower_stakes_vtr_map_ele_query( ctx->tower_stakes->vtr_map, &stake_xid, NULL, ctx->tower_stakes->vtr_pool );
610 0 : FD_TEST( stake_vtr ); /* must be in tower_stakes if in tower_voters */
611 :
612 0 : notar_vtr = fd_notar_vtr_insert( ctx->notar->vtr_map, tower_vtr->vote_acc );
613 0 : notar_vtr->stake = stake_vtr->stake;
614 0 : notar_vtr->bit = reindex_cnt++;
615 0 : }
616 0 : }
617 :
618 : /* Finally, reindex all existing slots in notar to reflect the voters'
619 : new bit positions. Note we intentionally do NOT update the stakes
620 : (even though the voter have a new stake in the new epoch) to match
621 : Agave behavior. As a result, it's possible for a notar blk to be
622 : >100% of stake (as of new root epoch). */
623 :
624 0 : for( ulong i = 0; i < fd_notar_slot_slot_cnt( ctx->notar->slot_map ); i++ ) {
625 0 : fd_notar_slot_t * notar_slot = &ctx->notar->slot_map[i];
626 0 : if( FD_UNLIKELY( fd_notar_slot_key_inval( notar_slot->slot ) ) ) continue;
627 :
628 0 : fd_notar_slot_vtrs_t temp[fd_notar_slot_vtrs_word_cnt];
629 0 : fd_notar_slot_vtrs_copy( temp, notar_slot->vtrs );
630 0 : fd_notar_slot_vtrs_null( notar_slot->vtrs );
631 0 : for( ulong idx = fd_notar_slot_vtrs_const_iter_init( temp );
632 0 : !fd_notar_slot_vtrs_const_iter_done( idx );
633 0 : idx = fd_notar_slot_vtrs_const_iter_next( temp, idx ) ) {
634 0 : fd_notar_slot_vtrs_insert_if( notar_slot->vtrs, reindex[idx]!=ULONG_MAX, fd_ulong_min( reindex[idx], fd_notar_slot_vtrs_max( notar_slot->vtrs ) - 1 ) );
635 0 : }
636 0 : }
637 0 : }
638 :
639 : static ulong
640 : update_tower_voters( fd_tower_tile_t * ctx,
641 : ulong bank_idx,
642 0 : ulong slot ) {
643 :
644 0 : fd_bank_t bank[1];
645 0 : if( FD_UNLIKELY( !fd_banks_bank_query( bank, ctx->banks, bank_idx ) ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", bank_idx ));
646 :
647 0 : fd_tower_voters_t * tower_voters = ctx->tower_voters;
648 0 : fd_tower_voters_remove_all( tower_voters );
649 :
650 0 : ulong total_stake = 0UL;
651 0 : ulong prev_voter_idx = ULONG_MAX;
652 :
653 0 : fd_accdb_ro_pipe_t ro_pipe[1];
654 0 : fd_funk_txn_xid_t xid = { .ul = { slot, bank_idx } };
655 0 : fd_accdb_ro_pipe_init( ro_pipe, ctx->accdb, &xid );
656 :
657 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes_locking_modify( bank );
658 0 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
659 :
660 0 : ulong pending_cnt = 0UL;
661 0 : fd_vote_stakes_iter_t * iter = fd_vote_stakes_fork_iter_init( vote_stakes, bank->data->vote_stakes_fork_id, iter_mem );
662 0 : for(;;) {
663 0 : if( FD_UNLIKELY( fd_vote_stakes_fork_iter_done( vote_stakes, bank->data->vote_stakes_fork_id, iter ) ) ) {
664 0 : if( !pending_cnt ) break;
665 0 : fd_accdb_ro_pipe_flush( ro_pipe );
666 0 : } else {
667 0 : fd_pubkey_t vote_acc;
668 0 : ulong stake;
669 0 : fd_vote_stakes_fork_iter_ele( vote_stakes, bank->data->vote_stakes_fork_id, iter, &vote_acc, NULL, &stake, NULL, NULL );
670 0 : if( FD_LIKELY( stake ) ) {
671 0 : fd_accdb_ro_pipe_enqueue( ro_pipe, vote_acc.key );
672 0 : pending_cnt++;
673 0 : }
674 0 : fd_vote_stakes_fork_iter_next( vote_stakes, bank->data->vote_stakes_fork_id, iter );
675 0 : }
676 :
677 0 : fd_accdb_ro_t * ro;
678 0 : while( (ro = fd_accdb_ro_pipe_poll( ro_pipe )) ) {
679 0 : pending_cnt--;
680 0 : fd_pubkey_t const * vote_acc = fd_accdb_ref_address( ro );
681 :
682 0 : ulong stake; fd_pubkey_t id; int found = fd_vote_stakes_query_t_2( vote_stakes, bank->data->vote_stakes_fork_id, vote_acc, &stake, &id );
683 0 : FD_TEST( found );
684 0 : FD_TEST( stake );
685 0 : total_stake += stake;
686 :
687 0 : if( FD_UNLIKELY( !fd_accdb_ref_lamports( ro ) || !fd_vsv_is_correct_size_and_initialized( ro->meta ) ) ) continue;
688 :
689 0 : fd_tower_voters_t acct = { .id = id, .vote_acc = *vote_acc, .stake = stake };
690 0 : fd_memcpy( acct.data, fd_accdb_ref_data_const( ro ), fd_ulong_min( fd_accdb_ref_data_sz( ro ), FD_VOTE_STATE_DATA_MAX ) );
691 0 : fd_tower_voters_push_tail( tower_voters, acct );
692 0 : prev_voter_idx = fd_tower_stakes_insert( ctx->tower_stakes, slot, vote_acc, stake, prev_voter_idx );
693 0 : }
694 0 : }
695 :
696 0 : fd_bank_vote_stakes_end_locking_modify( bank );
697 0 : fd_accdb_ro_pipe_fini( ro_pipe );
698 :
699 0 : return total_stake;
700 0 : }
701 :
702 : static void
703 : replay_slot_completed( fd_tower_tile_t * ctx,
704 : fd_replay_slot_completed_t * slot_completed,
705 : ulong tsorig,
706 0 : fd_stem_context_t * stem ) {
707 :
708 : /* Sanity checks. */
709 :
710 0 : FD_TEST( 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
711 0 : FD_TEST( ctx->init_slot==ULONG_MAX || 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
712 :
713 : /* The first replay_slot_completed is always either the snapshot slot
714 : or genesis slot, which we use to initialize our slot and
715 : epoch-related metadata. */
716 :
717 0 : int is_booting = ctx->init_slot==ULONG_MAX;
718 0 : if( FD_UNLIKELY( is_booting ) ) {
719 0 : ctx->init_slot = slot_completed->slot;
720 0 : ctx->root_slot = slot_completed->slot;
721 0 : }
722 :
723 : /* Due to asynchronous frag processing, it's possible this block from
724 : replay_slot_completed is on a minority fork Tower already pruned
725 : after publishing a new root. */
726 :
727 0 : if( FD_UNLIKELY( slot_completed->slot!=ctx->init_slot && !fd_ghost_query( ctx->ghost, &slot_completed->parent_block_id ) ) ) {
728 0 : ctx->metrics.slot_ignored_cnt++;
729 0 : ctx->metrics.slot_ignored_gauge = slot_completed->slot;
730 :
731 : /* Still need to return a message to replay so the refcnt on the
732 : bank is decremented. */
733 :
734 0 : fd_tower_slot_ignored_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
735 0 : msg->slot = slot_completed->slot;
736 0 : msg->bank_idx = slot_completed->bank_idx;
737 :
738 0 : fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_IGNORED, ctx->out_chunk, sizeof(fd_tower_slot_ignored_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
739 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_ignored_t), ctx->out_chunk0, ctx->out_wmark );
740 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
741 0 : return;
742 0 : }
743 :
744 : /* Reconcile our local tower with the on-chain tower (stored inside
745 : our vote account). */
746 :
747 0 : ulong our_vote_acct_bal = ULONG_MAX;
748 0 : int found = 0;
749 0 : fd_funk_txn_xid_t xid = { .ul = { slot_completed->slot, slot_completed->bank_idx } };
750 0 : fd_accdb_ro_t ro[1];
751 0 : if( FD_LIKELY( fd_accdb_open_ro( ctx->accdb, ro, &xid, ctx->vote_account ) ) ) {
752 0 : found = 1;
753 0 : ctx->our_vote_acct_sz = fd_ulong_min( fd_accdb_ref_data_sz( ro ), FD_VOTE_STATE_DATA_MAX );
754 0 : our_vote_acct_bal = fd_accdb_ref_lamports( ro );
755 0 : fd_memcpy( ctx->our_vote_acct, fd_accdb_ref_data_const( ro ), ctx->our_vote_acct_sz );
756 0 : fd_accdb_close_ro( ctx->accdb, ro );
757 0 : fd_tower_reconcile( ctx->tower, ctx->root_slot, ctx->our_vote_acct );
758 0 : }
759 :
760 : /* Check for equivocation (already received a replay_slot_completed
761 : for this slot). */
762 :
763 0 : fd_tower_blk_t * eqvoc_tower_blk = NULL;
764 0 : if( FD_UNLIKELY( eqvoc_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, slot_completed->slot ) ) ) {
765 :
766 : /* As fd_replay_slot_completed guarantees, we process at most 2
767 : equivocating blocks for a given slot. fd_tower_{...} only stores
768 : one version of a block (by design, given the tower protocol's
769 : limitations ... see notes in fd_tower_blocks.h). We also know
770 : when seeing the same slot a second time that second block is
771 : confirmed, also guaranteed by fd_replay_slot_completed ... see
772 : notes in fd_replay_tile.h).
773 :
774 : So we retain the existing tower_block we have (which contains the
775 : first replayed_block_id as well as the voted_block_id if we did
776 : indeed vote for it). We clear out the slot from the other tower
777 : adjacent structures, and re-insert into them with the confirmed
778 : version of the slot . */
779 :
780 0 : FD_TEST( eqvoc_tower_blk->confirmed ); /* check the confirmed bit is set (second replay_slot_completed version must be confirmed) */
781 0 : fd_tower_leaves_remove( ctx->tower_leaves, slot_completed->slot );
782 0 : fd_tower_lockos_remove( ctx->tower_lockos, slot_completed->slot );
783 0 : fd_tower_stakes_remove( ctx->tower_stakes, slot_completed->slot );
784 :
785 : /* If the previous equivocating version had a different parent than
786 : the new version, then we need to ensure the original equivocating
787 : block's parent is restored as a tower leaf.
788 :
789 : TODO check agave doesn't have equivocating? */
790 :
791 0 : if( FD_UNLIKELY( eqvoc_tower_blk->parent_slot != slot_completed->parent_slot &&
792 0 : !fd_tower_leaves_map_ele_query( ctx->tower_leaves->map, &eqvoc_tower_blk->parent_slot, NULL, ctx->tower_leaves->pool ) /* added by another leaf */ ) ) {
793 0 : fd_tower_leaf_t * leaf = fd_tower_leaves_pool_ele_acquire( ctx->tower_leaves->pool );
794 0 : leaf->slot = eqvoc_tower_blk->parent_slot;
795 0 : fd_tower_leaves_map_ele_insert( ctx->tower_leaves->map, leaf, ctx->tower_leaves->pool );
796 0 : fd_tower_leaves_dlist_ele_push_tail( ctx->tower_leaves->dlist, leaf, ctx->tower_leaves->pool );
797 0 : eqvoc_tower_blk->parent_slot = slot_completed->parent_slot;
798 0 : }
799 :
800 0 : ctx->metrics.slot_eqvoced_cnt++;
801 0 : ctx->metrics.slot_eqvoced_gauge = slot_completed->slot;
802 0 : } else {
803 :
804 : /* Otherwise this is the first replay of this block, so insert a new
805 : tower_blk. */
806 :
807 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_insert( ctx->tower_blocks, slot_completed->slot, slot_completed->parent_slot );
808 0 : tower_blk->parent_slot = slot_completed->parent_slot;
809 0 : tower_blk->epoch = slot_completed->epoch;
810 0 : tower_blk->replayed = 1;
811 0 : tower_blk->replayed_block_id = slot_completed->block_id;
812 0 : tower_blk->voted = 0;
813 0 : tower_blk->confirmed = 0;
814 0 : tower_blk->bank_idx = slot_completed->bank_idx;
815 0 : }
816 :
817 : /* Upsert tower_{...} structures. */
818 :
819 0 : ulong total_stake = update_tower_voters( ctx, slot_completed->bank_idx, slot_completed->slot );
820 0 : fd_tower_leaves_upsert( ctx->tower_leaves, slot_completed->slot, slot_completed->parent_slot );
821 :
822 : /* Insert into ghost */
823 :
824 0 : fd_ghost_blk_t * ghost_blk = fd_ghost_insert( ctx->ghost, &slot_completed->block_id, fd_ptr_if( slot_completed->slot!=ctx->init_slot, &slot_completed->parent_block_id, NULL ), slot_completed->slot );
825 0 : ghost_blk->total_stake = total_stake;
826 :
827 : /* Insert into hard fork detector. */
828 :
829 0 : fd_hfork_record_our_bank_hash( ctx->hfork, &slot_completed->block_id, &slot_completed->bank_hash, fd_ghost_root( ctx->ghost )->total_stake );
830 :
831 0 : fd_tower_voters_t * tower_voters = ctx->tower_voters;
832 0 : for( fd_tower_voters_iter_t iter = fd_tower_voters_iter_init( tower_voters );
833 0 : !fd_tower_voters_iter_done( tower_voters, iter );
834 0 : iter = fd_tower_voters_iter_next( tower_voters, iter ) ) {
835 0 : fd_tower_voters_t * acct = fd_tower_voters_iter_ele( tower_voters, iter );
836 :
837 : /* 1. Update forks with lockouts. */
838 :
839 0 : fd_tower_lockos_insert( ctx->tower_lockos, slot_completed->slot, &acct->vote_acc, acct );
840 :
841 : /* 2. Count the last vote slot in the vote state towards ghost. */
842 :
843 0 : ulong vote_slot = fd_vote_acc_vote_slot( acct->data );
844 0 : if( FD_LIKELY( vote_slot!=ULONG_MAX && /* has voted */
845 0 : vote_slot>=fd_ghost_root( ctx->ghost )->slot ) ) { /* vote not too old */
846 :
847 : /* We search up the ghost ancestry to find the ghost block for
848 : this vote slot. In Agave, they look this value up using a
849 : hashmap of slot->bank hash ("fork progress"), but that
850 : approach only works because they dump and repair (so there's
851 : only ever one canonical bank hash). We retain multiple block
852 : ids, both the original and confirmed one. */
853 :
854 0 : fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */
855 :
856 : /* It is impossible for ancestor_blk to be missing, because
857 : these are vote accounts on a given fork, not vote txns across
858 : forks. So we know these towers must contain slots we know
859 : about as long as they are >= root, which we checked above. */
860 :
861 0 : if( FD_UNLIKELY( !ancestor_blk ) ) {
862 0 : FD_BASE58_ENCODE_32_BYTES( acct->vote_acc.key, pubkey_b58 );
863 0 : FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_completed->slot, vote_slot, pubkey_b58 ));
864 0 : }
865 :
866 0 : fd_ghost_count_vote( ctx->ghost, ancestor_blk, &acct->vote_acc, acct->stake, vote_slot );
867 0 : }
868 0 : }
869 :
870 : /* Determine reset, vote, and root slots. There may not be a vote or
871 : root slot but there is always a reset slot. */
872 :
873 0 : fd_tower_out_t out = fd_tower_vote_and_reset( ctx->tower, ctx->tower_blocks, ctx->tower_leaves, ctx->tower_lockos, ctx->tower_stakes, ctx->tower_voters, ctx->ghost, ctx->notar );
874 :
875 : /* Update forks if there is a vote slot. */
876 :
877 0 : if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) {
878 :
879 : /* We might not be voting for the current replay slot because the
880 : best_blk (according to ghost) could be another fork. */
881 :
882 0 : fd_tower_blk_t * vote_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, out.vote_slot );
883 0 : vote_tower_blk->voted = 1;
884 0 : vote_tower_blk->voted_block_id = out.vote_block_id;
885 0 : }
886 :
887 : /* Publish structures if there is a new root. */
888 :
889 0 : if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) {
890 0 : if( FD_UNLIKELY( 0==memcmp( &out.root_block_id, &hash_null, sizeof(fd_hash_t) ) ) ) {
891 0 : FD_LOG_CRIT(( "invariant violation: root block id is null at slot %lu", out.root_slot ));
892 0 : }
893 :
894 0 : fd_tower_blk_t * oldr_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, ctx->root_slot );
895 0 : fd_tower_blk_t * newr_tower_blk = fd_tower_blocks_query( ctx->tower_blocks, out.root_slot );
896 0 : FD_TEST( oldr_tower_blk );
897 0 : FD_TEST( newr_tower_blk );
898 :
899 : /* It is a Solana consensus protocol invariant that a validator must
900 : make at least one root in an epoch, so the root's epoch cannot
901 : advance by more than one. */
902 :
903 0 : FD_TEST( oldr_tower_blk->epoch==newr_tower_blk->epoch || oldr_tower_blk->epoch+1==newr_tower_blk->epoch ); /* root can only move forward one epoch */
904 :
905 : /* Publish notar: 1. reindex if it's a new epoch. 2. publish the new
906 : root to notar. */
907 :
908 0 : if( FD_UNLIKELY( ctx->notar->root==ULONG_MAX || oldr_tower_blk->epoch+1==newr_tower_blk->epoch ) ) {
909 0 : FD_TEST( newr_tower_blk->epoch==slot_completed->epoch ); /* new root's epoch must be same as current slot_completed */
910 0 : reindex_notar( ctx, out.root_slot );
911 0 : fd_eqvoc_update_voters( ctx->eqvoc, ctx->tower_voters );
912 0 : }
913 0 : fd_notar_publish( ctx->notar, out.root_slot );
914 :
915 : /* Publish tower_blocks and tower_stakes: 1. remove any entries
916 : older than the new root. */
917 :
918 0 : for( ulong slot = ctx->root_slot; slot < out.root_slot; slot++ ) {
919 0 : fd_tower_blocks_remove( ctx->tower_blocks, slot );
920 0 : fd_tower_leaves_remove( ctx->tower_leaves, slot );
921 0 : fd_tower_lockos_remove( ctx->tower_lockos, slot );
922 0 : fd_tower_stakes_remove( ctx->tower_stakes, slot );
923 0 : }
924 :
925 : /* Publish ghost. 1. walk up the ghost ancestry to publish new root
926 : frags for intermediate slots we couldn't vote for. 2. publish
927 : the new root to ghost. */
928 :
929 0 : fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id );
930 0 : fd_ghost_blk_t * oldr = fd_ghost_root( ctx->ghost );
931 0 : fd_ghost_blk_t * curr = newr;
932 :
933 : /* It's possible our new root skips intermediate slots between our
934 : old root. This can happen if we couldn't vote for those
935 : intermediate slots but ended up making a new root on a descendant
936 : of those same slots. */
937 :
938 0 : while( FD_LIKELY( curr!=oldr ) ) {
939 0 : publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_ROOTED, .msg = { .slot_rooted = { .slot = curr->slot, .block_id = curr->id } } } );
940 0 : curr = fd_ghost_parent( ctx->ghost, curr );
941 0 : }
942 0 : fd_ghost_publish( ctx->ghost, newr );
943 :
944 : /* Update the new root. */
945 :
946 0 : ctx->root_slot = out.root_slot;
947 0 : }
948 :
949 : /* Publish a slot_done frag to tower_out. */
950 :
951 0 : fd_tower_slot_done_t * msg = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
952 0 : msg->replay_slot = slot_completed->slot;
953 0 : msg->active_fork_cnt = fd_tower_leaves_pool_used( ctx->tower_leaves->pool );
954 0 : msg->vote_slot = out.vote_slot;
955 0 : msg->reset_slot = out.reset_slot;
956 0 : msg->reset_block_id = out.reset_block_id;
957 0 : msg->root_slot = out.root_slot;
958 0 : msg->root_block_id = out.root_block_id;
959 0 : msg->replay_bank_idx = slot_completed->bank_idx;
960 0 : msg->vote_acct_bal = our_vote_acct_bal;
961 :
962 : /* Populate slot_done with a vote txn representing our current tower
963 : (regardless of whether there was a new vote slot or not).
964 :
965 : TODO only do this on refresh_last_vote? */
966 :
967 0 : ulong authority_idx = ULONG_MAX;
968 0 : fd_pubkey_t authority[1];
969 0 : int found_authority = deser_auth_vtr( ctx, slot_completed->epoch, found, authority, &authority_idx );
970 :
971 0 : if( FD_LIKELY( found_authority ) ) {
972 0 : msg->has_vote_txn = 1;
973 0 : fd_txn_p_t txn[1];
974 0 : fd_tower_to_vote_txn( ctx->tower, ctx->root_slot, &slot_completed->bank_hash, &slot_completed->block_id, &slot_completed->block_hash, ctx->identity_key, authority, ctx->vote_account, txn );
975 0 : FD_TEST( !fd_tower_empty( ctx->tower ) );
976 0 : FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
977 0 : fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
978 0 : msg->vote_txn_sz = txn->payload_sz;
979 0 : msg->authority_idx = authority_idx;
980 0 : } else {
981 0 : msg->has_vote_txn = 0;
982 0 : }
983 :
984 0 : msg->tower_cnt = 0UL;
985 0 : if( FD_LIKELY( found ) ) msg->tower_cnt = fd_tower_with_lat_from_vote_acc( msg->tower, ctx->our_vote_acct );
986 :
987 0 : fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DONE, ctx->out_chunk, sizeof(fd_tower_slot_done_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
988 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_done_t), ctx->out_chunk0, ctx->out_wmark );
989 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
990 :
991 : /* Write out metrics. */
992 :
993 0 : ctx->metrics.replay_slot = slot_completed->slot;
994 0 : ctx->metrics.vote_slot = out.vote_slot;
995 0 : ctx->metrics.reset_slot = out.reset_slot; /* always set */
996 0 : ctx->metrics.root_slot = ctx->root_slot;
997 0 : ctx->metrics.init_slot = ctx->init_slot;
998 :
999 0 : ctx->metrics.ancestor_rollback += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK );
1000 0 : ctx->metrics.sibling_confirmed += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SIBLING_CONFIRMED );
1001 0 : ctx->metrics.same_fork += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SAME_FORK );
1002 0 : ctx->metrics.switch_pass += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS );
1003 0 : ctx->metrics.switch_fail += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL );
1004 0 : ctx->metrics.lockout_fail += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_LOCKOUT_FAIL );
1005 0 : ctx->metrics.threshold_fail += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_THRESHOLD_FAIL );
1006 0 : ctx->metrics.propagated_fail += (ulong)fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_PROPAGATED_FAIL );
1007 :
1008 : /* Log out structures. */
1009 :
1010 0 : char cstr[4096]; ulong cstr_sz;
1011 0 : FD_LOG_DEBUG(( "\n\n%s", fd_ghost_to_cstr( ctx->ghost, fd_ghost_root( ctx->ghost ), cstr, sizeof(cstr), &cstr_sz ) ));
1012 0 : FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( ctx->tower, ctx->root_slot, cstr ) ));
1013 0 : }
1014 :
1015 : FD_FN_CONST static inline ulong
1016 0 : scratch_align( void ) {
1017 0 : return 128UL;
1018 0 : }
1019 :
1020 : FD_FN_PURE static inline ulong
1021 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
1022 0 : ulong slot_max = tile->tower.max_live_slots;
1023 0 : ulong blk_max = slot_max * EQVOC_MAX;
1024 0 : ulong fec_max = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
1025 0 : ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
1026 0 : ulong l = FD_LAYOUT_INIT;
1027 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1028 0 : l = FD_LAYOUT_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1029 : /*Â auth_vtr_keyswitch */
1030 0 : l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, FD_VOTER_MAX ) );
1031 0 : l = FD_LAYOUT_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, FD_VOTER_MAX ) );
1032 0 : l = FD_LAYOUT_APPEND( l, fd_hfork_align(), fd_hfork_footprint( slot_max, FD_VOTER_MAX ) );
1033 0 : l = FD_LAYOUT_APPEND( l, fd_notar_align(), fd_notar_footprint( slot_max ) );
1034 0 : l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint() );
1035 0 : l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint() );
1036 0 : l = FD_LAYOUT_APPEND( l, fd_tower_blocks_align(), fd_tower_blocks_footprint( slot_max ) );
1037 0 : l = FD_LAYOUT_APPEND( l, fd_tower_leaves_align(), fd_tower_leaves_footprint( slot_max ) );
1038 0 : l = FD_LAYOUT_APPEND( l, fd_tower_lockos_align(), fd_tower_lockos_footprint( slot_max, FD_VOTER_MAX ) );
1039 0 : l = FD_LAYOUT_APPEND( l, fd_tower_stakes_align(), fd_tower_stakes_footprint( slot_max ) );
1040 0 : l = FD_LAYOUT_APPEND( l, fd_tower_voters_align(), fd_tower_voters_footprint( FD_VOTER_MAX ) );
1041 0 : l = FD_LAYOUT_APPEND( l, publishes_align(), publishes_footprint( pub_max ) );
1042 0 : return FD_LAYOUT_FINI( l, scratch_align() );
1043 0 : }
1044 :
1045 : static void
1046 0 : during_housekeeping( fd_tower_tile_t * ctx ) {
1047 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
1048 0 : fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED );
1049 0 : }
1050 :
1051 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
1052 0 : fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( ctx->auth_vtr_keyswitch->bytes );
1053 0 : if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) FD_LOG_CRIT(( "keyswitch: duplicate authorized voter key, keys not synced up with sign tile" ));
1054 0 : if( FD_UNLIKELY( ctx->auth_vtr_path_cnt==AUTH_VOTERS_MAX ) ) FD_LOG_CRIT(( "keyswitch: too many authorized voters, keys not synced up with sign tile" ));
1055 :
1056 0 : auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
1057 0 : auth_vtr->paths_idx = ctx->auth_vtr_path_cnt;
1058 0 : ctx->auth_vtr_path_cnt++;
1059 0 : fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1060 0 : }
1061 :
1062 : /* FIXME: Currently, the tower tile doesn't support set-identity with
1063 : a tower file. When support for a tower file is added, we need to
1064 : swap the file that is running and sync it to the local state of
1065 : the tower. Because a tower file is not supported, if another
1066 : validator was running with the identity that was switched to, then
1067 : it is possible that the original validator and the fallback (this
1068 : node), may have tower files which are out of sync. This could lead
1069 : to consensus violations such as double voting or duplicate
1070 : confirmations. Currently it is unsafe for a validator operator to
1071 : switch identities without a 512 slot delay: the reason for this
1072 : delay is to account for the worst case number of slots a vote
1073 : account can be locked out for. */
1074 :
1075 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
1076 0 : FD_LOG_DEBUG(( "keyswitch: unhalting signing" ));
1077 0 : FD_CRIT( ctx->halt_signing, "state machine corruption" );
1078 0 : ctx->halt_signing = 0;
1079 0 : fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1080 0 : }
1081 :
1082 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
1083 0 : FD_LOG_DEBUG(( "keyswitch: halting signing" ));
1084 0 : memcpy( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL );
1085 0 : fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1086 0 : ctx->halt_signing = 1;
1087 0 : ctx->identity_keyswitch->result = ctx->out_seq;
1088 0 : }
1089 0 : }
1090 :
1091 : static inline void
1092 0 : metrics_write( fd_tower_tile_t * ctx ) {
1093 0 : FD_MCNT_SET ( TOWER, SLOT_IGNORED_CNT, ctx->metrics.slot_ignored_cnt );
1094 0 : FD_MGAUGE_SET( TOWER, SLOT_IGNORED_GAUGE, ctx->metrics.slot_ignored_gauge );
1095 :
1096 0 : FD_MGAUGE_SET( TOWER, REPLAY_SLOT, ctx->metrics.replay_slot );
1097 0 : FD_MGAUGE_SET( TOWER, VOTE_SLOT, ctx->metrics.vote_slot );
1098 0 : FD_MGAUGE_SET( TOWER, RESET_SLOT, ctx->metrics.reset_slot );
1099 0 : FD_MGAUGE_SET( TOWER, ROOT_SLOT, ctx->metrics.root_slot );
1100 0 : FD_MGAUGE_SET( TOWER, INIT_SLOT, ctx->metrics.init_slot );
1101 :
1102 0 : FD_MCNT_SET( TOWER, ANCESTOR_ROLLBACK, ctx->metrics.ancestor_rollback );
1103 0 : FD_MCNT_SET( TOWER, SIBLING_CONFIRMED, ctx->metrics.sibling_confirmed );
1104 0 : FD_MCNT_SET( TOWER, SAME_FORK, ctx->metrics.same_fork );
1105 0 : FD_MCNT_SET( TOWER, SWITCH_PASS, ctx->metrics.switch_pass );
1106 0 : FD_MCNT_SET( TOWER, SWITCH_FAIL, ctx->metrics.switch_fail );
1107 0 : FD_MCNT_SET( TOWER, LOCKOUT_FAIL, ctx->metrics.lockout_fail );
1108 0 : FD_MCNT_SET( TOWER, THRESHOLD_FAIL, ctx->metrics.threshold_fail );
1109 0 : FD_MCNT_SET( TOWER, PROPAGATED_FAIL, ctx->metrics.propagated_fail );
1110 :
1111 0 : FD_MCNT_SET( TOWER, VOTE_TXN_INVALID, ctx->metrics.vote_txn_invalid );
1112 0 : FD_MCNT_SET( TOWER, VOTE_TXN_IGNORED, ctx->metrics.vote_txn_ignored );
1113 :
1114 0 : FD_MCNT_SET( TOWER, EQVOC_SUCCESS_MERKLE, ctx->metrics.eqvoc_success_merkle );
1115 0 : FD_MCNT_SET( TOWER, EQVOC_SUCCESS_META, ctx->metrics.eqvoc_success_meta );
1116 0 : FD_MCNT_SET( TOWER, EQVOC_SUCCESS_LAST, ctx->metrics.eqvoc_success_last );
1117 0 : FD_MCNT_SET( TOWER, EQVOC_SUCCESS_OVERLAP, ctx->metrics.eqvoc_success_overlap );
1118 0 : FD_MCNT_SET( TOWER, EQVOC_SUCCESS_CHAINED, ctx->metrics.eqvoc_success_chained );
1119 :
1120 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_SERDE, ctx->metrics.eqvoc_err_serde );
1121 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_SLOT, ctx->metrics.eqvoc_err_slot );
1122 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_VERSION, ctx->metrics.eqvoc_err_version );
1123 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_TYPE, ctx->metrics.eqvoc_err_type );
1124 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_MERKLE, ctx->metrics.eqvoc_err_merkle );
1125 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_SIGNATURE, ctx->metrics.eqvoc_err_signature );
1126 :
1127 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_CNT, ctx->metrics.eqvoc_err_chunk_cnt );
1128 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_IDX, ctx->metrics.eqvoc_err_chunk_idx );
1129 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_CHUNK_LEN, ctx->metrics.eqvoc_err_chunk_len );
1130 :
1131 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_IGNORED_FROM, ctx->metrics.eqvoc_err_ignored_from );
1132 0 : FD_MCNT_SET( TOWER, EQVOC_ERR_IGNORED_SLOT, ctx->metrics.eqvoc_err_ignored_slot );
1133 :
1134 0 : FD_MCNT_SET( TOWER, EQVOC_PROOF_CONSTRUCTED, ctx->metrics.eqvoc_proof_constructed );
1135 0 : FD_MCNT_SET( TOWER, EQVOC_PROOF_VERIFIED, ctx->metrics.eqvoc_proof_verified );
1136 :
1137 0 : FD_MCNT_SET ( TOWER, HARD_FORKS_SEEN, ctx->metrics.hfork.seen );
1138 0 : FD_MCNT_SET ( TOWER, HARD_FORKS_PRUNED, ctx->metrics.hfork.pruned );
1139 0 : FD_MGAUGE_SET( TOWER, HARD_FORKS_ACTIVE, ctx->metrics.hfork.active );
1140 0 : FD_MGAUGE_SET( TOWER, HARD_FORKS_MAX_WIDTH, ctx->metrics.hfork.max_width );
1141 0 : }
1142 :
1143 : static inline void
1144 : after_credit( fd_tower_tile_t * ctx,
1145 : fd_stem_context_t * stem,
1146 : int * opt_poll_in,
1147 0 : int * charge_busy ) {
1148 0 : if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) {
1149 0 : publish_t * pub = publishes_pop_head_nocopy( ctx->publishes );
1150 0 : memcpy( fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ), &pub->msg, sizeof(fd_tower_msg_t) );
1151 0 : fd_stem_publish( stem, OUT_IDX, pub->sig, ctx->out_chunk, sizeof(fd_tower_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) );
1152 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_msg_t), ctx->out_chunk0, ctx->out_wmark );
1153 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
1154 0 : *opt_poll_in = 0; /* drain the publishes */
1155 0 : *charge_busy = 1;
1156 0 : }
1157 0 : }
1158 :
1159 : static inline int
1160 : returnable_frag( fd_tower_tile_t * ctx,
1161 : ulong in_idx,
1162 : ulong seq FD_PARAM_UNUSED,
1163 : ulong sig,
1164 : ulong chunk,
1165 : ulong sz,
1166 : ulong ctl FD_PARAM_UNUSED,
1167 : ulong tsorig,
1168 : ulong tspub FD_PARAM_UNUSED,
1169 0 : fd_stem_context_t * stem ) {
1170 :
1171 0 : if( FD_UNLIKELY( !ctx->in[ in_idx ].mcache_only && ( chunk<ctx->in[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) )
1172 0 : FD_LOG_ERR(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark ));
1173 :
1174 0 : switch( ctx->in_kind[ in_idx ] ) {
1175 0 : case IN_KIND_DEDUP: {
1176 0 : if( FD_UNLIKELY( ctx->root_slot==ULONG_MAX ) ) return 1;
1177 0 : fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
1178 0 : FD_TEST( txnm->payload_sz<=FD_TPU_MTU );
1179 0 : FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ );
1180 0 : count_vote( ctx, fd_txn_m_txn_t_const( txnm ), fd_txn_m_payload_const( txnm ) );
1181 0 : return 0;
1182 0 : }
1183 0 : case IN_KIND_EPOCH: {
1184 0 : fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
1185 0 : fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
1186 0 : return 0;
1187 0 : }
1188 0 : case IN_KIND_GOSSIP: {
1189 0 : if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED ) ) {
1190 0 : fd_gossip_update_message_t const * msg = (fd_gossip_update_message_t const *)fd_type_pun_const( fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) );
1191 0 : fd_gossip_duplicate_shred_t const * duplicate_shred = msg->duplicate_shred;
1192 0 : fd_pubkey_t const * from = (fd_pubkey_t const *)fd_type_pun_const( msg->origin );
1193 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, duplicate_shred->slot );
1194 0 : fd_tower_slot_duplicate_t * out = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
1195 0 : int eqvoc_err = fd_eqvoc_chunk_insert( ctx->eqvoc, ctx->shred_version, ctx->root_slot, lsched, from, duplicate_shred, out->chunks );
1196 0 : record_eqvoc_metric( ctx, eqvoc_err );
1197 0 : if( FD_UNLIKELY( eqvoc_err>0 ) ) {
1198 0 : ctx->metrics.eqvoc_proof_verified++;
1199 0 : fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DUPLICATE, ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
1200 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), ctx->out_chunk0, ctx->out_wmark );
1201 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
1202 0 : }
1203 0 : }
1204 0 : return 0;
1205 0 : }
1206 0 : case IN_KIND_IPECHO: {
1207 0 : FD_TEST( sig!=0UL && sig<=USHORT_MAX );
1208 0 : ctx->shred_version = (ushort)sig;
1209 0 : return 0;
1210 0 : }
1211 0 : case IN_KIND_REPLAY: {
1212 : /* In the case that the tower tile is halting signing, we don't
1213 : want to process any replay fragments that will cause us to
1214 : produce a vote txn. */
1215 0 : if( FD_UNLIKELY( ctx->halt_signing ) ) return 1;
1216 :
1217 0 : if( FD_LIKELY( sig==REPLAY_SIG_TXN_EXECUTED ) ) {
1218 :
1219 : /* Agave only counts replay vote txns that executed successfully.
1220 : https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/bank_utils.rs#L53 */
1221 :
1222 0 : fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ) );
1223 0 : if( FD_UNLIKELY( !txn_executed->is_committable || txn_executed->is_fees_only || txn_executed->txn_err ) ) return 0;
1224 0 : count_vote( ctx, TXN(txn_executed->txn), txn_executed->txn->payload );
1225 0 : } else if( FD_LIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) {
1226 0 : fd_replay_slot_completed_t * slot_completed = (fd_replay_slot_completed_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
1227 0 : replay_slot_completed( ctx, slot_completed, tsorig, stem );
1228 0 : } else if( FD_LIKELY( sig==REPLAY_SIG_SLOT_DEAD ) ) {
1229 0 : fd_replay_slot_dead_t * slot_dead = (fd_replay_slot_dead_t *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
1230 0 : fd_hfork_record_our_bank_hash( ctx->hfork, &slot_dead->block_id, NULL, fd_ghost_root( ctx->ghost )->total_stake );
1231 0 : }
1232 0 : return 0;
1233 0 : }
1234 0 : case IN_KIND_SHRED: {
1235 0 : if( FD_LIKELY( sz==FD_SHRED_MIN_SZ || sz==FD_SHRED_MAX_SZ ) ) { /* TODO depends on pending shred_out changes */
1236 0 : fd_shred_t * shred = (fd_shred_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
1237 0 : fd_tower_slot_duplicate_t * out = fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk );
1238 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, shred->slot );
1239 0 : int eqvoc_err = fd_eqvoc_shred_insert( ctx->eqvoc, ctx->shred_version, ctx->root_slot, lsched, shred, out->chunks );
1240 0 : record_eqvoc_metric( ctx, eqvoc_err );
1241 0 : if( FD_UNLIKELY( eqvoc_err>0 ) ) {
1242 0 : ctx->metrics.eqvoc_proof_constructed++;
1243 0 : fd_stem_publish( stem, OUT_IDX, FD_TOWER_SIG_SLOT_DUPLICATE, ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), 0UL, tsorig, fd_frag_meta_ts_comp( fd_tickcount() ) );
1244 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_slot_duplicate_t), ctx->out_chunk0, ctx->out_wmark );
1245 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
1246 0 : }
1247 0 : }
1248 0 : return 0;
1249 0 : }
1250 0 : default: {
1251 0 : FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] ));
1252 0 : }
1253 0 : }
1254 0 : }
1255 :
1256 : static void
1257 : privileged_init( fd_topo_t * topo,
1258 0 : fd_topo_tile_t * tile ) {
1259 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1260 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1261 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1262 0 : void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1263 0 : FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
1264 :
1265 0 : FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) );
1266 :
1267 0 : if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "identity_key_path not set" ));
1268 0 : ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) );
1269 :
1270 : /* The vote key can be specified either directly as a base58 encoded
1271 : pubkey, or as a file path. We first try to decode as a pubkey. */
1272 :
1273 0 : uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc );
1274 0 : if( FD_UNLIKELY( !vote_key ) ) ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) );
1275 :
1276 0 : ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) );
1277 0 : for( ulong i=0UL; i<tile->tower.authorized_voter_paths_cnt; i++ ) {
1278 0 : fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.authorized_voter_paths[ i ], /* pubkey only: */ 1 ) );
1279 0 : if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) {
1280 0 : FD_BASE58_ENCODE_32_BYTES( pubkey.uc, pubkey_b58 );
1281 0 : FD_LOG_ERR(( "authorized voter key duplicate %s", pubkey_b58 ));
1282 0 : }
1283 :
1284 0 : auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
1285 0 : auth_vtr->paths_idx = i;
1286 0 : }
1287 0 : ctx->auth_vtr_path_cnt = tile->tower.authorized_voter_paths_cnt;
1288 :
1289 : /* The tower file is used to checkpt and restore the state of the
1290 : local tower. */
1291 :
1292 0 : char path[ PATH_MAX ];
1293 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
1294 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin.new", tile->tower.base_path, identity_key_b58 ) );
1295 0 : ctx->checkpt_fd = open( path, O_WRONLY|O_CREAT|O_TRUNC, 0600 );
1296 0 : if( FD_UNLIKELY( -1==ctx->checkpt_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
1297 :
1298 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.base_path, identity_key_b58 ) );
1299 0 : ctx->restore_fd = open( path, O_RDONLY );
1300 0 : if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
1301 0 : }
1302 :
1303 : static void
1304 : unprivileged_init( fd_topo_t * topo,
1305 0 : fd_topo_tile_t * tile ) {
1306 0 : ulong slot_max = tile->tower.max_live_slots;
1307 0 : ulong blk_max = slot_max * EQVOC_MAX;
1308 0 : ulong fec_max = slot_max * FD_SHRED_BLK_MAX / FD_FEC_SHRED_CNT;
1309 0 : ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
1310 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1311 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1312 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1313 0 : void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1314 0 : void * eqvoc = FD_SCRATCH_ALLOC_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, FD_VOTER_MAX ) );
1315 0 : void * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, FD_VOTER_MAX ) );
1316 0 : void * hfork = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(), fd_hfork_footprint( slot_max, FD_VOTER_MAX ) );
1317 0 : void * notar = FD_SCRATCH_ALLOC_APPEND( l, fd_notar_align(), fd_notar_footprint( slot_max ) );
1318 0 : void * tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
1319 0 : void * scratch_tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint() );
1320 0 : void * tower_blocks = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_blocks_align(), fd_tower_blocks_footprint( slot_max ) );
1321 0 : void * tower_leaves = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_leaves_align(), fd_tower_leaves_footprint( slot_max ) );
1322 0 : void * tower_lockos = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_lockos_align(), fd_tower_lockos_footprint( slot_max, FD_VOTER_MAX ) );
1323 0 : void * tower_stakes = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_align(), fd_tower_stakes_footprint( slot_max ) );
1324 0 : void * tower_voters = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_voters_align(), fd_tower_voters_footprint( FD_VOTER_MAX ) );
1325 0 : void * publishes = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(), publishes_footprint( pub_max ) );
1326 0 : FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
1327 :
1328 0 : ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
1329 0 : ctx->identity_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
1330 0 : ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
1331 0 : (void)auth_vtr; /* privileged_init */
1332 0 : ctx->eqvoc = fd_eqvoc_join ( fd_eqvoc_new ( eqvoc, slot_max, fec_max, FD_VOTER_MAX, ctx->seed ) );
1333 0 : ctx->ghost = fd_ghost_join ( fd_ghost_new ( ghost, blk_max, FD_VOTER_MAX, ctx->seed ) );
1334 0 : ctx->hfork = fd_hfork_join ( fd_hfork_new ( hfork, slot_max, FD_VOTER_MAX, ctx->seed, tile->tower.hard_fork_fatal ) );
1335 0 : ctx->notar = fd_notar_join ( fd_notar_new ( notar, slot_max ) );
1336 0 : ctx->tower = fd_tower_join ( fd_tower_new ( tower ) );
1337 0 : ctx->scratch_tower = fd_tower_join ( fd_tower_new ( scratch_tower ) );
1338 0 : ctx->tower_blocks = fd_tower_blocks_join( fd_tower_blocks_new( tower_blocks, slot_max, ctx->seed ) );
1339 0 : ctx->tower_leaves = fd_tower_leaves_join( fd_tower_leaves_new( tower_leaves, slot_max, ctx->seed ) );
1340 0 : ctx->tower_lockos = fd_tower_lockos_join( fd_tower_lockos_new( tower_lockos, slot_max, FD_VOTER_MAX, ctx->seed ) );
1341 0 : ctx->tower_stakes = fd_tower_stakes_join( fd_tower_stakes_new( tower_stakes, slot_max, ctx->seed ) );
1342 0 : ctx->tower_voters = fd_tower_voters_join( fd_tower_voters_new( tower_voters, FD_VOTER_MAX ) );
1343 0 : ctx->publishes = publishes_join ( publishes_new ( publishes, pub_max ) );
1344 0 : ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) );
1345 :
1346 0 : FD_TEST( ctx->wksp );
1347 0 : FD_TEST( ctx->identity_keyswitch );
1348 0 : FD_TEST( ctx->auth_vtr_keyswitch );
1349 0 : FD_TEST( ctx->auth_vtr );
1350 0 : FD_TEST( ctx->eqvoc );
1351 0 : FD_TEST( ctx->ghost );
1352 0 : FD_TEST( ctx->hfork );
1353 0 : FD_TEST( ctx->notar );
1354 0 : FD_TEST( ctx->tower );
1355 0 : FD_TEST( ctx->scratch_tower );
1356 0 : FD_TEST( ctx->tower_blocks );
1357 0 : FD_TEST( ctx->tower_leaves );
1358 0 : FD_TEST( ctx->tower_lockos );
1359 0 : FD_TEST( ctx->tower_stakes );
1360 0 : FD_TEST( ctx->tower_voters );
1361 0 : FD_TEST( ctx->publishes );
1362 :
1363 0 : memset( ctx->chunks, 0, sizeof(ctx->chunks) );
1364 0 : memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) );
1365 0 : memset( ctx->notar_reindex, 0, sizeof(ctx->notar_reindex) );
1366 0 : memset( ctx->notar_removed, 0, sizeof(ctx->notar_removed) );
1367 0 : memset( ctx->vote_txn, 0, sizeof(ctx->vote_txn) );
1368 :
1369 0 : ctx->halt_signing = 0;
1370 0 : ctx->shred_version = 0;
1371 0 : ctx->init_slot = ULONG_MAX;
1372 0 : ctx->root_slot = ULONG_MAX;
1373 :
1374 0 : ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
1375 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
1376 0 : ulong banks_locks_obj_id = fd_pod_query_ulong( topo->props, "banks_locks", ULONG_MAX );
1377 0 : FD_TEST( banks_locks_obj_id!=ULONG_MAX );
1378 0 : FD_TEST( fd_banks_join( ctx->banks, fd_topo_obj_laddr( topo, banks_obj_id ), fd_topo_obj_laddr( topo, banks_locks_obj_id ) ) );
1379 :
1380 0 : fd_accdb_init_from_topo( ctx->accdb, topo, tile, tile->tower.accdb_max_depth );
1381 :
1382 0 : FD_TEST( tile->in_cnt<sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
1383 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
1384 0 : fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ];
1385 0 : fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
1386 :
1387 0 : if ( FD_LIKELY( !strcmp( link->name, "dedup_resolv" ) ) ) ctx->in_kind[ i ] = IN_KIND_DEDUP;
1388 0 : else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
1389 0 : else if( FD_LIKELY( !strcmp( link->name, "gossip_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
1390 0 : else if( FD_LIKELY( !strcmp( link->name, "ipecho_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
1391 0 : else if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
1392 0 : else if( FD_LIKELY( !strcmp( link->name, "shred_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_SHRED;
1393 0 : else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
1394 :
1395 0 : ctx->in[ i ].mcache_only = !link->mtu;
1396 0 : if( FD_LIKELY( !ctx->in[ i ].mcache_only ) ) {
1397 0 : ctx->in[ i ].mem = link_wksp->wksp;
1398 0 : ctx->in[ i ].mtu = link->mtu;
1399 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
1400 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
1401 0 : }
1402 0 : }
1403 :
1404 0 : ctx->out_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
1405 0 : ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
1406 0 : ctx->out_wmark = fd_dcache_compact_wmark ( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache, topo->links[ tile->out_link_id[ 0 ] ].mtu );
1407 0 : ctx->out_chunk = ctx->out_chunk0;
1408 0 : ctx->out_seq = 0UL;
1409 :
1410 0 : memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
1411 0 : }
1412 :
1413 : static ulong
1414 : populate_allowed_seccomp( fd_topo_t const * topo,
1415 : fd_topo_tile_t const * tile,
1416 : ulong out_cnt,
1417 0 : struct sock_filter * out ) {
1418 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1419 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1420 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1421 :
1422 0 : populate_sock_filter_policy_fd_tower_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->checkpt_fd, (uint)ctx->restore_fd );
1423 0 : return sock_filter_policy_fd_tower_tile_instr_cnt;
1424 0 : }
1425 :
1426 : static ulong
1427 : populate_allowed_fds( fd_topo_t const * topo,
1428 : fd_topo_tile_t const * tile,
1429 : ulong out_fds_cnt,
1430 0 : int * out_fds ) {
1431 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1432 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1433 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1434 :
1435 0 : if( FD_UNLIKELY( out_fds_cnt<4UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
1436 :
1437 0 : ulong out_cnt = 0UL;
1438 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
1439 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
1440 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
1441 0 : if( FD_LIKELY( ctx->checkpt_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->checkpt_fd;
1442 0 : if( FD_LIKELY( ctx->restore_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->restore_fd;
1443 0 : return out_cnt;
1444 0 : }
1445 :
1446 0 : #define STEM_BURST (2UL) /* MAX( slot_confirmed, slot_rooted AND (slot_done OR slot_ignored) ) */
1447 0 : #define STEM_LAZY (128L*3000L) /* see explanation in fd_pack */
1448 :
1449 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_tower_tile_t
1450 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_tower_tile_t)
1451 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
1452 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
1453 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
1454 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
1455 :
1456 : #include "../../disco/stem/fd_stem.c"
1457 :
1458 : fd_topo_run_tile_t fd_tile_tower = {
1459 : .name = "tower",
1460 : .populate_allowed_seccomp = populate_allowed_seccomp,
1461 : .populate_allowed_fds = populate_allowed_fds,
1462 : .scratch_align = scratch_align,
1463 : .scratch_footprint = scratch_footprint,
1464 : .unprivileged_init = unprivileged_init,
1465 : .privileged_init = privileged_init,
1466 : .run = stem_run,
1467 : };
|