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/votes/fd_votes.h"
8 : #include "../../choreo/tower/fd_tower.h"
9 : #include "../../choreo/tower/fd_tower_serdes.h"
10 : #include "../../choreo/tower/fd_tower_stakes.h"
11 : #include "../../disco/fd_txn_p.h"
12 : #include "../../disco/events/generated/fd_event_gen.h"
13 : #include "../../disco/shred/fd_shred_tile.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/node_info/fd_node_info.h"
18 : #include "../../disco/topo/fd_topo.h"
19 : #include "../../disco/fd_txn_m.h"
20 : #include "../../discof/replay/fd_replay_tile.h"
21 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
22 : #include "../../flamenco/runtime/fd_bank.h"
23 : #include "../../flamenco/leaders/fd_multi_epoch_leaders.h"
24 : #include "../../flamenco/runtime/fd_system_ids.h"
25 : #include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h"
26 : #include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h"
27 : #include "../../util/pod/fd_pod.h"
28 : #include "../../util/fd_hash32.h"
29 :
30 : #include <errno.h>
31 : #include <fcntl.h>
32 : #include <unistd.h>
33 :
34 : /* The Tower tile broadly processes three classes of frags, leading to
35 : three distinct kinds of frag processing:
36 :
37 : 1. Processing vote _accounts_ (after replaying a block)
38 :
39 : When Replay finishes executing a block, Tower reads back the vote
40 : account state for every staked validator. This is deterministic:
41 : the vote account state is the result of executing all vote txns in
42 : the block through the vote program, so it is guaranteed to
43 : converge with Agave's view of the same accounts. Tower uses these
44 : accounts to run the fork choice rule (fd_ghost) and TowerBFT
45 : (fd_tower).
46 :
47 : 2. Processing vote _transactions_ (at arbitrary points in time)
48 :
49 : Tower also receives vote txns from Gossip and TPU. These arrive
50 : at arbitrary, nondeterministic times because Gossip and TPU are
51 : both unreliable mediums: there's no guarantee we observe all the
52 : same vote txns as Agave (nor another Firedancer, for that matter).
53 :
54 : Tower is stricter than Agave when validating these vote txns (e.g.
55 : we use is_simple_vote which requires at most two signers, whereas
56 : Agave's Gossip vote parser does not). Being stricter is
57 : acceptable given vote txns from Gossip and TPU are inherently
58 : unreliable, so dropping a small number of votes that Agave allows
59 : but Firedancer does not is not significant to convergence.
60 :
61 : However, these same vote txns are (redundantly) transmitted as
62 : part of a block as well ie. through Replay. The validation of
63 : these Replay-sourced vote txns _is_ one-to-one with Agave (namely
64 : the Vote Program), and critical for convergence. Specifically, we
65 : only process Replay vote txns that have been successfully executed
66 : when counting them towards confirmations.
67 :
68 : The guarantee is "eventual consistency": even though individual
69 : Gossip or TPU vote txns may be lost, we are guaranteed to
70 : "eventually" confirm a block and converge with Agave as long as we
71 : receive the block and replay its contained vote txns, because our
72 : vote programs match 1-1. Gossip / TPU can provide a fast-path for
73 : earlier confirmations as well as a source of security via
74 : redundancy in case we are not receiving the blocks from the rest
75 : of the network.
76 :
77 : The processing of vote txns is important to (as already alluded)
78 : fd_votes and fd_hfork.
79 :
80 : 3. Processing "other" frags. Vote account and vote transaction
81 : processing (1 and 2 above) is the meat and potatoes, but Tower also
82 : processes several auxiliary frag types:
83 :
84 : a. Duplicate shred gossip messages (from the gossip tile): Tower
85 : receives duplicate shred proofs from other validators via
86 : gossip. These proofs arrive in chunks (fd_eqvoc_chunk_insert)
87 : and are reassembled and cryptographically verified before being
88 : accepted.
89 :
90 : b. Epoch stake updates (from the replay tile): Tower receives
91 : epoch stake information to maintain the leader schedule via
92 : fd_stake_ci, which is needed by eqvoc for signature
93 : verification of shred proofs.
94 :
95 : c. Shred version (from the ipecho tile): Tower receives the shred
96 : version from ipecho to configure eqvoc's shred version
97 : filtering for proof verification.
98 :
99 : d. Shreds (from the shred tile): Tower checks incoming shreds for
100 : equivocation via fd_eqvoc. If two conflicting shreds are
101 : detected for the same FEC set, Tower constructs a duplicate
102 : proof and publishes it (FD_TOWER_SIG_SLOT_DUPLICATE).
103 :
104 : e. Slot dead (from the replay tile): Tower records a NULL bank
105 : hash for dead slots in the hard fork detector (fd_hfork).
106 :
107 : Tower signals to other tiles about events that occur as a result of
108 : those three modes, such as what block to vote on, what block to reset
109 : onto as leader, what block got rooted, what blocks are duplicates,
110 : and what blocks are confirmed.
111 :
112 : In general, Tower uses "block_id" as the identifier for a block. The
113 : block_id is the merkle root of the last FEC set for a block. Unlike
114 : slot numbers, this is guaranteed to be unique for a given block and
115 : is therefore a canonical identifier because slot numbers can identify
116 : multiple blocks, if a leader equivocates (produces multiple blocks
117 : for the same slot), whereas it is not feasible for a leader to
118 : produce block_id collisions.
119 :
120 : However, the block_id was only introduced into the Solana protocol
121 : recently, and TowerBFT still uses the "legacy" identifier of slot
122 : numbers for blocks. So the tile (and relevant modules) will use
123 : block_id when possible to interface with the protocol but otherwise
124 : fallback to slot number when block_id is unsupported due to limits of
125 : the protocol. */
126 :
127 : #define LOGGING 0
128 :
129 0 : #define IN_KIND_DEDUP (0)
130 0 : #define IN_KIND_EPOCH (1)
131 0 : #define IN_KIND_REPLAY (2)
132 0 : #define IN_KIND_GOSSIP (3)
133 0 : #define IN_KIND_IPECHO (4)
134 0 : #define IN_KIND_SHRED (5)
135 :
136 0 : #define OUT_IDX 0 /* only a single out link tower_out */
137 0 : #define AUTH_VTR_LG_MAX (5) /* The Solana Vote Interface supports up to 32 authorized voters. */
138 : FD_STATIC_ASSERT( 1<<AUTH_VTR_LG_MAX==32, AUTH_VTR_LG_MAX );
139 :
140 : /* Tower processes at most 2 equivocating blocks for a given slot: the
141 : first block is the first one we observe for a slot, and the second
142 : block is the one that gets duplicate confirmed. Most of the time,
143 : they are the same (ie. the block we first saw is the block that gets
144 : duplicate confirmed), but we size for the worst case which is every
145 : block in slot_max equivocates and we always see 2 blocks for every
146 : slot. */
147 :
148 0 : #define EQVOC_MAX (2)
149 :
150 : /* The Alpenglow VAT caps the voting set of validators to 2000. Only
151 : the top 2000 voters by stake will be counted towards consensus rules.
152 : Firedancer uses the same bound for TowerBFT.
153 :
154 : Note module implementations may round the max capacity of various
155 : structures to pow2 for performance, but the consensus logic will only
156 : retain at most 2000 voters.
157 :
158 : https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0357-alpenglow_validator_admission_ticket.md */
159 :
160 0 : #define VTR_MAX (2000) /* the maximum # of unique voters ie. node pubkeys. */
161 :
162 : /* PER_VTR_MAX controls how many "entries" a validator is allowed to
163 : occupy in various vote-tracking structures. This is set somewhat
164 : arbitrarily based on expected worst-case usage by an honest validator
165 : and is set to guard against a malicious spamming validator attempting
166 : to oom Firedancer structures. */
167 :
168 0 : #define PER_VTR_MAX (512) /* the maximum amount of slot history the sysvar retains */
169 :
170 : struct publish {
171 : ulong sig;
172 : fd_tower_msg_t msg;
173 : };
174 : typedef struct publish publish_t;
175 :
176 : #define DEQUE_NAME publishes
177 0 : #define DEQUE_T publish_t
178 : #include "../../util/tmpl/fd_deque_dynamic.c"
179 :
180 : struct auth_vtr {
181 : fd_pubkey_t addr; /* map key, vote account address */
182 : uint hash; /* reserved for use by fd_map */
183 : ulong paths_idx; /* index in authorized voter paths */
184 : };
185 : typedef struct auth_vtr auth_vtr_t;
186 :
187 : #define MAP_NAME auth_vtr
188 0 : #define MAP_T auth_vtr_t
189 0 : #define MAP_LG_SLOT_CNT AUTH_VTR_LG_MAX
190 0 : #define MAP_KEY addr
191 0 : #define MAP_KEY_T fd_pubkey_t
192 0 : #define MAP_KEY_NULL (fd_pubkey_t){0}
193 0 : #define MAP_KEY_EQUAL(k0,k1) (!(memcmp((k0).key,(k1).key,sizeof(fd_pubkey_t))))
194 0 : #define MAP_KEY_INVAL(k) (MAP_KEY_EQUAL((k),MAP_KEY_NULL))
195 : #define MAP_KEY_EQUAL_IS_SLOW 1
196 0 : #define MAP_KEY_HASH(k) ((uint)fd_ulong_hash( fd_ulong_load_8( (k).uc ) ))
197 : #include "../../util/tmpl/fd_map.c"
198 :
199 : struct epoch_vtr {
200 : fd_pubkey_t vote_acc;
201 : ulong stake;
202 : fd_pubkey_t auth_vtr; /* authorized voter for vote_acc at this map's target epoch; all-zero if unavailable */
203 : ulong next; /* reserved for fd_pool and fd_map_chain */
204 : };
205 : typedef struct epoch_vtr epoch_vtr_t;
206 :
207 : #define POOL_NAME epoch_vtr_pool
208 0 : #define POOL_T epoch_vtr_t
209 : #include "../../util/tmpl/fd_pool.c"
210 :
211 : #define MAP_NAME epoch_vtr_map
212 : #define MAP_ELE_T epoch_vtr_t
213 0 : #define MAP_KEY vote_acc
214 : #define MAP_KEY_T fd_pubkey_t
215 0 : #define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_pubkey_t)))
216 0 : #define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) ))
217 0 : #define MAP_NEXT next
218 : #include "../../util/tmpl/fd_map_chain.c"
219 :
220 : #define AUTH_VOTERS_MAX (16UL)
221 :
222 : struct in_ctx {
223 : int mcache_only;
224 : fd_wksp_t * mem;
225 : ulong chunk0;
226 : ulong wmark;
227 : ulong mtu;
228 : };
229 : typedef struct in_ctx in_ctx_t;
230 :
231 : struct fd_tower_tile {
232 : ulong seed; /* map seed */
233 : int checkpt_fd;
234 : int restore_fd;
235 : fd_pubkey_t identity_key[1];
236 : fd_pubkey_t vote_account[1];
237 : ulong auth_vtr_path_cnt; /* number of authorized voter paths passed to tile */
238 : uchar our_vote_acct[FD_VOTE_STATE_DATA_MAX]; /* buffer for reading back our own vote acct data */
239 : ulong our_vote_acct_sz;
240 :
241 : /* owned joins */
242 :
243 : fd_wksp_t * wksp; /* workspace */
244 : fd_keyswitch_t * identity_keyswitch;
245 : auth_vtr_t * auth_vtr;
246 : fd_keyswitch_t * auth_vtr_keyswitch; /* authorized voter keyswitch */
247 :
248 : fd_eqvoc_t * eqvoc;
249 : fd_ghost_t * ghost;
250 : fd_hfork_t * hfork;
251 : fd_votes_t * votes;
252 : fd_tower_t * tower;
253 :
254 : fd_vote_instruction_t scratch_ix;
255 : fd_tower_vote_t * scratch_tower; /* spare deque used during vote txn processing */
256 :
257 : publish_t * publishes; /* deque of slot_confirmed msgs queued for publishing */
258 : fd_multi_epoch_leaders_t * mleaders; /* multi-epoch leaders */
259 :
260 : /* borrowed joins */
261 :
262 : fd_banks_t * banks;
263 : fd_accdb_t * accdb;
264 :
265 : /* static structures */
266 :
267 : fd_pubkey_t id_keys [VTR_MAX]; /* identity keys */
268 : fd_pubkey_t vote_accs[VTR_MAX]; /* vote account addresses */
269 : ulong vtr_cnt; /* actual cnt of elements in above arrays */
270 : fd_gossip_duplicate_shred_t duplicate_chunks[FD_EQVOC_CHUNK_CNT];
271 : fd_compact_tower_sync_serde_t compact_tower_sync_serde;
272 : uchar vote_txn[FD_TPU_PARSED_MTU];
273 :
274 : uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ];
275 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
276 :
277 : ulong root_epoch;
278 : ulong root_epoch_total_stake;
279 : ulong next_epoch_total_stake;
280 : epoch_vtr_t * root_epoch_vtr_pool;
281 : epoch_vtr_map_t * root_epoch_vtr_map;
282 : epoch_vtr_t * next_epoch_vtr_pool;
283 : epoch_vtr_map_t * next_epoch_vtr_map;
284 :
285 : /* metadata */
286 :
287 : int halt_signing;
288 : int hard_fork_fatal;
289 : int wfs; /* 1 if booted with wait_for_supermajority */
290 : ushort shred_version;
291 : int init; /* 1 after ghost_init has been called */
292 :
293 : /* in/out link setup */
294 :
295 : int in_kind[ 64UL ];
296 : in_ctx_t in [ 64UL ];
297 :
298 : fd_wksp_t * out_mem;
299 : ulong out_chunk0;
300 : ulong out_wmark;
301 : ulong out_chunk;
302 : ulong out_seq;
303 :
304 : /* metrics */
305 :
306 : struct {
307 : ulong not_ready;
308 :
309 : ulong ignored_cnt;
310 : ulong ignored_slot;
311 : ulong eqvoc_cnt;
312 : ulong eqvoc_slot;
313 :
314 : ulong replay_slot;
315 : ulong last_vote_slot;
316 : ulong reset_slot;
317 : ulong root_slot;
318 : ulong init_slot;
319 :
320 : ulong fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_CNT ];
321 : ulong gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_CNT ];
322 :
323 : ulong votes [ FD_METRICS_ENUM_VOTE_TXN_RESULT_CNT ];
324 : ulong vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_CNT ];
325 : ulong gate_int [ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_CNT ];
326 :
327 : ulong eqvoc_success;
328 : ulong eqvoc_err;
329 :
330 : ulong ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_CNT ];
331 :
332 : ulong hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_CNT ];
333 :
334 : ulong hfork_matched_slot;
335 : ulong hfork_mismatched_slot;
336 : } metrics;
337 : };
338 : typedef struct fd_tower_tile fd_tower_tile_t;
339 :
340 : /* Compile-time dependency injection. This macro defaults to the
341 : production implementation defined below. Tests can #define it before
342 : #include-ing this file to substitute a mock. */
343 :
344 : #ifndef QUERY_TOWERS
345 0 : #define QUERY_TOWERS query_towers
346 : #endif
347 :
348 : #ifndef QUERY_VOTERS
349 0 : #define QUERY_VOTERS query_voters
350 : #endif
351 :
352 : ulong QUERY_TOWERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, fd_ghost_blk_t *, int *, ulong *, ushort * );
353 : void QUERY_VOTERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, ulong );
354 :
355 : /* vote_account_config extracts configuration of this validator's vote
356 : account (on-chain state). data points to the first byte of the
357 : vote account's data. Sets:
358 : - *authority_out to the selected authorized voter's public key
359 : - *authority_idx_out to the tile's auth_vtr index (matches sign tile)
360 : or ULONG_MAX it the authorized voter is the node identity
361 : or LONG_MAX if it matches neither
362 : - *node_pubkey to the vote account's pubkey
363 : Returns 1 if the validator has a key for the found vote authority,
364 : and 0 otherwise. */
365 :
366 : static int
367 : vote_account_config( fd_tower_tile_t * ctx,
368 : uchar const * data,
369 : ulong data_sz,
370 : ulong epoch,
371 : fd_pubkey_t * authority_out,
372 : ulong * authority_idx_out,
373 0 : fd_pubkey_t * node_pubkey_out ) {
374 :
375 0 : fd_vote_state_versioned_t vsv[1];
376 0 : FD_CHECK_CRIT( fd_vote_state_versioned_deserialize( vsv, data, data_sz ), "unable to decode vote state versioned" );
377 :
378 0 : fd_pubkey_t const * auth_vtr_addr = NULL;
379 0 : switch( vsv->kind ) {
380 0 : case fd_vote_state_versioned_enum_v1_14_11:
381 0 : *node_pubkey_out = vsv->v1_14_11.node_pubkey;
382 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v1_14_11.authorized_voters.treap, vsv->v1_14_11.authorized_voters.pool );
383 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
384 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v1_14_11.authorized_voters.pool ) ) {
385 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v1_14_11.authorized_voters.pool );
386 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
387 0 : auth_vtr_addr = &ele->pubkey;
388 0 : break;
389 0 : }
390 0 : }
391 0 : break;
392 0 : case fd_vote_state_versioned_enum_v3:
393 0 : *node_pubkey_out = vsv->v3.node_pubkey;
394 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v3.authorized_voters.treap, vsv->v3.authorized_voters.pool );
395 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
396 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v3.authorized_voters.pool ) ) {
397 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v3.authorized_voters.pool );
398 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
399 0 : auth_vtr_addr = &ele->pubkey;
400 0 : break;
401 0 : }
402 0 : }
403 0 : break;
404 0 : case fd_vote_state_versioned_enum_v4:
405 0 : *node_pubkey_out = vsv->v4.node_pubkey;
406 0 : for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v4.authorized_voters.treap, vsv->v4.authorized_voters.pool );
407 0 : !fd_vote_authorized_voters_treap_rev_iter_done( iter );
408 0 : iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v4.authorized_voters.pool ) ) {
409 0 : fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v4.authorized_voters.pool );
410 0 : if( FD_LIKELY( ele->epoch<=epoch ) ) {
411 0 : auth_vtr_addr = &ele->pubkey;
412 0 : break;
413 0 : }
414 0 : }
415 0 : break;
416 0 : default:
417 0 : FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", vsv->kind ));
418 0 : }
419 :
420 0 : FD_CHECK_CRIT( auth_vtr_addr, "unable to find authorized voter, likely corrupt vote account state" );
421 0 : *authority_out = *auth_vtr_addr;
422 :
423 0 : if( fd_pubkey_eq( auth_vtr_addr, ctx->identity_key ) ) {
424 0 : *authority_idx_out = ULONG_MAX;
425 0 : return 1;
426 0 : }
427 :
428 0 : auth_vtr_t * auth_vtr = auth_vtr_query( ctx->auth_vtr, *auth_vtr_addr, NULL );
429 0 : if( FD_LIKELY( auth_vtr ) ) {
430 0 : *authority_idx_out = auth_vtr->paths_idx;
431 0 : return 1;
432 0 : }
433 :
434 0 : *authority_idx_out = LONG_MAX;
435 0 : return 0;
436 0 : }
437 :
438 : static void
439 : update_metrics_eqvoc( fd_tower_tile_t * ctx,
440 0 : int err ) {
441 0 : ctx->metrics.eqvoc_success += (ulong)(err==FD_EQVOC_SUCCESS);
442 0 : ctx->metrics.eqvoc_err += (ulong)(err<0);
443 0 : }
444 :
445 : static void
446 : update_metrics_ghost( fd_tower_tile_t * ctx,
447 0 : int err ) {
448 0 : ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_SUCCESS_IDX ] += (ulong)(err==FD_GHOST_SUCCESS);
449 0 : ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_NOT_VOTED_IDX ] += (ulong)(err==FD_GHOST_ERR_NOT_VOTED);
450 0 : ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_TOO_OLD_IDX ] += (ulong)(err==FD_GHOST_ERR_VOTE_TOO_OLD);
451 0 : ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_GHOST_ERR_ALREADY_VOTED);
452 0 : }
453 :
454 : static void
455 : update_metrics_hfork( fd_tower_tile_t * ctx,
456 : int hfork_err,
457 : ulong slot,
458 0 : fd_hash_t const * block_id ) {
459 0 : switch( hfork_err ) {
460 0 : case FD_HFORK_SUCCESS_MATCHED:
461 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_SUCCESS_MATCHED_IDX ]++;
462 0 : ctx->metrics.hfork_matched_slot = fd_ulong_max( ctx->metrics.hfork_matched_slot, slot );
463 0 : break;
464 0 : case FD_HFORK_SUCCESS:
465 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_SUCCESS_IDX ]++;
466 0 : break;
467 0 : case FD_HFORK_ERR_MISMATCHED:
468 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MISMATCHED_IDX ]++;
469 0 : if( FD_UNLIKELY( ctx->hard_fork_fatal ) ) {
470 0 : FD_BASE58_ENCODE_32_BYTES( block_id->uc, _block_id );
471 0 : FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", slot, _block_id ));
472 0 : }
473 0 : ctx->metrics.hfork_mismatched_slot = fd_ulong_max( ctx->metrics.hfork_mismatched_slot, slot );
474 0 : break;
475 0 : case FD_HFORK_ERR_UNKNOWN_VTR:
476 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_UNKNOWN_VOTER_IDX ]++;
477 0 : break;
478 0 : case FD_HFORK_ERR_ALREADY_VOTED:
479 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_ALREADY_VOTED_IDX ]++;
480 0 : break;
481 0 : case FD_HFORK_ERR_VOTE_TOO_OLD:
482 0 : ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_TOO_OLD_IDX ]++;
483 0 : break;
484 0 : default:
485 0 : FD_LOG_ERR(( "unhandled hfork_err %d", hfork_err ));
486 0 : }
487 0 : }
488 :
489 : static void
490 : update_metrics_vote_slot( fd_tower_tile_t * ctx,
491 0 : int err ) {
492 0 : ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_SUCCESS_IDX ] += (ulong)(err==FD_VOTES_SUCCESS);
493 0 : ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_NEW_IDX ] += (ulong)(err==FD_VOTES_ERR_VOTE_TOO_NEW);
494 0 : ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_VOTER_IDX ] += (ulong)(err==FD_VOTES_ERR_UNKNOWN_VTR);
495 0 : ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_VOTES_ERR_ALREADY_VOTED);
496 0 : }
497 :
498 : static int
499 0 : event_level_from_tower( int tower_level ) {
500 0 : switch( tower_level ) {
501 0 : case FD_TOWER_SLOT_CONFIRMED_PROPAGATED: return FD_EVENT_SLOT_CONFIRMED_LEVEL_PROPAGATED;
502 0 : case FD_TOWER_SLOT_CONFIRMED_DUPLICATE: return FD_EVENT_SLOT_CONFIRMED_LEVEL_DUPLICATE;
503 0 : case FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC: return FD_EVENT_SLOT_CONFIRMED_LEVEL_OPTIMISTIC;
504 0 : case FD_TOWER_SLOT_CONFIRMED_SUPER: return FD_EVENT_SLOT_CONFIRMED_LEVEL_SUPER;
505 0 : default: FD_LOG_ERR(( "unexpected tower confirmation level %d", tower_level ));
506 0 : }
507 0 : }
508 :
509 : static void
510 : report_slot_confirmed( ulong bank_seq,
511 : ulong slot,
512 : fd_hash_t const * block_id,
513 : ulong stake,
514 : ulong total_stake,
515 : int valid,
516 : int level,
517 0 : int forward ) {
518 0 : fd_event_slot_confirmed_t ev = {
519 0 : .bank_seq = bank_seq,
520 0 : .slot = slot,
521 0 : .stake = stake,
522 0 : .total_stake = total_stake,
523 0 : .valid = valid,
524 0 : .level = level,
525 0 : .forward = forward,
526 0 : };
527 0 : fd_memcpy( ev.block_id, block_id->uc, sizeof(fd_hash_t) );
528 0 : fd_event_report_slot_confirmed( &ev );
529 0 : }
530 :
531 : struct block_equivocated_args {
532 : ulong slot;
533 : ulong parent_slot;
534 : ulong epoch;
535 : fd_hash_t const * block_id; /* our replayed block (or the just-replayed block) */
536 : fd_hash_t const * sibling_block_id; /* conflicting block; NULL if unknown (shred proof) */
537 : fd_hash_t const * bank_hash; /* our block's bank hash; NULL if not replayed locally */
538 : fd_hash_t const * block_hash; /* our block's last microblock hash; NULL if not replayed locally */
539 : ulong bank_seq; /* our replayed bank seq; 0 if no local bank */
540 : int is_leader;
541 : int our_block_voted;
542 : int our_block_confirmed;
543 : ulong block_stake; /* stake voted on our replayed block; 0 if none/unknown */
544 : ulong sibling_stake; /* stake on the conflicting block; 0 if unknown */
545 : ulong total_stake; /* 0 if unknown */
546 : int detection;
547 : };
548 : typedef struct block_equivocated_args block_equivocated_args_t;
549 :
550 : static ulong
551 0 : votes_stake( fd_tower_tile_t * ctx, ulong slot, fd_hash_t const * block_id ) {
552 0 : fd_votes_blk_t * vb = fd_votes_query( ctx->votes, slot, block_id );
553 0 : return vb ? vb->stake : 0UL;
554 0 : }
555 :
556 : static int
557 0 : our_block_confirmed( fd_tower_blk_t const * blk ) {
558 0 : return blk && blk->confirmed && 0==memcmp( &blk->replayed_block_id, &blk->confirmed_block_id, sizeof(fd_hash_t) );
559 0 : }
560 :
561 : static void
562 0 : report_block_equivocated( block_equivocated_args_t const * a ) {
563 0 : fd_event_block_equivocated_t ev = {
564 0 : .slot = a->slot,
565 0 : .parent_slot = a->parent_slot,
566 0 : .epoch = a->epoch,
567 0 : .bank_seq = a->bank_seq,
568 0 : .is_leader = a->is_leader,
569 0 : .our_block_voted = a->our_block_voted,
570 0 : .our_block_confirmed = a->our_block_confirmed,
571 0 : .block_stake = a->block_stake,
572 0 : .sibling_stake = a->sibling_stake,
573 0 : .total_stake = a->total_stake,
574 0 : .detection = a->detection,
575 0 : };
576 0 : fd_memcpy( ev.block_id, a->block_id->uc, sizeof(fd_hash_t) );
577 0 : if( FD_LIKELY( a->sibling_block_id ) ) fd_memcpy( ev.sibling_block_id, a->sibling_block_id->uc, sizeof(fd_hash_t) );
578 0 : if( FD_LIKELY( a->bank_hash ) ) fd_memcpy( ev.bank_hash, a->bank_hash->uc, sizeof(fd_hash_t) );
579 0 : if( FD_LIKELY( a->block_hash ) ) fd_memcpy( ev.block_hash, a->block_hash->uc, sizeof(fd_hash_t) );
580 0 : fd_event_report_block_equivocated( &ev );
581 0 : }
582 :
583 : static void
584 : publish_slot_confirmed( fd_tower_tile_t * ctx,
585 : ulong slot,
586 : fd_hash_t const * block_id,
587 0 : ulong total_stake ) {
588 :
589 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot );
590 0 : fd_ghost_blk_t * ghost_blk = fd_ghost_query( ctx->ghost, block_id );
591 0 : fd_votes_blk_t * votes_blk = fd_votes_query( ctx->votes, slot, block_id );
592 0 : if( FD_UNLIKELY( !votes_blk ) ) return;
593 :
594 0 : static double const ratios[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_RATIOS;
595 0 : int const levels[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_LEVELS;
596 0 : for( int i = 0; i < FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; i++ ) {
597 0 : if( FD_LIKELY( fd_uchar_extract_bit( votes_blk->flags, i ) ) ) continue; /* already contiguously confirmed */
598 0 : double ratio = (double)votes_blk->stake / (double)total_stake;
599 0 : if( FD_LIKELY( ratio <= ratios[i] ) ) break; /* threshold not met */
600 :
601 : /* If the ghost_blk is missing, then we know this is a forward
602 : confirmation (ie. we haven't replayed the block yet). */
603 :
604 0 : if( FD_UNLIKELY( !ghost_blk ) ) {
605 0 : if( fd_uchar_extract_bit( votes_blk->flags, i+4 ) ) continue; /* already forward confirmed */
606 0 : votes_blk->flags = fd_uchar_set_bit( votes_blk->flags, i+4 );
607 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 = votes_blk->key.slot, .block_id = votes_blk->key.block_id } } } );
608 0 : report_slot_confirmed( 0UL, votes_blk->key.slot, &votes_blk->key.block_id, votes_blk->stake, total_stake, 1 /* valid */, event_level_from_tower( levels[ i ] ), 1 /* forward */ );
609 :
610 : /* If we have a tower_blk for the slot when the ghost_blk is
611 : missing, we usually replayed an equivocating block_id that is
612 : not the confirmed_block_id. The exception: ghost also prunes
613 : blocks we replayed whose fork lost, so an identical block_id
614 : means the cluster duplicate-confirmed a block our root already
615 : passed. Consensus divergence; halt deliberately. Only
616 : relevant for the duplicate confirmed level. */
617 :
618 0 : if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE && tower_blk ) ) {
619 0 : if( FD_UNLIKELY( 0==memcmp( &tower_blk->replayed_block_id, &votes_blk->key.block_id, sizeof(fd_hash_t) ) ) ) {
620 0 : FD_BASE58_ENCODE_32_BYTES( votes_blk->key.block_id.uc, dup_blk_id );
621 0 : FD_LOG_CRIT(( "cluster duplicate-confirmed block %s (slot %lu), which we replayed but pruned: our root conflicts with the cluster", dup_blk_id, votes_blk->key.slot ));
622 0 : }
623 0 : tower_blk->confirmed = 1;
624 0 : tower_blk->confirmed_block_id = votes_blk->key.block_id;
625 0 : FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id );
626 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (replayed before confirmed). slot: %lu. block_id: %s", __func__, votes_blk->key.slot, eqvoc_blk_id ));
627 0 : fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id );
628 0 : report_block_equivocated( &(block_equivocated_args_t){
629 0 : .slot = votes_blk->key.slot, .parent_slot = tower_blk->parent_slot, .epoch = tower_blk->epoch,
630 0 : .block_id = &tower_blk->replayed_block_id, .sibling_block_id = &votes_blk->key.block_id,
631 0 : .bank_hash = &tower_blk->bank_hash, .block_hash = &tower_blk->block_hash,
632 0 : .is_leader = tower_blk->leader, .our_block_voted = tower_blk->voted, .our_block_confirmed = our_block_confirmed( tower_blk ),
633 0 : .block_stake = votes_stake( ctx, votes_blk->key.slot, &tower_blk->replayed_block_id ),
634 0 : .sibling_stake = votes_blk->stake, .total_stake = total_stake,
635 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } );
636 0 : }
637 0 : continue;
638 0 : }
639 :
640 : /* Otherwise if they are present, then we know this is not a forward
641 : confirmation and thus we have replayed and confirmed the block,
642 : which also implies we have replayed and confirmed all its
643 : ancestors. So we publish confirmations for all its ancestors
644 : (short-circuiting at the first ancestor already confirmed).
645 :
646 : We use ghost to walk up the ancestry and also mark ghost and
647 : tower blocks as confirmed as we walk if this is the duplicate
648 : confirmation level. */
649 :
650 0 : fd_ghost_blk_t * ghost_anc = ghost_blk;
651 0 : fd_tower_blk_t * tower_anc = tower_blk;
652 0 : fd_votes_blk_t * votes_anc = votes_blk;
653 0 : while( FD_LIKELY( ghost_anc ) ) {
654 :
655 0 : tower_anc = fd_tower_blocks_query( ctx->tower, ghost_anc->slot );
656 0 : votes_anc = fd_votes_query( ctx->votes, ghost_anc->slot, &ghost_anc->id );
657 0 : if( FD_UNLIKELY( !tower_anc || !votes_anc ) ) break;
658 :
659 : /* Terminate at the first ancestor that has already reached this
660 : confirmation level. */
661 :
662 0 : if( FD_LIKELY( fd_uchar_extract_bit( votes_anc->flags, i ) ) ) break;
663 :
664 : /* Mark the ancestor as confirmed at this level, before reporting,
665 : so a duplicate-level row reflects the eligibility it restores.
666 : If this is the duplicate confirmation level, also mark the ghost
667 : and tower blocks as confirmed. */
668 :
669 0 : votes_anc->flags = fd_uchar_set_bit( votes_anc->flags, i );
670 0 : if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_PROPAGATED ) ) {
671 0 : tower_anc->propagated = 1;
672 0 : }
673 0 : if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) {
674 0 : tower_anc->confirmed = 1;
675 0 : tower_anc->confirmed_block_id = ghost_anc->id;
676 0 : fd_ghost_confirm( ctx->ghost, &ghost_anc->id );
677 0 : if( FD_UNLIKELY( memcmp( &tower_anc->replayed_block_id, &ghost_anc->id, sizeof(fd_hash_t) ) ) ) {
678 0 : FD_BASE58_ENCODE_32_BYTES( tower_anc->replayed_block_id.uc, eqvoc_blk_id );
679 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via ancestor duplicate confirmation. slot: %lu. block_id: %s", __func__, ghost_anc->slot, eqvoc_blk_id ));
680 0 : fd_ghost_eqvoc( ctx->ghost, &tower_anc->replayed_block_id );
681 0 : report_block_equivocated( &(block_equivocated_args_t){
682 0 : .slot = ghost_anc->slot, .parent_slot = tower_anc->parent_slot, .epoch = tower_anc->epoch,
683 0 : .block_id = &tower_anc->replayed_block_id, .sibling_block_id = &ghost_anc->id,
684 0 : .bank_hash = &tower_anc->bank_hash, .block_hash = &tower_anc->block_hash,
685 0 : .bank_seq = 0UL,
686 0 : .is_leader = tower_anc->leader, .our_block_voted = tower_anc->voted, .our_block_confirmed = our_block_confirmed( tower_anc ),
687 0 : .block_stake = votes_stake( ctx, ghost_anc->slot, &tower_anc->replayed_block_id ),
688 0 : .sibling_stake = votes_anc->stake, .total_stake = total_stake,
689 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } );
690 0 : }
691 0 : }
692 :
693 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 } } } );
694 0 : if( FD_LIKELY( !fd_uchar_extract_bit( votes_anc->flags, i+4 ) ) ) {
695 : /* Skip telemetry report if already forward reported. */
696 0 : report_slot_confirmed( ghost_anc->bank_seq, ghost_anc->slot, &ghost_anc->id, votes_anc->stake, total_stake, ghost_anc->valid, event_level_from_tower( levels[ i ] ), 0 /* not forward */ );
697 0 : }
698 : /* Walk up to next ancestor. */
699 :
700 0 : ghost_anc = fd_ghost_parent( ctx->ghost, ghost_anc );
701 0 : }
702 0 : }
703 0 : }
704 :
705 : static void
706 : publish_slot_done( fd_tower_tile_t * ctx,
707 : fd_replay_slot_completed_t * slot_completed,
708 : fd_tower_out_t * out,
709 : int found,
710 : ulong our_vote_acct_bal,
711 : ushort our_vote_acct_com,
712 : ulong tsorig FD_PARAM_UNUSED,
713 0 : fd_stem_context_t * stem FD_PARAM_UNUSED ) {
714 :
715 0 : publish_t * pub = publishes_push_head_nocopy( ctx->publishes );
716 0 : pub->sig = FD_TOWER_SIG_SLOT_DONE;
717 :
718 0 : fd_tower_slot_done_t * msg = &pub->msg.slot_done;
719 0 : msg->replay_slot = slot_completed->slot;
720 0 : msg->active_fork_cnt = fd_ghost_width( ctx->ghost );
721 0 : msg->vote_slot = out->vote_slot;
722 0 : msg->reset_slot = out->reset_slot;
723 0 : msg->reset_block_id = out->reset_block_id;
724 0 : msg->reset_bank_seq = out->reset_bank_seq;
725 0 : msg->root_slot = out->root_slot;
726 0 : msg->root_block_id = out->root_block_id;
727 0 : msg->replay_bank_idx = slot_completed->bank_idx;
728 0 : msg->replay_bank_seq = slot_completed->bank_seq;
729 0 : msg->vote_acct_bal = our_vote_acct_bal;
730 0 : msg->vote_acct_com = our_vote_acct_com;
731 :
732 0 : ulong authority_idx = ULONG_MAX;
733 0 : fd_pubkey_t authority[1];
734 0 : fd_pubkey_t identity[1];
735 : /* Refuse to vote if we don't have a matching vote authority key */
736 0 : int found_authority = found && vote_account_config( ctx, ctx->our_vote_acct, ctx->our_vote_acct_sz, slot_completed->epoch, authority, &authority_idx, identity );
737 : /* Refuse to vote if our node identity does not match the one
738 : specified in the vote account (hot spare check) */
739 0 : int identity_matches = found_authority && fd_pubkey_eq( identity, ctx->identity_key );
740 0 : msg->is_voting = found_authority && identity_matches;
741 :
742 0 : if( FD_LIKELY( out->vote_slot!=ULONG_MAX &&
743 0 : found_authority &&
744 0 : identity_matches &&
745 0 : !fd_tower_vote_empty( ctx->tower->votes ) ) ) {
746 : /* The reason to use a historical blockhash and not the most recent
747 : one is because if a vote txn lands on another validator, they
748 : may not have finished processing the slot and therefore the
749 : newest blockhash may not be available to the leader yet; this is
750 : especially true for the first leader block in a rotation. */
751 0 : msg->has_vote_txn = 1;
752 0 : fd_txn_p_t txn[1];
753 0 : fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot );
754 0 : FD_TEST( parent_tower_blk );
755 0 : fd_hash_t const * recent_blockhash = &parent_tower_blk->block_hash;
756 0 : fd_tower_to_vote_txn( ctx->tower, &out->vote_bank_hash, &out->vote_block_id, recent_blockhash, ctx->identity_key, authority, ctx->vote_account, txn );
757 0 : FD_TEST( !fd_tower_vote_empty( ctx->tower->votes ) );
758 0 : FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU );
759 0 : fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz );
760 0 : msg->vote_txn_sz = txn->payload_sz;
761 0 : msg->authority_idx = authority_idx;
762 0 : msg->vote_created_nanos = fd_log_wallclock();
763 0 : } else {
764 0 : msg->has_vote_txn = 0;
765 0 : }
766 0 : }
767 :
768 : static void
769 : publish_slot_ignored( fd_tower_tile_t * ctx,
770 : fd_replay_slot_completed_t * slot_completed,
771 : ulong tsorig FD_PARAM_UNUSED,
772 0 : fd_stem_context_t * stem FD_PARAM_UNUSED ) {
773 0 : publishes_push_head( ctx->publishes, (publish_t){
774 0 : .sig = FD_TOWER_SIG_SLOT_IGNORED,
775 0 : .msg = { .slot_ignored = { .slot = slot_completed->slot, .bank_idx = slot_completed->bank_idx } }
776 0 : });
777 0 : }
778 :
779 : static void
780 : publish_slot_rooted( fd_tower_tile_t * ctx,
781 : ulong slot,
782 0 : fd_hash_t const * block_id ) {
783 0 : publishes_push_head( ctx->publishes, (publish_t){
784 0 : .sig = FD_TOWER_SIG_SLOT_ROOTED,
785 0 : .msg = { .slot_rooted = { .slot = slot, .block_id = *block_id } }
786 0 : });
787 0 : }
788 :
789 : static void
790 : publish_slot_duplicate( fd_tower_tile_t * ctx,
791 : fd_gossip_duplicate_shred_t const chunks[static FD_EQVOC_CHUNK_CNT],
792 0 : ulong slot ) {
793 0 : publish_t * pub = publishes_push_head_nocopy( ctx->publishes );
794 0 : pub->sig = FD_TOWER_SIG_SLOT_DUPLICATE;
795 0 : memcpy( pub->msg.slot_duplicate.chunks, chunks, sizeof(pub->msg.slot_duplicate.chunks) );
796 :
797 : /* If we already have a tower blk for this just-proved duplicate
798 : slot, then we know we have replayed one of the equivocating
799 : blocks. So determine:
800 :
801 : 1. whether we already know what is the confirmed block_id
802 : 2. if our replayed_block_id is the confirmed_block_id
803 :
804 : If either 1. or 2. are false (with 2. contingent on 1.), then
805 : mark the replayed block as eqvoc in ghost. */
806 :
807 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot );
808 0 : int eqvoc = tower_blk && (!tower_blk->confirmed || memcmp( &tower_blk->replayed_block_id, &tower_blk->confirmed_block_id, sizeof(fd_hash_t) ) );
809 0 : if( FD_LIKELY( eqvoc ) ) {
810 0 : FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id );
811 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate shred proof. slot: %lu. block_id: %s", __func__, slot, eqvoc_blk_id ));
812 0 : fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id );
813 0 : report_block_equivocated( &(block_equivocated_args_t){
814 0 : .slot = slot, .parent_slot = tower_blk->parent_slot, .epoch = tower_blk->epoch,
815 0 : .block_id = &tower_blk->replayed_block_id, .sibling_block_id = NULL /* unknown */,
816 0 : .bank_hash = &tower_blk->bank_hash, .block_hash = &tower_blk->block_hash,
817 0 : .is_leader = tower_blk->leader, .our_block_voted = tower_blk->voted, .our_block_confirmed = our_block_confirmed( tower_blk ),
818 0 : .block_stake = votes_stake( ctx, slot, &tower_blk->replayed_block_id ),
819 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_SHRED_PROOF } );
820 0 : }
821 0 : }
822 :
823 : static void
824 : count_vote_acc( fd_tower_tile_t * ctx,
825 : fd_replay_slot_completed_t * slot_completed,
826 : fd_ghost_blk_t * ghost_blk,
827 : fd_pubkey_t const * vote_acc,
828 : ulong stake,
829 : uchar const * data,
830 0 : ulong data_sz ) {
831 :
832 0 : fd_tower_count_vote( ctx->tower, vote_acc, stake, data, data_sz );
833 :
834 0 : fd_tower_vtr_t const * vtr = fd_tower_vtr_peek_tail_const( ctx->tower->vtrs );
835 :
836 : /* 1. Update forks with lockouts. */
837 :
838 0 : fd_tower_lockos_insert( ctx->tower, slot_completed->slot, vote_acc, vtr->votes );
839 :
840 : /* 2. Count the last vote slot in the vote state towards ghost. */
841 :
842 0 : ulong vote_slot = fd_tower_vote_empty( vtr->votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( vtr->votes )->slot;
843 0 : if( FD_LIKELY( vote_slot!=ULONG_MAX && /* has voted */
844 0 : vote_slot>=fd_ghost_root( ctx->ghost )->slot ) ) { /* vote not too old */
845 :
846 0 : fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */
847 :
848 0 : if( FD_UNLIKELY( !ancestor_blk ) ) {
849 0 : FD_BASE58_ENCODE_32_BYTES( vote_acc->key, vote_acc_cstr );
850 0 : FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_completed->slot, vote_slot, vote_acc_cstr ));
851 0 : }
852 :
853 0 : int ghost_err = fd_ghost_count_vote( ctx->ghost, ancestor_blk, vote_acc, stake, vote_slot );
854 0 : update_metrics_ghost( ctx, ghost_err );
855 0 : }
856 :
857 0 : FD_TEST( !fd_vote_account_node_pubkey( data, data_sz, &ctx->id_keys[ctx->vtr_cnt] ) );
858 0 : ctx->vote_accs[ctx->vtr_cnt] = *vote_acc;
859 0 : ctx->vtr_cnt++;
860 0 : }
861 :
862 : /* Query all the relevant towers for running Tower rules on this slot:
863 :
864 : 1. staked voter set from banks
865 : 2. vote accounts (for each staked voter, which contains their tower)
866 : from accountsDB. */
867 :
868 : FD_FN_UNUSED ulong
869 : query_towers( fd_tower_tile_t * ctx,
870 : fd_replay_slot_completed_t * slot_completed,
871 : fd_ghost_blk_t * ghost_blk,
872 : int * found_our_vote_acct,
873 : ulong * our_vote_acct_bal,
874 0 : ushort * our_vote_acct_com ) {
875 :
876 0 : ulong total_stake = 0UL;
877 0 : ulong prev_voter_idx = ULONG_MAX;
878 :
879 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx );
880 0 : if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx ));
881 :
882 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
883 0 : ulong fork_id = bank->vote_stakes_fork_id;
884 0 : uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ];
885 :
886 0 : #define BATCH 64UL
887 0 : fd_pubkey_t vote_accs[ BATCH ];
888 0 : ulong stakes[ BATCH ];
889 0 : uchar const * pubkeys[ BATCH ];
890 0 : int writable[ BATCH ];
891 0 : fd_acc_t accs[ BATCH ];
892 :
893 0 : fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter_mem );
894 0 : while( !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) ) {
895 0 : ulong batch_n = 0UL;
896 0 : while( !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) && batch_n<BATCH ) {
897 0 : uchar is_valid;
898 0 : fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter,
899 0 : &vote_accs[ batch_n ], NULL, &stakes[ batch_n ],
900 0 : NULL, NULL, NULL, &is_valid, NULL, NULL, NULL );
901 0 : fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter );
902 0 : total_stake += stakes[ batch_n ];
903 0 : if( FD_UNLIKELY( !is_valid ) ) continue;
904 0 : pubkeys[ batch_n ] = vote_accs[ batch_n ].uc;
905 0 : writable[ batch_n ] = 0;
906 0 : batch_n++;
907 0 : }
908 0 : if( FD_UNLIKELY( !batch_n ) ) continue;
909 :
910 0 : fd_accdb_acquire( ctx->accdb, bank->accdb_fork_id, batch_n, pubkeys, writable, accs );
911 :
912 0 : for( ulong j=0UL; j<batch_n; j++ ) {
913 0 : FD_TEST( accs[ j ].lamports && fd_vsv_is_correct_size_owner_and_init( accs[ j ].owner, accs[ j ].data, accs[ j ].data_len ) );
914 0 : count_vote_acc( ctx, slot_completed, ghost_blk, &vote_accs[ j ], stakes[ j ], accs[ j ].data, accs[ j ].data_len );
915 0 : prev_voter_idx = fd_tower_stakes_insert( ctx->tower, slot_completed->slot, &vote_accs[ j ], stakes[ j ], prev_voter_idx );
916 0 : }
917 :
918 0 : fd_accdb_release( ctx->accdb, batch_n, accs );
919 0 : }
920 0 : #undef BATCH
921 :
922 : /* Reconcile our local tower with the on-chain tower (stored inside
923 : our vote account).
924 :
925 : Skip reconciliation on the first replay_slot_completed if booted
926 : with wait_for_supermajority. This prevents spurious lockout_check
927 : failures (slot <= last_vote_slot) and threshold_check failures
928 : (deep stale tower with no voter support) */
929 :
930 0 : *our_vote_acct_bal = ULONG_MAX;
931 0 : *our_vote_acct_com = USHORT_MAX;
932 0 : *found_our_vote_acct = 0;
933 0 : fd_acc_t reconcile_ro = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc );
934 0 : if( FD_LIKELY( reconcile_ro.lamports ) ) {
935 0 : *found_our_vote_acct = 1;
936 0 : ctx->our_vote_acct_sz = fd_ulong_min( reconcile_ro.data_len, FD_VOTE_STATE_DATA_MAX );
937 0 : *our_vote_acct_bal = reconcile_ro.lamports;
938 0 : FD_TEST( !fd_vote_account_commission_bps( reconcile_ro.data,
939 0 : reconcile_ro.data_len,
940 0 : FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ),
941 0 : our_vote_acct_com ) );
942 0 : fd_memcpy( ctx->our_vote_acct, reconcile_ro.data, ctx->our_vote_acct_sz );
943 0 : int skip_reconcile = !ctx->init && ctx->wfs;
944 0 : if( FD_LIKELY( !skip_reconcile ) ) {
945 0 : ulong root;
946 0 : fd_tower_vote_remove_all( ctx->scratch_tower );
947 0 : fd_tower_from_vote_acc( ctx->scratch_tower, &root, ctx->our_vote_acct, ctx->our_vote_acct_sz );
948 0 : fd_tower_reconcile( ctx->tower, ctx->scratch_tower, root );
949 0 : } else {
950 0 : FD_LOG_NOTICE(( "wait_for_supermajority: skipping tower reconcile on init slot %lu", slot_completed->slot ));
951 0 : }
952 0 : }
953 0 : fd_accdb_unread_one( ctx->accdb, &reconcile_ro );
954 :
955 0 : return total_stake;
956 0 : }
957 :
958 : /* validate_vote_txn is the C equivalent of Agave's
959 : parse_vote_transaction. Returns the vote account on success, NULL
960 : on failure. Deserializes the vote instruction into ctx->scratch_ix.
961 :
962 : https://github.com/anza-xyz/agave/blob/v2.3.7/sdk/src/transaction/versioned/mod.rs#L79 */
963 :
964 : static fd_pubkey_t const *
965 : validate_vote_txn( fd_tower_tile_t * ctx,
966 : fd_txn_t const * txn,
967 0 : uchar const * payload ) {
968 :
969 0 : if( FD_UNLIKELY( !txn->instr_cnt ) ) return NULL;
970 0 : fd_txn_instr_t const * instr = &txn->instr[ 0 ];
971 :
972 0 : fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
973 0 : if( FD_UNLIKELY( 0!=memcmp( &accs[ instr->program_id ], &fd_solana_vote_program_id, FD_TXN_ACCT_ADDR_SZ ) ) ) return NULL;
974 :
975 0 : uchar const * instr_data = payload + instr->data_off;
976 0 : if( FD_UNLIKELY( !fd_vote_instruction_deserialize( &ctx->scratch_ix, instr_data, instr->data_sz ) ) ) return NULL;
977 :
978 0 : if( FD_UNLIKELY( !instr->acct_cnt ) ) return NULL;
979 0 : uchar const * instr_accts = payload + instr->acct_off;
980 0 : return (fd_pubkey_t const *)fd_type_pun_const( &accs[ instr_accts[ 0 ] ] );
981 0 : }
982 :
983 : /* count_vote_txn counts vote txns from Gossip, TPU and Replay. Note
984 : these txns have already been parsed and sigverified before they are
985 : sent to tower. In addition, vote txns coming from Replay have also
986 : been successfully executed. They are counted towards hfork and votes
987 : (see point 2 in the top-level documentation). */
988 :
989 : static void
990 : count_vote_txn( fd_tower_tile_t * ctx,
991 : fd_txn_t const * txn,
992 0 : uchar const * payload ) {
993 :
994 : /* We are a little stricter than Agave here because Agave only does
995 : the is_simple_vote check on replay vote txns, whereas we are doing
996 : the check on both replay and gossip / TPU vote txns.
997 :
998 : Being a little stricter with non-replay vote txns is ok because
999 : even if we drop some votes that Agave would consider valid
1000 : (unlikely unless they were sent by an actively malicious
1001 : validator), gossip votes are in general considered unreliable and
1002 : ultimately consensus (fork choice, tower rules, rooting, etc.) is
1003 : reached with vote accounts updated by replaying blocks.
1004 :
1005 : See: https://github.com/anza-xyz/agave/blob/v4.1.0-beta.1/runtime/src/bank_utils.rs#L54 */
1006 :
1007 0 : if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, payload ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_SIMPLE_VOTE_IDX ]++; return; }
1008 :
1009 0 : fd_pubkey_t const * vote_acc = validate_vote_txn( ctx, txn, payload );
1010 0 : if( FD_UNLIKELY( !vote_acc ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; }
1011 :
1012 : /* Filter any non-TowerSync vote instructions. For gossip / TPU this
1013 : filters deprecated vote kinds; for replay this shouldn't happen
1014 : after SIMD-0138 is activated. */
1015 :
1016 : /* TODO SECURITY ensure SIMD-0138 is activated */
1017 :
1018 0 : if( FD_UNLIKELY( ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync && ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync_switch ) ) {
1019 0 : ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_TOWER_SYNC_IDX ]++;
1020 0 : return;
1021 0 : }
1022 :
1023 0 : fd_tower_sync_t * tower_sync = &ctx->scratch_ix.tower_sync; /* this is safe, because TowerSyncSwitch is the same as TowerSync except with 32-bytes appended */
1024 0 : if( FD_UNLIKELY( tower_sync->lockouts_cnt>FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
1025 0 : if( FD_UNLIKELY( !tower_sync->lockouts_cnt ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_IDX ]++; return; }
1026 :
1027 0 : fd_tower_vote_remove_all( ctx->scratch_tower );
1028 0 : for( ulong i = 0; i < tower_sync->lockouts_cnt; i++ ) {
1029 0 : fd_vote_lockout_t const * lockout = deq_fd_vote_lockout_t_peek_index_const( tower_sync->lockouts, i );
1030 0 : fd_tower_vote_push_tail( ctx->scratch_tower, (fd_tower_vote_t){ .slot = lockout->slot, .conf = lockout->confirmation_count } );
1031 0 : }
1032 :
1033 : /* Validate the tower. */
1034 :
1035 0 : fd_tower_vote_t const * prev = fd_tower_vote_peek_head_const( ctx->scratch_tower );
1036 0 : if( FD_UNLIKELY( prev->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
1037 :
1038 0 : fd_tower_vote_iter_t iter = fd_tower_vote_iter_next( ctx->scratch_tower, fd_tower_vote_iter_init( ctx->scratch_tower ) );
1039 0 : for( ; !fd_tower_vote_iter_done( ctx->scratch_tower, iter ); iter = fd_tower_vote_iter_next( ctx->scratch_tower, iter ) ) {
1040 0 : fd_tower_vote_t const * vote = fd_tower_vote_iter_ele( ctx->scratch_tower, iter );
1041 0 : if( FD_UNLIKELY( vote->slot <= prev->slot ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
1042 0 : if( FD_UNLIKELY( vote->conf >= prev->conf ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
1043 0 : if( FD_UNLIKELY( vote->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; }
1044 0 : prev = vote;
1045 0 : }
1046 :
1047 0 : if( FD_UNLIKELY( 0==memcmp( &tower_sync->block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_UNKNOWN_BLOCK_ID_IDX ]++; return; };
1048 :
1049 : /* The vote txn contains a block id and bank hash for their last vote
1050 : slot in the tower. Agave always counts the last vote.
1051 :
1052 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */
1053 :
1054 0 : fd_tower_vote_t const * their_last_vote = fd_tower_vote_peek_tail_const( ctx->scratch_tower );
1055 0 : fd_hash_t const * their_block_id = &tower_sync->block_id;
1056 0 : fd_hash_t const * their_bank_hash = &tower_sync->hash;
1057 :
1058 : /* Return early if their last vote is too old. */
1059 :
1060 0 : if( FD_UNLIKELY( their_last_vote->slot <= ctx->tower->root ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_TOO_OLD_IDX ]++; return; }
1061 :
1062 : /* Determine the epoch of the vote slot and look up the voter's stake
1063 : for that epoch. Votes can be at most 1 epoch ahead of root. */
1064 :
1065 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, their_last_vote->slot );
1066 0 : if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; } /* no leader schedule to resolve the vote's epoch */
1067 0 : ulong vote_epoch = lsched->epoch;
1068 :
1069 0 : epoch_vtr_t * epoch_vtr_pool = NULL;
1070 0 : epoch_vtr_map_t * epoch_vtr_map = NULL;
1071 0 : ulong total_stake = 0UL;
1072 0 : if( FD_LIKELY( vote_epoch==ctx->root_epoch ) ) { epoch_vtr_pool = ctx->root_epoch_vtr_pool; epoch_vtr_map = ctx->root_epoch_vtr_map; total_stake = ctx->root_epoch_total_stake; }
1073 0 : else if( FD_LIKELY( vote_epoch==ctx->root_epoch + 1 ) ) { epoch_vtr_pool = ctx->next_epoch_vtr_pool; epoch_vtr_map = ctx->next_epoch_vtr_map; total_stake = ctx->next_epoch_total_stake; }
1074 0 : else { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return; }
1075 0 : epoch_vtr_t * vtr = epoch_vtr_map_ele_query( epoch_vtr_map, vote_acc, NULL, epoch_vtr_pool );
1076 0 : if( FD_UNLIKELY( !vtr ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return; }
1077 :
1078 : /* Verify the authorized voter for this vote account at vote_epoch is
1079 : among the txn signers. Mirrors Agave's cluster_info_vote_listener
1080 : check. authorized_voter is cached on the epoch_vtr by
1081 : query_voters; an all-zero value means we couldn't read it. */
1082 :
1083 0 : if( FD_UNLIKELY( 0==memcmp( &vtr->auth_vtr, &pubkey_null, sizeof(fd_pubkey_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; }
1084 0 : fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off );
1085 0 : int signer_ok = 0;
1086 0 : for( ulong i=0; i<txn->signature_cnt; i++ ) {
1087 0 : if( 0==memcmp( &accs[i], &vtr->auth_vtr, sizeof(fd_pubkey_t) ) ) { signer_ok = 1; break; }
1088 0 : }
1089 0 : if( FD_UNLIKELY( !signer_ok ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; }
1090 :
1091 : /* The txn passed all per-txn validation; we will now count its
1092 : individual vote slots (per-slot metrics below). */
1093 :
1094 0 : ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_SUCCESS_IDX ]++;
1095 :
1096 0 : int hfork_err = fd_hfork_count_vote( ctx->hfork, vote_acc, their_block_id, their_bank_hash, their_last_vote->slot, vtr->stake, total_stake );
1097 0 : update_metrics_hfork( ctx, hfork_err, their_last_vote->slot, their_block_id );
1098 :
1099 0 : int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, vtr->stake, their_last_vote->slot, their_block_id );
1100 0 : update_metrics_vote_slot( ctx, votes_err );
1101 0 : if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_last_vote->slot, their_block_id, total_stake );
1102 :
1103 : /* Agave decides to count intermediate vote slots in the tower iff:
1104 :
1105 : 1. they've replayed the slot
1106 : 2. their replay bank hash matches the vote's bank hash.
1107 :
1108 : This guarantees the intermediate slots they are counting are in
1109 : fact for the correct ancestry (in case of equivocation). We do the
1110 : same thing, but using block ids instead of bank hashes.
1111 :
1112 : It's possible we haven't yet replayed this slot being voted on
1113 : because gossip votes can be ahead of our replay.
1114 :
1115 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */
1116 :
1117 0 : if( FD_UNLIKELY( !fd_tower_blocks_query( ctx->tower, their_last_vote->slot ) ) ) { ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_UNKNOWN_SLOT_IDX ]++; return; }; /* we haven't replayed this block yet */
1118 0 : fd_hash_t const * our_block_id = fd_tower_blocks_canonical_block_id( ctx->tower, their_last_vote->slot );
1119 0 : if( FD_UNLIKELY( 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) { ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_UNKNOWN_BLOCK_ID_IDX ]++; return; } /* we don't recognize this block id */
1120 :
1121 : /* At this point, we know we have replayed the same slot and also have
1122 : a matching block id, so we can count the intermediate votes. */
1123 :
1124 0 : ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_GATE_V_PROCEED_IDX ]++;
1125 :
1126 0 : int skipped_last_vote = 0;
1127 0 : for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( ctx->scratch_tower );
1128 0 : !fd_tower_vote_iter_done_rev( ctx->scratch_tower, iter );
1129 0 : iter = fd_tower_vote_iter_prev ( ctx->scratch_tower, iter ) ) {
1130 0 : if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; }
1131 0 : fd_tower_vote_t const * their_intermediate_vote = fd_tower_vote_iter_ele_const( ctx->scratch_tower, iter );
1132 :
1133 : /* If we don't recognize an intermediate vote slot in their tower,
1134 : it means their tower either:
1135 :
1136 : 1. Contains intermediate vote slots that are too old (older than
1137 : our root) so we already pruned them for tower_forks. Normally
1138 : if the descendant (last vote slot) is in tower forks, then all
1139 : of its ancestors should be in there too.
1140 :
1141 : 2. Is invalid. Even though at this point we have successfully
1142 : sigverified and deserialized their vote txn, the tower itself
1143 : might still be invalid because unlike TPU vote txns, we have
1144 : not plumbed through the vote program, but obviously gossip
1145 : votes do not so we need to do some light validation here.
1146 :
1147 : We could throwaway this voter's tower, but we handle it the same
1148 : way as Agave which is to just skip this intermediate vote slot:
1149 :
1150 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */
1151 :
1152 0 : if( FD_UNLIKELY( their_intermediate_vote->slot <= ctx->tower->root ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_OLD_IDX ]++; continue; }
1153 :
1154 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, their_intermediate_vote->slot );
1155 0 : if( FD_UNLIKELY( !tower_blk ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_SLOT_IDX ]++; continue; }
1156 :
1157 : /* Otherwise, we count the vote using our own block id for that slot
1158 : (again, mirroring what Agave does albeit with bank hashes).
1159 :
1160 : Agave uses the current root bank's total stake when counting vote
1161 : txns from gossip / replay:
1162 :
1163 : https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */
1164 :
1165 0 : fd_hash_t const * intermediate_block_id = fd_tower_blocks_canonical_block_id( ctx->tower, their_intermediate_vote->slot );
1166 0 : int votes_err = fd_votes_count_vote( ctx->votes, vote_acc, vtr->stake, their_intermediate_vote->slot, intermediate_block_id );
1167 0 : update_metrics_vote_slot( ctx, votes_err );
1168 0 : if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_intermediate_vote->slot, intermediate_block_id, total_stake );
1169 0 : }
1170 0 : }
1171 :
1172 : /* Query the staked voters in the provided epoch:
1173 :
1174 : 1. identity keys (aka. node pubkeys)
1175 : 2. vote account addresses
1176 : 3. associated stake (for the epoch)
1177 : 4. authorized voter (for the epoch) */
1178 :
1179 : static ulong
1180 : query_epoch_voters( fd_tower_tile_t * ctx,
1181 : ulong epoch,
1182 : int iter_kind,
1183 : fd_accdb_fork_id_t accdb_fork_id,
1184 : fd_vote_stakes_t * vote_stakes,
1185 : ulong vote_stakes_fork_id,
1186 : epoch_vtr_t * pool,
1187 : epoch_vtr_map_t * map,
1188 0 : int update_id_keys_vote_accs ) {
1189 :
1190 0 : epoch_vtr_pool_reset( pool );
1191 0 : epoch_vtr_map_reset( map );
1192 0 : ulong total_stake = 0UL;
1193 0 : fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, vote_stakes_fork_id, iter_kind, ctx->iter_mem );
1194 0 : while( !fd_vote_stakes_iter_done( vote_stakes, vote_stakes_fork_id, iter_kind, iter ) ) {
1195 0 : fd_pubkey_t pubkey;
1196 0 : ulong stake;
1197 0 : fd_vote_stakes_iter_ele( vote_stakes, vote_stakes_fork_id, iter_kind, iter, &pubkey, NULL, &stake,
1198 0 : NULL, NULL, NULL, NULL, NULL, NULL, NULL );
1199 0 : fd_vote_stakes_iter_next( vote_stakes, vote_stakes_fork_id, iter_kind, iter );
1200 0 : total_stake += stake;
1201 0 : epoch_vtr_t * vtr = epoch_vtr_pool_ele_acquire( pool );
1202 0 : vtr->vote_acc = pubkey;
1203 0 : vtr->stake = stake;
1204 0 : memset( &vtr->auth_vtr, 0, sizeof(fd_pubkey_t) );
1205 :
1206 : /* Cache the authorized voter for target_epoch. Leaves
1207 : auth_vtr all-zero if the vote account is unreadable —
1208 : count_vote_txn will reject txns whose signer can't match. */
1209 :
1210 0 : fd_acc_t ro = fd_accdb_read_one( ctx->accdb, accdb_fork_id, pubkey.uc );
1211 0 : if( FD_LIKELY( ro.lamports && fd_vsv_is_correct_size_owner_and_init( ro.owner, ro.data, ro.data_len ) ) ) {
1212 0 : fd_pubkey_t identity[1];
1213 0 : ulong dummy_idx;
1214 0 : vote_account_config( ctx, ro.data, ro.data_len, epoch, &vtr->auth_vtr, &dummy_idx, identity );
1215 0 : if( update_id_keys_vote_accs ) {
1216 0 : FD_TEST( 0==fd_vote_account_node_pubkey( ro.data, ro.data_len, &ctx->id_keys[ctx->vtr_cnt] ) ); /* check vote account is not corrupt */
1217 0 : ctx->vote_accs[ctx->vtr_cnt] = pubkey;
1218 0 : ctx->vtr_cnt++;
1219 0 : }
1220 0 : }
1221 0 : fd_accdb_unread_one( ctx->accdb, &ro );
1222 :
1223 0 : epoch_vtr_map_ele_insert( map, vtr, pool );
1224 0 : }
1225 0 : return total_stake;
1226 0 : }
1227 :
1228 : /* Update the cached voters for both the currently rooted epoch and the
1229 : next epoch, to allow processing vote transactions for vote slots that
1230 : span both these epochs. */
1231 :
1232 : FD_FN_UNUSED void
1233 : query_voters( fd_tower_tile_t * ctx,
1234 : fd_replay_slot_completed_t * slot_completed,
1235 0 : ulong epoch ) {
1236 0 : if( FD_LIKELY( ctx->banks ) ) {
1237 0 : fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx );
1238 0 : if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx ));
1239 :
1240 0 : ctx->vtr_cnt = 0;
1241 0 : fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank );
1242 0 : ctx->root_epoch_total_stake = query_epoch_voters( ctx, epoch, FD_VOTE_STAKES_ITER_T_2, bank->accdb_fork_id, vote_stakes, bank->vote_stakes_fork_id, ctx->root_epoch_vtr_pool, ctx->root_epoch_vtr_map, 1 );
1243 0 : ctx->next_epoch_total_stake = query_epoch_voters( ctx, epoch+1UL, FD_VOTE_STAKES_ITER_T_1, bank->accdb_fork_id, vote_stakes, bank->vote_stakes_fork_id, ctx->next_epoch_vtr_pool, ctx->next_epoch_vtr_map, 0 );
1244 0 : }
1245 0 : ctx->root_epoch = epoch;
1246 :
1247 0 : fd_eqvoc_update_voters( ctx->eqvoc, ctx->id_keys, ctx->vtr_cnt );
1248 0 : fd_hfork_update_voters( ctx->hfork, ctx->vote_accs, ctx->vtr_cnt );
1249 0 : fd_votes_update_voters( ctx->votes, ctx->vote_accs, ctx->vtr_cnt );
1250 0 : }
1251 :
1252 : static void
1253 : replay_slot_completed( fd_tower_tile_t * ctx,
1254 : fd_replay_slot_completed_t * slot_completed,
1255 : ulong tsorig,
1256 0 : fd_stem_context_t * stem ) {
1257 :
1258 : /* If the slot has already been replayed, we can just ignore it (but
1259 : refresh bank_seq so confirmations reference the surviving row, and
1260 : release the bank ref). */
1261 0 : fd_ghost_blk_t * replayed = fd_ghost_query( ctx->ghost, &slot_completed->block_id );
1262 0 : if( FD_UNLIKELY( replayed ) ) {
1263 0 : replayed->bank_seq = slot_completed->bank_seq;
1264 0 : publish_slot_ignored( ctx, slot_completed, tsorig, stem );
1265 0 : return;
1266 0 : }
1267 :
1268 : /* Sanity checks. */
1269 :
1270 0 : FD_TEST( 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) );
1271 :
1272 0 : fd_tower_stakes_remove( ctx->tower, slot_completed->slot ); /* no-op for 99% of cases except for eqvoc */
1273 0 : fd_tower_vtr_t * tower_voters = ctx->tower->vtrs;
1274 0 : fd_tower_vtr_remove_all( tower_voters );
1275 0 : ctx->vtr_cnt = 0;
1276 :
1277 : /* Insert into ghost. */
1278 :
1279 0 : fd_ghost_blk_t * ghost_blk;
1280 0 : if( FD_UNLIKELY( !ctx->init ) ) {
1281 :
1282 : /* This is the first replay_slot_completed (ie. the snapshot or
1283 : genesis slot), so initialize the ghost root. */
1284 :
1285 0 : ghost_blk = fd_ghost_init( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id );
1286 :
1287 0 : } else if ( FD_UNLIKELY( !fd_ghost_query( ctx->ghost, &slot_completed->parent_block_id ) )) {
1288 :
1289 : /* Due to asynchronous frag processing, it's possible this block from
1290 : replay_slot_completed is on a minority fork Tower already pruned
1291 : after publishing a new root. */
1292 :
1293 0 : ctx->metrics.ignored_cnt++;
1294 0 : ctx->metrics.ignored_slot = slot_completed->slot;
1295 0 : publish_slot_ignored( ctx, slot_completed, tsorig, stem );
1296 0 : report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, 0UL /* stake */, 0UL /* total_stake */, 1 /* valid */, FD_EVENT_SLOT_CONFIRMED_LEVEL_IGNORED, 0 /* not forward */ );
1297 0 : return; /* short-circuit processing this slot */
1298 :
1299 0 : } else {
1300 :
1301 : /* Common case. */
1302 :
1303 0 : ghost_blk = fd_ghost_insert( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, &slot_completed->parent_block_id );
1304 0 : }
1305 0 : FD_TEST( ghost_blk );
1306 :
1307 : /* Insert into tower. */
1308 :
1309 0 : fd_tower_blk_t * eqvoc_tower_blk = NULL;
1310 0 : if( FD_UNLIKELY( eqvoc_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->slot ) ) ) {
1311 :
1312 : /* If eqvoc_tower_blk is not NULL, then we know this slot
1313 : equivocates (there are multiple blocks in the slot).
1314 :
1315 : Replay processes at most 2 equivocating blocks for a given slot,
1316 : and the latter block is guaranteed to be confirmed.
1317 :
1318 : At this point, we know we are processing the latter block, so we
1319 : record that in the tower_blk. */
1320 :
1321 0 : fd_tower_lockos_remove( ctx->tower, slot_completed->slot );
1322 :
1323 0 : ctx->metrics.eqvoc_cnt++;
1324 0 : ctx->metrics.eqvoc_slot = fd_ulong_max( ctx->metrics.eqvoc_slot, slot_completed->slot );
1325 :
1326 0 : fd_ghost_confirm( ctx->ghost, &slot_completed->block_id );
1327 0 : FD_BASE58_ENCODE_32_BYTES( eqvoc_tower_blk->replayed_block_id.uc, eqvoc_blk_id );
1328 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
1329 0 : fd_ghost_eqvoc( ctx->ghost, &eqvoc_tower_blk->replayed_block_id );
1330 0 : report_block_equivocated( &(block_equivocated_args_t){
1331 0 : .slot = slot_completed->slot, .parent_slot = eqvoc_tower_blk->parent_slot, .epoch = eqvoc_tower_blk->epoch,
1332 0 : .block_id = &eqvoc_tower_blk->replayed_block_id, .sibling_block_id = &slot_completed->block_id,
1333 0 : .bank_hash = &eqvoc_tower_blk->bank_hash, .block_hash = &eqvoc_tower_blk->block_hash,
1334 0 : .bank_seq = 0UL, .is_leader = eqvoc_tower_blk->leader,
1335 0 : .our_block_voted = eqvoc_tower_blk->voted, .our_block_confirmed = our_block_confirmed( eqvoc_tower_blk ),
1336 0 : .block_stake = votes_stake( ctx, slot_completed->slot, &eqvoc_tower_blk->replayed_block_id ),
1337 0 : .sibling_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ),
1338 0 : .total_stake = ghost_blk->total_stake,
1339 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_DUPLICATE_REPLAY } );
1340 :
1341 0 : eqvoc_tower_blk->parent_slot = slot_completed->parent_slot;
1342 0 : eqvoc_tower_blk->bank_hash = slot_completed->bank_hash;
1343 0 : eqvoc_tower_blk->block_hash = slot_completed->block_hash;
1344 0 : eqvoc_tower_blk->replayed_block_id = slot_completed->block_id;
1345 0 : } else {
1346 :
1347 : /* Otherwise this is the first replay of this block, so insert a new
1348 : tower_blk. */
1349 :
1350 0 : fd_tower_blk_t * tower_blk = fd_tower_blocks_insert( ctx->tower, slot_completed->slot, slot_completed->parent_slot );
1351 0 : tower_blk->parent_slot = slot_completed->parent_slot;
1352 0 : tower_blk->epoch = slot_completed->epoch;
1353 0 : tower_blk->bank_hash = slot_completed->bank_hash;
1354 0 : tower_blk->block_hash = slot_completed->block_hash;
1355 0 : tower_blk->replayed = 1;
1356 0 : tower_blk->replayed_block_id = slot_completed->block_id;
1357 0 : tower_blk->voted = 0;
1358 0 : tower_blk->confirmed = 0;
1359 0 : tower_blk->leader = slot_completed->is_leader;
1360 0 : tower_blk->propagated = 0;
1361 :
1362 : /* Set the prev_leader_slot. */
1363 :
1364 0 : if( FD_UNLIKELY( tower_blk->leader ) ) {
1365 0 : tower_blk->prev_leader_slot = slot_completed->slot;
1366 0 : } else if ( FD_UNLIKELY( ghost_blk==fd_ghost_root( ctx->ghost ) ) ) {
1367 0 : tower_blk->prev_leader_slot = ULONG_MAX;
1368 0 : } else {
1369 0 : fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot );
1370 0 : FD_TEST( parent_tower_blk );
1371 0 : tower_blk->prev_leader_slot = parent_tower_blk->prev_leader_slot;
1372 0 : }
1373 :
1374 0 : fd_votes_blk_t * fwd_votes_blk = fd_votes_query( ctx->votes, slot_completed->slot, NULL );
1375 0 : if( FD_UNLIKELY( fwd_votes_blk && fd_uchar_extract_bit( fwd_votes_blk->flags, FD_TOWER_SLOT_CONFIRMED_DUPLICATE+4 ) ) ) {
1376 :
1377 : /* A block_id for this slot was forward-confirmed at the duplicate
1378 : level before replay (publish_slot_confirmed ran when no
1379 : ghost_blk existed). Resolve the pending confirmation now. */
1380 :
1381 0 : tower_blk->confirmed = 1;
1382 0 : tower_blk->confirmed_block_id = fwd_votes_blk->key.block_id;
1383 :
1384 0 : if( FD_LIKELY( 0==memcmp( &tower_blk->replayed_block_id, &fwd_votes_blk->key.block_id, sizeof(fd_hash_t) ) ) ) {
1385 :
1386 : /* The forward-confirmed block_id matches what we replayed. */
1387 :
1388 0 : fd_ghost_confirm( ctx->ghost, &slot_completed->block_id );
1389 :
1390 0 : } else {
1391 :
1392 : /* The forward-confirmed block_id differs from what we replayed,
1393 : so our replayed block is an equivocating sibling. */
1394 :
1395 0 : FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id );
1396 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (confirmed before replayed). slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
1397 0 : fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id );
1398 0 : report_block_equivocated( &(block_equivocated_args_t){
1399 0 : .slot = slot_completed->slot, .parent_slot = slot_completed->parent_slot, .epoch = slot_completed->epoch,
1400 0 : .block_id = &slot_completed->block_id, .sibling_block_id = &fwd_votes_blk->key.block_id,
1401 0 : .bank_hash = &slot_completed->bank_hash, .block_hash = &slot_completed->block_hash,
1402 0 : .bank_seq = slot_completed->bank_seq, .is_leader = slot_completed->is_leader,
1403 0 : .our_block_confirmed = 0,
1404 0 : .block_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ),
1405 0 : .sibling_stake = fwd_votes_blk->stake,
1406 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } );
1407 0 : }
1408 :
1409 0 : } else if( FD_UNLIKELY( fd_eqvoc_proof_verified( ctx->eqvoc, slot_completed->slot ) ) ) {
1410 :
1411 : /* Eqvoc already detected equivocation for this slot (via shreds
1412 : or gossip before replay). Mark the ghost block invalid. */
1413 :
1414 0 : FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id );
1415 0 : FD_LOG_DEBUG(( "[%s] equivocation detected via eqvoc shred proof before replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id ));
1416 0 : fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id );
1417 0 : report_block_equivocated( &(block_equivocated_args_t){
1418 0 : .slot = slot_completed->slot, .parent_slot = slot_completed->parent_slot, .epoch = slot_completed->epoch,
1419 0 : .block_id = &slot_completed->block_id, .sibling_block_id = NULL /* unknown */,
1420 0 : .bank_hash = &slot_completed->bank_hash, .block_hash = &slot_completed->block_hash,
1421 0 : .bank_seq = slot_completed->bank_seq, .is_leader = slot_completed->is_leader,
1422 0 : .block_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ),
1423 0 : .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_SHRED_PROOF } );
1424 0 : }
1425 0 : }
1426 :
1427 0 : if( FD_UNLIKELY( !ctx->init ) ) {
1428 0 : ctx->metrics.init_slot = slot_completed->slot;
1429 0 : ctx->tower->root = slot_completed->slot;
1430 0 : fd_votes_publish( ctx->votes, slot_completed->slot );
1431 0 : }
1432 :
1433 : /* Count the vote accounts and reconcile our own vote account. */
1434 :
1435 0 : ulong our_vote_acct_bal = ULONG_MAX;
1436 0 : ushort our_vote_acct_com = USHORT_MAX;
1437 0 : int found = 0;
1438 0 : ghost_blk->total_stake = QUERY_TOWERS( ctx, slot_completed, ghost_blk, &found, &our_vote_acct_bal, &our_vote_acct_com );
1439 :
1440 : /* Capture the values needed for the processed event now: advancing the
1441 : root below (fd_ghost_publish) can prune ghost_blk if this block was
1442 : replayed on a minority fork, freeing it before we report. */
1443 :
1444 0 : ulong processed_stake = ghost_blk->stake;
1445 0 : ulong processed_total_stake = ghost_blk->total_stake;
1446 0 : int processed_valid = ghost_blk->valid;
1447 :
1448 : /* The first replay_slot_completed msg is used to initialize the tower
1449 : tile's various structures. */
1450 :
1451 0 : if( FD_UNLIKELY( !ctx->init ) ) {
1452 0 : ctx->init = 1;
1453 0 : QUERY_VOTERS( ctx, slot_completed, slot_completed->epoch );
1454 0 : }
1455 :
1456 : /* Insert into hard fork detector. */
1457 :
1458 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_completed->slot );
1459 0 : int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_completed->block_id, &slot_completed->bank_hash, fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake ) );
1460 0 : update_metrics_hfork( ctx, hfork_flag, slot_completed->slot, &slot_completed->block_id );
1461 :
1462 : /* Determine reset, vote, and root slots. There may not be a vote or
1463 : root slot but there is always a reset slot. */
1464 :
1465 0 : fd_tower_out_t out = { .vote_slot = ULONG_MAX, .root_slot = ULONG_MAX };
1466 0 : out.flags = fd_tower_vote_and_reset( ctx->tower, ctx->ghost, ctx->votes,
1467 0 : &out.reset_slot, &out.reset_block_id, &out.reset_bank_seq,
1468 0 : &out.vote_slot, &out.vote_block_id, &out.vote_bank_hash,
1469 0 : &out.root_slot, &out.root_block_id );
1470 0 : if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) { /* if there is a vote slot we record it. */
1471 0 : fd_tower_blk_t * vote_tower_blk = fd_tower_blocks_query( ctx->tower, out.vote_slot );
1472 0 : vote_tower_blk->voted = 1;
1473 0 : vote_tower_blk->voted_block_id = out.vote_block_id;
1474 0 : }
1475 :
1476 : /* Publish structures if there is a new root. */
1477 :
1478 0 : if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) {
1479 0 : if( FD_UNLIKELY( 0==memcmp( &out.root_block_id, &hash_null, sizeof(fd_hash_t) ) ) ) {
1480 0 : FD_LOG_CRIT(( "invariant violation: root block id is null at slot %lu", out.root_slot ));
1481 0 : }
1482 :
1483 0 : fd_tower_blk_t * oldr_tower_blk = fd_tower_blocks_query( ctx->tower, ctx->tower->root );
1484 0 : fd_tower_blk_t * newr_tower_blk = fd_tower_blocks_query( ctx->tower, out.root_slot );
1485 0 : FD_TEST( oldr_tower_blk );
1486 0 : FD_TEST( newr_tower_blk );
1487 :
1488 : /* It is a Solana consensus protocol invariant that a validator must
1489 : make at least one root in an epoch, so the root's epoch cannot
1490 : advance by more than one. */
1491 :
1492 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 */
1493 :
1494 : /* Publish votes: 1. reindex if it's a new epoch. 2. publish the new
1495 : root to votes. */
1496 :
1497 0 : if( FD_UNLIKELY( oldr_tower_blk->epoch+1==newr_tower_blk->epoch ) ) {
1498 0 : FD_TEST( newr_tower_blk->epoch==slot_completed->epoch ); /* new root's epoch must be same as current slot_completed */
1499 0 : QUERY_VOTERS( ctx, slot_completed, newr_tower_blk->epoch );
1500 0 : }
1501 0 : fd_votes_publish( ctx->votes, out.root_slot );
1502 :
1503 : /* Publish tower_blocks and tower_stakes by removing any entries
1504 : older than the new root. */
1505 :
1506 0 : for( ulong slot = ctx->tower->root; slot < out.root_slot; slot++ ) {
1507 0 : fd_tower_blocks_remove( ctx->tower, slot );
1508 0 : fd_tower_lockos_remove( ctx->tower, slot );
1509 0 : fd_tower_stakes_remove( ctx->tower, slot );
1510 0 : }
1511 :
1512 : /* Publish roots by walking up the ghost ancestry to publish new root
1513 : frags for intermediate slots we couldn't vote for. */
1514 :
1515 0 : fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id );
1516 0 : fd_ghost_blk_t * oldr = fd_ghost_root( ctx->ghost );
1517 :
1518 0 : if( FD_UNLIKELY( !newr ) ) {
1519 0 : FD_BASE58_ENCODE_32_BYTES( out.root_block_id.uc, root_blk_id );
1520 0 : FD_LOG_ERR(( "new root block %s (slot %lu) is not in ghost: our root conflicts with the cluster", root_blk_id, out.root_slot ));
1521 0 : }
1522 :
1523 : /* oldr is not guaranteed to be the immediate parent of newr, but is
1524 : rather an arbitrary ancestor. This can happen if we couldn't
1525 : vote for those intermediate slot(s). We publish those slots as
1526 : intermediate roots. */
1527 :
1528 0 : fd_ghost_blk_t * intr = newr;
1529 0 : while( FD_LIKELY( intr!=oldr ) ) {
1530 0 : publish_slot_rooted( ctx, intr->slot, &intr->id );
1531 0 : report_slot_confirmed( intr->bank_seq, intr->slot, &intr->id, intr->stake, intr->total_stake, intr->valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_ROOTED, 0 /* not forward */ );
1532 0 : intr = fd_ghost_parent( ctx->ghost, intr );
1533 0 : }
1534 :
1535 : /* Publish ghost. */
1536 :
1537 0 : fd_ghost_publish( ctx->ghost, newr );
1538 :
1539 : /* Update the new root. */
1540 :
1541 0 : ctx->tower->root = out.root_slot;
1542 0 : }
1543 :
1544 : /* Publish a slot_done frag to tower_out. */
1545 :
1546 0 : publish_slot_done( ctx, slot_completed, &out, found, our_vote_acct_bal, our_vote_acct_com, tsorig, stem );
1547 0 : report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, processed_stake, processed_total_stake, processed_valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_PROCESSED, 0 /* not forward */ );
1548 :
1549 : /* Write out metrics. */
1550 :
1551 0 : ctx->metrics.replay_slot = slot_completed->slot;
1552 0 : if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) ctx->metrics.last_vote_slot = out.vote_slot;
1553 0 : ctx->metrics.last_vote_slot = fd_ulong_if( out.vote_slot!=ULONG_MAX, out.vote_slot, ctx->metrics.last_vote_slot );
1554 0 : ctx->metrics.reset_slot = out.reset_slot; /* always set */
1555 0 : ctx->metrics.root_slot = ctx->tower->root;
1556 :
1557 : /* Fork-decision axis: fd_tower_vote_and_reset sets exactly one of these
1558 : five fork flags, except in the two no-vote-yet short-circuits (case 0a
1559 : and 0b) where it sets no flags. Those are distinguished by vote_slot:
1560 : 0a does not vote (vote_slot==ULONG_MAX), 0b votes (vote_slot set). */
1561 :
1562 0 : if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_ANCESTOR_ROLLBACK_IDX ]++;
1563 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SIBLING_CONFIRMED ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SIBLING_CONFIRMED_IDX ]++;
1564 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SAME_FORK ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SAME_FORK_IDX ]++;
1565 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SWITCH_PASS_IDX ]++;
1566 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_SWITCH_FAIL_IDX ]++;
1567 0 : else if( out.vote_slot!=ULONG_MAX ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_EMPTY_TOWER_VOTE_IDX ]++;
1568 0 : else ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_DECISION_V_NO_VOTE_NOT_RECENT_IDX ]++;
1569 :
1570 : /* Vote-gate axis: if a votable block was selected, it is gated by the
1571 : lockout/threshold/propagated checks (at most one fails) or it passes
1572 : all of them and we vote. If no votable block was selected, there is
1573 : no candidate to gate. */
1574 :
1575 0 : if( out.vote_slot!=ULONG_MAX ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_VOTED_IDX ]++;
1576 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_LOCKOUT_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_LOCKOUT_FAIL_IDX ]++;
1577 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_THRESHOLD_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_THRESHOLD_FAIL_IDX ]++;
1578 0 : else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_PROPAGATED_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_PROPAGATED_FAIL_IDX ]++;
1579 0 : else ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_V_NO_CANDIDATE_IDX ]++;
1580 :
1581 : /* Log out structures. */
1582 :
1583 0 : char cstr[4096]; ulong cstr_sz;
1584 0 : FD_LOG_DEBUG(( "\n\n%s", fd_ghost_to_cstr( ctx->ghost, fd_ghost_root( ctx->ghost ), cstr, sizeof(cstr), &cstr_sz ) ));
1585 0 : FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( ctx->tower, cstr ) ));
1586 0 : }
1587 :
1588 : FD_FN_CONST static inline ulong
1589 0 : scratch_align( void ) {
1590 0 : return 128UL;
1591 0 : }
1592 :
1593 : FD_FN_PURE static inline ulong
1594 0 : scratch_footprint( fd_topo_tile_t const * tile ) {
1595 0 : ulong slot_max = fd_ulong_pow2_up( tile->tower.max_live_slots );
1596 0 : ulong blk_max = slot_max * EQVOC_MAX;
1597 0 : ulong fec_max = slot_max * tile->tower.max_shreds_per_block / FD_FEC_SHRED_CNT;
1598 0 : ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
1599 :
1600 0 : ulong l = FD_LAYOUT_INIT;
1601 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1602 0 : l = FD_LAYOUT_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1603 : /* auth_vtr_keyswitch */
1604 0 : l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) );
1605 0 : l = FD_LAYOUT_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, VTR_MAX ) );
1606 0 : l = FD_LAYOUT_APPEND( l, fd_hfork_align(), fd_hfork_footprint( PER_VTR_MAX, VTR_MAX ) );
1607 0 : l = FD_LAYOUT_APPEND( l, fd_votes_align(), fd_votes_footprint( slot_max, VTR_MAX ) );
1608 0 : l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint( slot_max, VTR_MAX ) );
1609 0 : l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
1610 0 : l = FD_LAYOUT_APPEND( l, publishes_align(), publishes_footprint( pub_max ) );
1611 0 : l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->tower.max_live_slots ) );
1612 0 : ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX );
1613 0 : l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) );
1614 0 : l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) );
1615 0 : l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) );
1616 0 : l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) );
1617 0 : return FD_LAYOUT_FINI( l, scratch_align() );
1618 0 : }
1619 :
1620 : /* init_choreo allocates and initializes all choreo consensus structures
1621 : from scratch memory. scratch must be at least scratch_footprint
1622 : bytes aligned to scratch_align(). The seed field at the start of
1623 : scratch must be pre-initialized (eg. by privileged_init). Returns a
1624 : handle to the fd_tower_tile_t in scratch. */
1625 :
1626 : static fd_tower_tile_t *
1627 : init_choreo( void * scratch,
1628 : fd_topo_t const * topo,
1629 0 : fd_topo_tile_t const * tile ) {
1630 0 : ulong slot_max = fd_ulong_pow2_up( tile->tower.max_live_slots );
1631 0 : ulong blk_max = slot_max * EQVOC_MAX;
1632 0 : ulong fec_max = slot_max * tile->tower.max_shreds_per_block / FD_FEC_SHRED_CNT;
1633 0 : ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT;
1634 :
1635 0 : void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->tower.accdb_obj_id );
1636 0 : fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem );
1637 0 : FD_TEST( accdb_shmem );
1638 :
1639 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1640 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1641 0 : void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1642 0 : void * eqvoc = FD_SCRATCH_ALLOC_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) );
1643 0 : void * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, VTR_MAX ) );
1644 0 : void * hfork = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(), fd_hfork_footprint( PER_VTR_MAX, VTR_MAX ) );
1645 0 : void * votes = FD_SCRATCH_ALLOC_APPEND( l, fd_votes_align(), fd_votes_footprint( slot_max, VTR_MAX ) );
1646 0 : void * tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint( slot_max, VTR_MAX ) );
1647 0 : void * scratch_tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() );
1648 0 : void * publishes = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(), publishes_footprint( pub_max ) );
1649 0 : void * accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->tower.max_live_slots ) );
1650 0 : ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX );
1651 0 : void * root_epoch_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) );
1652 0 : void * root_epoch_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) );
1653 0 : void * next_epoch_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) );
1654 0 : void * next_epoch_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) );
1655 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
1656 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
1657 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
1658 0 : (void)auth_vtr; /* privileged_init */
1659 0 : ctx->eqvoc = fd_eqvoc_join ( fd_eqvoc_new ( eqvoc, slot_max, fec_max, PER_VTR_MAX, VTR_MAX, ctx->seed ) );
1660 0 : ctx->ghost = fd_ghost_join ( fd_ghost_new ( ghost, blk_max, VTR_MAX, ctx->seed ) );
1661 0 : ctx->hfork = fd_hfork_join ( fd_hfork_new ( hfork, PER_VTR_MAX, VTR_MAX, ctx->seed ) );
1662 0 : ctx->votes = fd_votes_join ( fd_votes_new ( votes, slot_max, VTR_MAX, ctx->seed ) );
1663 0 : ctx->tower = fd_tower_join ( fd_tower_new ( tower, slot_max, VTR_MAX, ctx->seed ) );
1664 0 : ctx->scratch_tower = fd_tower_vote_join ( fd_tower_vote_new ( scratch_tower ) );
1665 0 : ctx->publishes = publishes_join ( publishes_new ( publishes, pub_max ) );
1666 0 : ctx->accdb = fd_accdb_join ( fd_accdb_new ( accdb, _accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL ) );
1667 0 : ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) );
1668 0 : ctx->root_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( root_epoch_vtr_pool, VTR_MAX ) );
1669 0 : ctx->root_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( root_epoch_vtr_map, epoch_vtr_chain_cnt, ctx->seed ) );
1670 0 : ctx->next_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( next_epoch_vtr_pool, VTR_MAX ) );
1671 0 : ctx->next_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( next_epoch_vtr_map, epoch_vtr_chain_cnt, ctx->seed ) );
1672 :
1673 0 : FD_TEST( ctx->eqvoc );
1674 0 : FD_TEST( ctx->ghost );
1675 0 : FD_TEST( ctx->hfork );
1676 0 : FD_TEST( ctx->votes );
1677 0 : FD_TEST( ctx->tower );
1678 0 : FD_TEST( ctx->scratch_tower );
1679 0 : FD_TEST( ctx->publishes );
1680 0 : FD_TEST( ctx->accdb );
1681 0 : FD_TEST( ctx->mleaders );
1682 0 : FD_TEST( ctx->root_epoch_vtr_pool );
1683 0 : FD_TEST( ctx->root_epoch_vtr_map );
1684 0 : FD_TEST( ctx->next_epoch_vtr_pool );
1685 0 : FD_TEST( ctx->next_epoch_vtr_map );
1686 :
1687 0 : memset( ctx->duplicate_chunks, 0, sizeof(ctx->duplicate_chunks) );
1688 0 : memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) );
1689 0 : memset( ctx->vote_txn, 0, sizeof(ctx->vote_txn) );
1690 :
1691 0 : ctx->halt_signing = 0;
1692 0 : ctx->hard_fork_fatal = tile->tower.hard_fork_fatal;
1693 0 : ctx->wfs = tile->tower.wait_for_supermajority;
1694 0 : ctx->shred_version = 0;
1695 0 : ctx->init = 0;
1696 0 : ctx->root_epoch = ULONG_MAX;
1697 :
1698 0 : memset( &ctx->metrics, 0, sizeof(ctx->metrics) );
1699 0 : ctx->metrics.last_vote_slot = ULONG_MAX;
1700 :
1701 0 : return ctx;
1702 0 : }
1703 :
1704 : static void
1705 0 : during_housekeeping( fd_tower_tile_t * ctx ) {
1706 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
1707 0 : if( fd_keyswitch_param_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_PARAM_AV_CLEAR ) ctx->halt_signing = 0;
1708 0 : fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED );
1709 0 : }
1710 :
1711 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
1712 0 : ulong param = fd_keyswitch_param_query( ctx->auth_vtr_keyswitch );
1713 0 : if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_ADD ) ) {
1714 0 : fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( ctx->auth_vtr_keyswitch->bytes );
1715 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" ));
1716 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" ));
1717 :
1718 0 : auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
1719 0 : auth_vtr->paths_idx = ctx->auth_vtr_path_cnt;
1720 0 : ctx->auth_vtr_path_cnt++;
1721 0 : } else if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_CLEAR ) ) {
1722 0 : ctx->halt_signing = 1;
1723 0 : auth_vtr_clear( ctx->auth_vtr );
1724 0 : ctx->auth_vtr_path_cnt = 0UL;
1725 0 : if( FD_UNLIKELY( !publishes_empty( ctx->publishes ) ) ) return;
1726 0 : ctx->auth_vtr_keyswitch->result = ctx->out_seq;
1727 0 : } else {
1728 0 : FD_LOG_CRIT(( "keyswitch: unexpected authorized voter operation %lu", param ));
1729 0 : }
1730 0 : fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1731 0 : }
1732 :
1733 : /* FIXME: Currently, the tower tile doesn't support set-identity with
1734 : a tower file. When support for a tower file is added, we need to
1735 : swap the file that is running and sync it to the local state of
1736 : the tower. Because a tower file is not supported, if another
1737 : validator was running with the identity that was switched to, then
1738 : it is possible that the original validator and the fallback (this
1739 : node), may have tower files which are out of sync. This could lead
1740 : to consensus violations such as double voting or duplicate
1741 : confirmations. Currently it is unsafe for a validator operator to
1742 : switch identities without a 512 slot delay: the reason for this
1743 : delay is to account for the worst case number of slots a vote
1744 : account can be locked out for. */
1745 :
1746 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) {
1747 0 : FD_LOG_DEBUG(( "keyswitch: unhalting signing" ));
1748 0 : FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" );
1749 0 : ctx->halt_signing = 0;
1750 0 : fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1751 0 : }
1752 :
1753 0 : if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) {
1754 0 : FD_LOG_DEBUG(( "keyswitch: halting signing" ));
1755 0 : memcpy( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL );
1756 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, pubkey_str );
1757 0 : FD_LOG_INFO(( "my identity key: %s (key switched)", pubkey_str ));
1758 0 : fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED );
1759 0 : ctx->halt_signing = 1;
1760 0 : ctx->identity_keyswitch->result = ctx->out_seq;
1761 0 : }
1762 0 : }
1763 :
1764 : static inline void
1765 0 : metrics_write( fd_tower_tile_t * ctx ) {
1766 0 : fd_accdb_flush_metrics( ctx->accdb );
1767 :
1768 0 : FD_MCNT_SET( TOWER, FRAG_NOT_READY_DROPPED, ctx->metrics.not_ready );
1769 :
1770 0 : FD_MCNT_SET ( TOWER, FRAG_IGNORED, ctx->metrics.ignored_cnt );
1771 0 : FD_MGAUGE_SET( TOWER, SLOT_LAST_IGNORED, ctx->metrics.ignored_slot );
1772 :
1773 0 : FD_MGAUGE_SET( TOWER, REPLAY_SLOT, ctx->metrics.replay_slot );
1774 0 : FD_MGAUGE_SET( TOWER, VOTE_SLOT, ctx->metrics.last_vote_slot );
1775 0 : FD_MGAUGE_SET( TOWER, RESET_SLOT, ctx->metrics.reset_slot );
1776 0 : FD_MGAUGE_SET( TOWER, ROOT_SLOT, ctx->metrics.root_slot );
1777 0 : FD_MGAUGE_SET( TOWER, INIT_SLOT, ctx->metrics.init_slot );
1778 :
1779 0 : FD_MCNT_ENUM_COPY( TOWER, FORK_DECISION, ctx->metrics.fork );
1780 0 : FD_MCNT_ENUM_COPY( TOWER, VOTE_GATE, ctx->metrics.gate );
1781 :
1782 0 : FD_MCNT_ENUM_COPY( TOWER, VOTE_TXN, ctx->metrics.votes );
1783 0 : FD_MCNT_ENUM_COPY( TOWER, VOTE_SLOT_COUNTED, ctx->metrics.vote_slots );
1784 0 : FD_MCNT_ENUM_COPY( TOWER, VOTE_INTERMEDIATE_GATE, ctx->metrics.gate_int );
1785 :
1786 0 : ulong eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_CNT ];
1787 0 : eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_SUCCESS_IDX ] = ctx->metrics.eqvoc_success;
1788 0 : eqvoc_proof[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_ERROR_IDX ] = ctx->metrics.eqvoc_err;
1789 0 : FD_MCNT_ENUM_COPY( TOWER, EQVOC_PROOF, eqvoc_proof );
1790 :
1791 0 : FD_MCNT_ENUM_COPY( TOWER, GHOST_VOTE, ctx->metrics.ghost );
1792 :
1793 0 : FD_MCNT_ENUM_COPY( TOWER, HARD_FORK_VOTE, ctx->metrics.hfork );
1794 :
1795 0 : FD_MGAUGE_SET( TOWER, HARD_FORK_MATCHED_SLOT, ctx->metrics.hfork_matched_slot );
1796 0 : FD_MGAUGE_SET( TOWER, HARD_FORK_MISMATCHED_SLOT, ctx->metrics.hfork_mismatched_slot );
1797 :
1798 0 : FD_ACCDB_METRICS_WRITE( TOWER, fd_accdb_metrics( ctx->accdb ) );
1799 0 : }
1800 :
1801 : static inline void
1802 : after_credit( fd_tower_tile_t * ctx,
1803 : fd_stem_context_t * stem,
1804 : int * opt_poll_in,
1805 0 : int * charge_busy ) {
1806 0 : if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) {
1807 0 : publish_t * pub = publishes_pop_head_nocopy( ctx->publishes );
1808 0 : memcpy( fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ), &pub->msg, sizeof(fd_tower_msg_t) );
1809 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() ) );
1810 0 : ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_msg_t), ctx->out_chunk0, ctx->out_wmark );
1811 0 : ctx->out_seq = stem->seqs[ OUT_IDX ];
1812 0 : *opt_poll_in = 0; /* drain the publishes */
1813 0 : *charge_busy = 1;
1814 0 : }
1815 0 : }
1816 :
1817 : static inline int
1818 : returnable_frag( fd_tower_tile_t * ctx,
1819 : ulong in_idx,
1820 : ulong seq FD_PARAM_UNUSED,
1821 : ulong sig,
1822 : ulong chunk,
1823 : ulong sz,
1824 : ulong ctl FD_PARAM_UNUSED,
1825 : ulong tsorig,
1826 : ulong tspub FD_PARAM_UNUSED,
1827 0 : fd_stem_context_t * stem ) {
1828 :
1829 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 ) ) )
1830 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 ));
1831 :
1832 0 : switch( ctx->in_kind[ in_idx ] ) {
1833 0 : case IN_KIND_DEDUP:{
1834 0 : if( FD_UNLIKELY( !ctx->init ) ) {
1835 : /* we cannot backpressure vote txns on boot, without risking a
1836 : deadlock on the snapshot load pipeline. */
1837 0 : ctx->metrics.not_ready++;
1838 0 : return 0;
1839 0 : }
1840 0 : fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk );
1841 0 : count_vote_txn( ctx, fd_txn_m_txn_t_const( txnm ), fd_txn_m_payload_const( txnm ) );
1842 0 : return 0;
1843 0 : }
1844 0 : case IN_KIND_EPOCH: {
1845 0 : fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk );
1846 0 : FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS );
1847 0 : FD_TEST( msg->staked_id_cnt<=MAX_STAKE_WEIGHTS );
1848 0 : fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, msg );
1849 0 : fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders );
1850 0 : return 0;
1851 0 : }
1852 0 : case IN_KIND_GOSSIP: {
1853 0 : if( FD_UNLIKELY( !ctx->init ) ) { ctx->metrics.not_ready++; return 0; } /* don't backpressure gossip on boot */
1854 0 : if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED ) ) {
1855 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 ) );
1856 0 : fd_gossip_duplicate_shred_t const * duplicate_shred = msg->duplicate_shred;
1857 0 : fd_pubkey_t const * from = (fd_pubkey_t const *)fd_type_pun_const( msg->origin );
1858 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, duplicate_shred->slot );
1859 0 : if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.not_ready++; return 0; }
1860 0 : int eqvoc_err = fd_eqvoc_chunk_insert( ctx->eqvoc, ctx->tower->root, ctx->shred_version, lsched, from, duplicate_shred, ctx->duplicate_chunks );
1861 0 : update_metrics_eqvoc( ctx, eqvoc_err );
1862 0 : if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) {
1863 0 : publish_slot_duplicate( ctx, ctx->duplicate_chunks, duplicate_shred->slot );
1864 0 : }
1865 0 : }
1866 0 : return 0;
1867 0 : }
1868 0 : case IN_KIND_IPECHO: {
1869 0 : FD_TEST( sig && sig<=USHORT_MAX );
1870 0 : ctx->shred_version = (ushort)sig;
1871 0 : return 0;
1872 0 : }
1873 0 : case IN_KIND_REPLAY: {
1874 0 : switch( sig ) {
1875 0 : case REPLAY_SIG_SLOT_COMPLETED:;
1876 0 : if( FD_UNLIKELY( ctx->halt_signing ) ) return 1; /* backpressure replay_slot_completed during halt_signing. */
1877 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 ) );
1878 : /* Return frags until the epoch schedule is loaded. The epoch
1879 : schedule frag (replay_epoch link) can arrive after this
1880 : slot frag (replay_out link) because they travel on different
1881 : links with no cross-link ordering guarantee. */
1882 0 : if( FD_UNLIKELY( !fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_completed->slot ) ) ) return 1;
1883 0 : replay_slot_completed( ctx, slot_completed, tsorig, stem );
1884 0 : break;
1885 0 : case REPLAY_SIG_SLOT_DEAD:;
1886 0 : fd_replay_slot_dead_t * slot_dead = (fd_replay_slot_dead_t *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk );
1887 0 : if( FD_UNLIKELY( slot_dead->slot < ctx->tower->root ) ) return 0; /* ignore dead slots before root */
1888 0 : fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_dead->slot );
1889 0 : FD_TEST( lsched );
1890 0 : FD_TEST( lsched->epoch==ctx->root_epoch || lsched->epoch==ctx->root_epoch + 1 );
1891 0 : ulong total_stake = fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake );
1892 0 : int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_dead->block_id, NULL, total_stake );
1893 0 : update_metrics_hfork( ctx, hfork_flag, slot_dead->slot, &slot_dead->block_id );
1894 0 : break;
1895 0 : case REPLAY_SIG_TXN_EXECUTED:;
1896 0 : FD_TEST( ctx->init ); /* replay_txn_executed should never be received before replay_slot_completed, which sets init to 1. */
1897 0 : fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ) );
1898 0 : if( FD_UNLIKELY( !txn_executed->is_committable || txn_executed->is_fees_only || txn_executed->txn_err ) ) return 0;
1899 0 : count_vote_txn( ctx, TXN(txn_executed->txn), txn_executed->txn->payload );
1900 0 : break;
1901 0 : default:
1902 0 : break;
1903 0 : }
1904 0 : return 0;
1905 0 : }
1906 0 : case IN_KIND_SHRED: {
1907 0 : if( FD_LIKELY( fd_shred_sig_src( sig )==SHRED_SIG_SRC_TURBINE || fd_shred_sig_src( sig )==SHRED_SIG_SRC_REPAIR ) ) {
1908 0 : fd_shred_base_t * msg = (fd_shred_base_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) );
1909 0 : fd_shred_t * shred = &msg->shred;
1910 0 : int eqvoc_err = fd_eqvoc_shred_insert( ctx->eqvoc, fd_shred_sig_res( sig )==SHRED_SIG_RESULT_EQVOC, shred, ctx->duplicate_chunks );
1911 0 : update_metrics_eqvoc( ctx, eqvoc_err );
1912 0 : if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) publish_slot_duplicate( ctx, ctx->duplicate_chunks, shred->slot );
1913 0 : }
1914 0 : return 0;
1915 0 : }
1916 0 : default: FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] ));
1917 0 : }
1918 0 : }
1919 :
1920 : static void
1921 : privileged_init( fd_topo_t const * topo,
1922 0 : fd_topo_tile_t const * tile ) {
1923 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1924 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
1925 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
1926 0 : void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() );
1927 0 : ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() );
1928 0 : if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) )
1929 0 : FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) ));
1930 :
1931 0 : FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) );
1932 :
1933 0 : if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "missing [paths.identity_key]" ));
1934 0 : ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) );
1935 :
1936 : /* The vote key can be specified either directly as a base58 encoded
1937 : pubkey, or as a file path. We first try to decode as a pubkey. */
1938 :
1939 0 : uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc );
1940 0 : if( FD_UNLIKELY( !vote_key ) ) {
1941 0 : if( FD_UNLIKELY( !strcmp( tile->tower.vote_account, "" ) ) ) FD_LOG_ERR(( "missing [paths.vote_account]" ));
1942 0 : ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) );
1943 0 : }
1944 :
1945 0 : ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX ); FD_TEST( node_info_obj_id!=ULONG_MAX );
1946 0 : fd_node_info_box_t * node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) ); FD_TEST( node_info );
1947 0 : fd_node_info_write_begin( node_info );
1948 0 : node_info->info.vote_account = *ctx->vote_account;
1949 0 : fd_node_info_write_end( node_info );
1950 :
1951 0 : ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) );
1952 0 : for( ulong i=0UL; i<tile->tower.authorized_voter_paths_cnt; i++ ) {
1953 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 ) );
1954 0 : if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) {
1955 0 : FD_BASE58_ENCODE_32_BYTES( pubkey.uc, pubkey_b58 );
1956 0 : FD_LOG_ERR(( "authorized voter key duplicate %s", pubkey_b58 ));
1957 0 : }
1958 :
1959 0 : auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey );
1960 0 : auth_vtr->paths_idx = i;
1961 0 : }
1962 0 : ctx->auth_vtr_path_cnt = tile->tower.authorized_voter_paths_cnt;
1963 :
1964 : /* The tower file is used to checkpt and restore the state of the
1965 : local tower. */
1966 :
1967 0 : char path[ PATH_MAX ];
1968 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
1969 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 ) );
1970 0 : ctx->checkpt_fd = open( path, O_WRONLY|O_CREAT|O_TRUNC, 0600 );
1971 0 : if( FD_UNLIKELY( -1==ctx->checkpt_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
1972 :
1973 0 : FD_TEST( fd_cstr_printf_check( path, sizeof(path), NULL, "%s/tower-1_9-%s.bin", tile->tower.base_path, identity_key_b58 ) );
1974 0 : ctx->restore_fd = open( path, O_RDONLY );
1975 0 : if( FD_UNLIKELY( -1==ctx->restore_fd && errno!=ENOENT ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", path, errno, fd_io_strerror( errno ) ));
1976 0 : }
1977 :
1978 : static void
1979 : unprivileged_init( fd_topo_t const * topo,
1980 0 : fd_topo_tile_t const * tile ) {
1981 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
1982 0 : fd_tower_tile_t * ctx = init_choreo( scratch, topo, tile );
1983 :
1984 0 : ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp;
1985 0 : ctx->identity_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) );
1986 0 : ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) );
1987 :
1988 0 : FD_TEST( ctx->wksp );
1989 0 : FD_TEST( ctx->identity_keyswitch );
1990 0 : FD_TEST( ctx->auth_vtr_keyswitch );
1991 0 : FD_TEST( ctx->auth_vtr );
1992 :
1993 0 : ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX );
1994 0 : FD_TEST( banks_obj_id!=ULONG_MAX );
1995 0 : ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) );
1996 0 : FD_TEST( ctx->banks );
1997 :
1998 0 : FD_TEST( tile->in_cnt<sizeof(ctx->in_kind)/sizeof(ctx->in_kind[0]) );
1999 0 : for( ulong i=0UL; i<tile->in_cnt; i++ ) {
2000 0 : fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ];
2001 0 : fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ];
2002 :
2003 0 : if ( FD_LIKELY( !strcmp( link->name, "dedup_resolv" ) ) ) ctx->in_kind[ i ] = IN_KIND_DEDUP;
2004 0 : else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH;
2005 0 : else if( FD_LIKELY( !strcmp( link->name, "gossip_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP;
2006 0 : else if( FD_LIKELY( !strcmp( link->name, "ipecho_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO;
2007 0 : else if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY;
2008 0 : else if( FD_LIKELY( !strcmp( link->name, "shred_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_SHRED;
2009 0 : else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name ));
2010 :
2011 0 : ctx->in[ i ].mcache_only = !link->mtu;
2012 0 : if( FD_LIKELY( !ctx->in[ i ].mcache_only ) ) {
2013 0 : ctx->in[ i ].mem = link_wksp->wksp;
2014 0 : ctx->in[ i ].mtu = link->mtu;
2015 0 : ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache );
2016 0 : ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu );
2017 0 : }
2018 0 : }
2019 :
2020 0 : ctx->out_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp;
2021 0 : ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache );
2022 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 );
2023 0 : ctx->out_chunk = ctx->out_chunk0;
2024 0 : ctx->out_seq = 0UL;
2025 :
2026 0 : FD_BASE58_ENCODE_32_BYTES( ctx->vote_account->uc, vote_account_b58 );
2027 0 : FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 );
2028 0 : FD_LOG_INFO(( "my vote account: %s", vote_account_b58 ));
2029 0 : FD_LOG_INFO(( "my identity key: %s", identity_key_b58 ));
2030 0 : }
2031 :
2032 : static ulong
2033 : populate_allowed_seccomp( fd_topo_t const * topo,
2034 : fd_topo_tile_t const * tile,
2035 : ulong out_cnt,
2036 0 : struct sock_filter * out ) {
2037 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2038 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2039 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
2040 :
2041 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, FD_ACCDB_FD_RW );
2042 0 : return sock_filter_policy_fd_tower_tile_instr_cnt;
2043 0 : }
2044 :
2045 : static ulong
2046 : populate_allowed_fds( fd_topo_t const * topo,
2047 : fd_topo_tile_t const * tile,
2048 : ulong out_fds_cnt,
2049 0 : int * out_fds ) {
2050 0 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
2051 0 : FD_SCRATCH_ALLOC_INIT( l, scratch );
2052 0 : fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) );
2053 :
2054 0 : if( FD_UNLIKELY( out_fds_cnt<5UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt ));
2055 :
2056 0 : ulong out_cnt = 0UL;
2057 0 : out_fds[ out_cnt++ ] = 2; /* stderr */
2058 0 : if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) )
2059 0 : out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */
2060 0 : if( FD_LIKELY( ctx->checkpt_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->checkpt_fd;
2061 0 : if( FD_LIKELY( ctx->restore_fd!=-1 ) ) out_fds[ out_cnt++ ] = ctx->restore_fd;
2062 0 : out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts database */
2063 :
2064 0 : return out_cnt;
2065 0 : }
2066 :
2067 0 : #define STEM_BURST (2UL) /* MAX( slot_confirmed, slot_rooted AND (slot_done OR slot_ignored) ) */
2068 0 : #define STEM_LAZY (128L*3000L) /* see explanation in fd_pack */
2069 :
2070 0 : #define STEM_CALLBACK_CONTEXT_TYPE fd_tower_tile_t
2071 0 : #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_tower_tile_t)
2072 0 : #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping
2073 0 : #define STEM_CALLBACK_METRICS_WRITE metrics_write
2074 0 : #define STEM_CALLBACK_AFTER_CREDIT after_credit
2075 0 : #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag
2076 :
2077 : #include "../../disco/stem/fd_stem.c"
2078 :
2079 : static ulong
2080 0 : max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) {
2081 0 : return sizeof(fd_event_slot_confirmed_t) > sizeof(fd_event_block_equivocated_t) ?
2082 0 : sizeof(fd_event_slot_confirmed_t) : sizeof(fd_event_block_equivocated_t);
2083 0 : }
2084 :
2085 : fd_topo_run_tile_t fd_tile_tower = {
2086 : .name = "tower",
2087 : .max_event_sz = max_event_sz,
2088 : .populate_allowed_seccomp = populate_allowed_seccomp,
2089 : .populate_allowed_fds = populate_allowed_fds,
2090 : .scratch_align = scratch_align,
2091 : .scratch_footprint = scratch_footprint,
2092 : .unprivileged_init = unprivileged_init,
2093 : .privileged_init = privileged_init,
2094 : .run = stem_run,
2095 : };
|