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