Line data Source code
1 : #ifndef HEADER_fd_src_disco_gui_fd_gui_h
2 : #define HEADER_fd_src_disco_gui_fd_gui_h
3 :
4 : #include "fd_gui_peers.h"
5 : #include "fd_gui_hist.h"
6 : #include "fd_gui_ema.h"
7 :
8 : #include "../topo/fd_topo.h"
9 : #include "../diag/fd_diag_tile.h"
10 :
11 : #include "../../ballet/txn/fd_txn.h"
12 : #include "../../disco/tiles.h"
13 : #include "../../disco/fd_txn_p.h"
14 : #include "../../disco/bundle/fd_bundle_tile.h"
15 : #include "../../discof/backup/fd_snapsv_tile.h"
16 : #include "../../discof/restore/fd_snapct_tile.h"
17 : #include "../../discof/restore/utils/fd_ssmsg.h"
18 : #include "../../discof/tower/fd_tower_tile.h"
19 : #include "../../discof/replay/fd_replay_tile.h"
20 : #include "../../flamenco/leaders/fd_leaders.h"
21 : #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" /* fd_slot_to_epoch */
22 : #include "../../util/fd_util_base.h"
23 : #include "../../util/hist/fd_histf.h"
24 : #include "../../waltz/http/fd_http_server.h"
25 : #include "../../waltz/http/fd_url.h"
26 : #include "../../flamenco/accdb/fd_accdb_cache.h"
27 : #include "../../flamenco/accdb/fd_accdb_shmem.h"
28 : #include "../../flamenco/alpenglow/fd_block_marker.h" /* FD_NUM_SLOTS_FOR_REWARD */
29 :
30 :
31 : /* ---- Network Bandwidth Monitoring ----------------------------------- */
32 :
33 0 : #define FD_GUI_NETWORK_EMA_HALF_LIFE_NS (1000000000L) /* 1 second in nanoseconds */
34 0 : #define FD_GUI_NET_PROTO_CNT (7UL) /* turbine, gossip, tpu, repair, rserve, metric, votor */
35 0 : #define FD_GUI_NET_RATE_MAX_WINDOW_NS (300L*1000L*1000L*1000L) /* 5 minutes in nanoseconds */
36 :
37 : /* Monotonic deque element for sliding-window max tracking of
38 : EMA-smoothed network throughput. */
39 : struct fd_gui_rate_entry {
40 : long ts_nanos;
41 : double value;
42 : };
43 : typedef struct fd_gui_rate_entry fd_gui_rate_entry_t;
44 :
45 : /* At 100 ms sampling, 5 minutes = 3000 samples. */
46 : #define DEQUE_NAME fd_gui_rate_deque
47 0 : #define DEQUE_T fd_gui_rate_entry_t
48 0 : #define DEQUE_MAX 4096UL
49 : #include "../../util/tmpl/fd_deque.c"
50 :
51 : /* ---- History Buffers ------------------------------------------------ */
52 :
53 0 : #define FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS (10L)
54 0 : #define FD_GUI_TPS_HISTORY_SAMPLE_CNT (300UL)
55 :
56 0 : #define FD_GUI_PROGCACHE_HISTORY_CNT (600UL) /* 60s / 100ms */
57 :
58 0 : #define FD_GUI_TURBINE_RECV_TIMESTAMPS (750UL)
59 :
60 : /* One use case for tracking ingress shred slot is to estimate when we
61 : have caught up to the tip of the blockchain. A naive approach would
62 : be to track the maximum seen slot.
63 :
64 : maximum_seen_slot = fd_ulong_max( maximum_seen_slot, new_slot_from_shred_tile );
65 :
66 : Unfortunately, this doesn't always work because a validator can send
67 : a slot number that is arbitrarily large on a false fork. Also, these
68 : shreds can be for a repair response, which can be arbitrarily small.
69 :
70 : The prospects here seem bleak, but not all hope is lost! We know
71 : that for a sufficiently large historical time window there is a high
72 : probability that at least some of the slots we observe will be valid
73 : recent turbine slots. For a sufficiently small window there is a high
74 : probability that all the observed shred slots are non-malicious (i.e.
75 : not arbitrarily large).
76 :
77 : In practice shred slots are almost always non-malicious. We can keep
78 : a history of the 12 largest slots we've seen in the past 4.8 seconds.
79 : We'll consider the "tip" of the blockchain to be the maximum slot in
80 : our history. This way, if we receive maliciously large slot number,
81 : it will be evicted after 4.8 seconds. If we receive a small slot from
82 : a repair response it will be ignored because we've seen other larger
83 : slots, meaning that our estimate is eventually consistent. For
84 : monitoring purposes this is sufficient.
85 :
86 : The worst case scenario is that this validator receives an incorrect
87 : shred slot slot more than once every 3 leader rotations. Before the
88 : previous incorrect slot is evicted from the history, a new one takes
89 : it's place and we wouldn't never get a correct estimate of the tip of
90 : the chain. We also would indefinitely think that that we haven't
91 : caught up. This would require the chain having perpetually malicious
92 : leaders with adjacent rotations. If this happens, Solana has bigger
93 : problems. */
94 0 : #define FD_GUI_TURBINE_SLOT_HISTORY_SZ ( 12UL )
95 :
96 : /* Like the turbine slot, the latest repair slot can also swing to
97 : arbitrarily large values due to a malicious fork switch. The gui
98 : provides the same guarantees for freshness and accuracy. This
99 : history is somewhat larger to handle the increased repair bandwidth
100 : during catch up. */
101 0 : #define FD_GUI_REPAIR_SLOT_HISTORY_SZ ( 512UL )
102 :
103 : /* FD_GUI_*_CATCH_UP_HISTORY_SZ is the capacity of the record of slots
104 : seen from repair or turbine during the catch up stage at startup.
105 : These buffers are run-length encoded, so they will typically be very
106 : small. The worst-case scenario is unbounded, so bounds here are
107 : determined heuristically. */
108 0 : #define FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ (4096UL)
109 0 : #define FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ (4096UL)
110 :
111 : /* FD_GUI_SHREDS_HISTORY_SZ the number of shred events in our historical
112 : shred store. Shred events here belong to finalized slots which means
113 : we won't record any additional shred updates for these slots.
114 :
115 : All shred events for a given slot will be places in a contiguous
116 : chunk in the array, and the bounding indices are stored in the
117 : slot history. Within a slot chunk, shred events are ordered in the
118 : order they were recorded by the gui tile.
119 :
120 : Ideally, we have enough space to store an epoch's worth of events,
121 : but we are limited by realistic memory consumption. Instead, we pick
122 : bound heuristically. */
123 : #define FD_GUI_SHREDS_HISTORY_SZ (432000UL*2000UL*4UL / 12UL)
124 :
125 0 : #define FD_GUI_SLOT_RANKINGS_SZ (100UL)
126 0 : #define FD_GUI_SLOT_RANKING_TYPE_ASC (0)
127 0 : #define FD_GUI_SLOT_RANKING_TYPE_DESC (1)
128 :
129 : /* ---- Boot ----------------------------------------------------------- */
130 :
131 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_JOINING_GOSSIP (1)
132 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_FULL_SNAPSHOT (2)
133 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_INCREMENTAL_SNAPSHOT (3)
134 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY (4)
135 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_CATCHING_UP (5)
136 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_RUNNING (6)
137 :
138 0 : #define FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX (0UL)
139 0 : #define FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX (1UL)
140 0 : #define FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT (2UL)
141 :
142 0 : #define FD_GUI_SLOT_LEVEL_INCOMPLETE (0)
143 0 : #define FD_GUI_SLOT_LEVEL_COMPLETED (1)
144 0 : #define FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED (2) /* "notarized" under Alpenglow */
145 0 : #define FD_GUI_SLOT_LEVEL_ROOTED (3)
146 0 : #define FD_GUI_SLOT_LEVEL_FINALIZED (4)
147 :
148 : /* Strength of the notarization and finalization proofs known for a
149 : block.*/
150 :
151 0 : #define FD_GUI_AG_NOTAR_NONE ((uchar)0)
152 0 : #define FD_GUI_AG_NOTAR_FALLBACK ((uchar)1)
153 0 : #define FD_GUI_AG_NOTAR_REGULAR ((uchar)2) /* supersedes fallback */
154 :
155 0 : #define FD_GUI_AG_FINAL_NONE ((uchar)0)
156 0 : #define FD_GUI_AG_FINAL_IMPLICIT ((uchar)1) /* an ancestor of a finalized block */
157 0 : #define FD_GUI_AG_FINAL_SLOW ((uchar)2) /* supersedes implicit */
158 0 : #define FD_GUI_AG_FINAL_FAST ((uchar)3) /* supersedes slow */
159 :
160 : /* Whether our ordinary notarize or skip vote for a slot made it into
161 : the reward certificate. */
162 :
163 0 : #define FD_GUI_VOTE_REWARDED_UNKNOWN ((uchar)0)
164 0 : #define FD_GUI_VOTE_REWARDED_YES ((uchar)1)
165 0 : #define FD_GUI_VOTE_REWARDED_NO ((uchar)2)
166 :
167 0 : #define FD_GUI_IS_VOTER_UNKNOWN ((uchar)0)
168 0 : #define FD_GUI_IS_VOTER_YES ((uchar)1)
169 0 : #define FD_GUI_IS_VOTER_NO ((uchar)2)
170 :
171 : /* Under Tower, NOTARIZED isn't used and FINALIZED is a catch-all for
172 : skipped. UNKNOWN is used while fork choice has not resolved a
173 : replayed slot. */
174 0 : #define FD_GUI_SKIP_STATUS_UNKNOWN ((uchar)0)
175 0 : #define FD_GUI_SKIP_STATUS_NOT_SKIPPED ((uchar)1)
176 0 : #define FD_GUI_SKIP_STATUS_NOTARIZED ((uchar)2)
177 0 : #define FD_GUI_SKIP_STATUS_FINALIZED ((uchar)3)
178 :
179 0 : #define FD_GUI_TXN_FLAGS_STARTED ( 1U)
180 0 : #define FD_GUI_TXN_FLAGS_ENDED ( 2U)
181 0 : #define FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE ( 4U)
182 0 : #define FD_GUI_TXN_FLAGS_FROM_BUNDLE ( 8U)
183 0 : #define FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK (16U)
184 :
185 0 : #define FD_GUI_VOTE_STATE_NON_VOTING (0)
186 0 : #define FD_GUI_VOTE_STATE_VOTING (1)
187 0 : #define FD_GUI_VOTE_STATE_DELINQUENT (2)
188 :
189 : #define FD_GUI_MAX_VOTE_DISTANCE (512UL)
190 :
191 : #define FD_GUI_LANDED_VOTE_MAX (4096UL)
192 :
193 0 : #define FD_GUI_SLOT_SHRED_REPAIR_REQUEST (0UL)
194 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_TURBINE (1UL)
195 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_REPAIR (2UL)
196 0 : #define FD_GUI_SLOT_SHRED_SHRED_REPLAY_EXEC_DONE (3UL)
197 0 : #define FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE (4UL)
198 : /* #define FD_GUI_SLOT_SHRED_SHRED_REPLAY_EXEC_START (5UL) // UNUSED */
199 0 : #define FD_GUI_SLOT_SHRED_SHRED_PUBLISHED (6UL)
200 :
201 : struct fd_gui_tile_timers {
202 : long sample_time_nanos; /* wallclock ns this sample was taken; identical across the per-tile records. */
203 : ulong tile_idx; /* global tile index into topo->tiles. */
204 : ulong timers[ FD_METRICS_ENUM_TILE_REGIME_CNT ];
205 : ulong sched_timers[ FD_METRICS_ENUM_CPU_REGIME_CNT ];
206 :
207 : int in_backp;
208 : ushort last_cpu;
209 : uchar status;
210 : ulong heartbeat;
211 : ulong backp_cnt;
212 : ulong nvcsw;
213 : ulong nivcsw;
214 : ulong minflt;
215 : ulong majflt;
216 : ulong interrupts;
217 : ulong tlb_shootdowns;
218 : ulong timer_ticks;
219 : };
220 :
221 : typedef struct fd_gui_tile_timers fd_gui_tile_timers_t;
222 :
223 : struct fd_gui_tile_timers_hist {
224 : long sample_time_nanos;
225 : ushort tile_idx;
226 : uchar alive;
227 : uchar in_backp;
228 : ushort timers[ FD_METRICS_ENUM_TILE_REGIME_CNT ];
229 : ushort sched_timers[ FD_METRICS_ENUM_CPU_REGIME_CNT ];
230 : ushort idle_ratio;
231 : ushort last_cpu;
232 : ulong backp_msgs;
233 : ulong nvcsw;
234 : ulong nivcsw;
235 : ulong minflt;
236 : ulong majflt;
237 : ulong interrupts;
238 : ulong tlb_shootdowns;
239 : ulong timer_ticks;
240 : };
241 :
242 : typedef struct fd_gui_tile_timers_hist fd_gui_tile_timers_hist_t;
243 :
244 : struct fd_gui_scheduler_counts {
245 : long sample_time_ns;
246 : ulong regular;
247 : ulong votes;
248 : ulong conflicting;
249 : ulong bundles;
250 : };
251 :
252 : typedef struct fd_gui_scheduler_counts fd_gui_scheduler_counts_t;
253 :
254 : struct fd_gui_network_stats {
255 : /* total bytes accumulated */
256 : struct {
257 : ulong turbine;
258 : ulong gossip;
259 : ulong tpu;
260 : ulong repair;
261 : ulong rserve;
262 : ulong metric;
263 : ulong votor;
264 : } in, out;
265 : };
266 :
267 : typedef struct fd_gui_network_stats fd_gui_network_stats_t;
268 :
269 : struct fd_gui_accounts_stats {
270 : /* Raw counters/gauges read from tile metric pages. Stored so we can
271 : compute deltas (and per-second rates) against a previous snapshot. */
272 : long sample_time_nanos;
273 :
274 : /* Disk (gauges from accdb tile). */
275 : ulong accounts_total;
276 : ulong accounts_capacity;
277 : ulong disk_allocated_bytes;
278 : ulong disk_current_bytes;
279 : ulong disk_used_bytes;
280 :
281 : /* Compaction (gauges + counters from accdb tile). */
282 : ulong in_compaction;
283 : ulong compactions_requested;
284 : ulong compactions_completed;
285 : ulong accounts_relocated_bytes;
286 :
287 : /* Cache occupancy (gauges from accdb tile, per class). */
288 : ulong cache_class_used [ FD_ACCDB_CACHE_CLASS_CNT ];
289 : ulong cache_class_max [ FD_ACCDB_CACHE_CLASS_CNT ];
290 : ulong cache_class_reserved [ FD_ACCDB_CACHE_CLASS_CNT ];
291 : /* Preeviction thresholds, expressed as used-slot counts directly
292 : comparable to cache_class_used / cache_class_max. */
293 : ulong cache_class_target_used [ FD_ACCDB_CACHE_CLASS_CNT ];
294 : ulong cache_class_low_water_used [ FD_ACCDB_CACHE_CLASS_CNT ];
295 :
296 : /* Aggregate counters summed across all accdb consumer tiles. */
297 : ulong acquired; /* total acquires */
298 : ulong acquired_writable; /* writable subset of acquires (RW tiles only) */
299 : ulong acquired_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* acquires attributed to a class (RW tiles only) */
300 : ulong acquired_writable_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* writable acquires attributed to a class (RW tiles only) */
301 : ulong not_found_per_class [ FD_ACCDB_CACHE_CLASS_CNT ]; /* misses, per class */
302 : ulong evicted_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
303 : ulong preevicted_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
304 : ulong committed_new_per_class [ FD_ACCDB_CACHE_CLASS_CNT ];
305 : ulong committed_overwrite_per_class[ FD_ACCDB_CACHE_CLASS_CNT ];
306 :
307 : /* IO counters. bytes_written/read_ops/write_ops are summed across
308 : all consumer tiles; bytes_written_accdb is the bytes written by
309 : the accdb tile itself (background preevict + compaction), used to
310 : compute the prewrite ratio. */
311 : ulong bytes_read;
312 : ulong bytes_copied;
313 : ulong bytes_written;
314 : ulong bytes_written_accdb;
315 : ulong read_ops;
316 : ulong write_ops;
317 : };
318 :
319 : typedef struct fd_gui_accounts_stats fd_gui_accounts_stats_t;
320 :
321 : struct fd_gui_turbine_slot {
322 : ulong slot;
323 : long timestamp;
324 : };
325 :
326 : typedef struct fd_gui_turbine_slot fd_gui_turbine_slot_t;
327 :
328 : struct __attribute__((packed)) fd_gui_slot_history_shred_event {
329 : long timestamp;
330 : uint slot;
331 : ushort shred_idx;
332 : uchar event;
333 : };
334 :
335 : typedef struct fd_gui_slot_history_shred_event fd_gui_slot_history_shred_event_t;
336 :
337 : struct __attribute__((packed)) fd_gui_slot {
338 : ulong slot; /* this record's slot number. */
339 : ulong bank_seq; /* this block's fork discriminator (the record's own key bank_seq).*/
340 : ulong parent_bank_seq; /* parent block's fork discriminator (ULONG_MAX if unknown) */
341 : long completed_time; /* slot completion wallclock ns (LONG_MAX if unknown) */
342 : uint shred_cnt; /* slot->shred_cnt at completion */
343 : fd_hash_t block_hash; /* block hash of the slot */
344 : uchar mine:1; /* 1 if this was our leader slot */
345 : uchar is_voter:2; /* one of FD_GUI_IS_VOTER_* */
346 : uint vote_success; /* successful vote txn count (UINT_MAX if unknown) */
347 : uint vote_failed; /* failed vote txn count (UINT_MAX if unknown) */
348 : uint nonvote_success; /* successful nonvote txn count (UINT_MAX if unknown) */
349 : uint nonvote_failed; /* failed nonvote txn count (UINT_MAX if unknown) */
350 : uchar vote_latency_exact; /* our vote latency minus skipped slots on the landed fork, or FD_GUI_VOTE_LATENCY_NOT_VOTED if no vote landed. */
351 : uint max_compute_units;/* block compute unit limit (UINT_MAX if unknown) */
352 : uint compute_units; /* block compute units consumed (UINT_MAX if unknown) */
353 : ulong transaction_fee; /* total transaction fee (ULONG_MAX if unknown) */
354 : ulong priority_fee; /* total priority fee (ULONG_MAX if unknown) */
355 : ulong tips; /* total tips (ULONG_MAX if unknown) */
356 : ulong parent_slot; /* parent (on-fork) slot number (ULONG_MAX if unknown) */
357 : long parent_completed_time; /* parent block completion wallclock ns (LONG_MAX if unknown) */
358 : ulong vote_slot; /* most recent slot we had landed a vote for as of this slot's replay (ULONG_MAX if unknown) */
359 : uchar skip; /* one of FD_GUI_SKIP_STATUS_* */
360 : uchar level; /* one of FD_GUI_SLOT_LEVEL_* */
361 : uchar notarization_kind; /* Alpenglow: one of FD_GUI_AG_NOTAR_* */
362 : uchar finalization_kind; /* Alpenglow: one of FD_GUI_AG_FINAL_* */
363 : uchar vote_rewarded; /* Alpenglow: one of FD_GUI_VOTE_REWARDED_* */
364 : };
365 :
366 : typedef struct fd_gui_slot fd_gui_slot_t;
367 :
368 : static inline void
369 : fd_gui_slot_set_voter_state( fd_gui_slot_t * slot,
370 0 : uchar state ) {
371 0 : slot->is_voter = FD_GUI_IS_VOTER_UNKNOWN;
372 0 : if( state==FD_GUI_IS_VOTER_YES ) slot->is_voter = FD_GUI_IS_VOTER_YES;
373 0 : else if( state==FD_GUI_IS_VOTER_NO ) slot->is_voter = FD_GUI_IS_VOTER_NO;
374 0 : }
375 :
376 : /* Alpenglow certificates name a block by (slot, block_id), while slot
377 : records are keyed by (slot, bank_seq). Certificates are buffered as
378 : they arrive before replay completes their block. They are then
379 : eagerly joined by their block_id. The buffer covers max_live_slots
380 : so replay can consume any block retained by the execution pipeline. */
381 :
382 : struct fd_gui_ag_slot {
383 : ulong slot; /* ULONG_MAX when this entry is empty */
384 : fd_hash_t block_id; /* Double merkle root of the block data. { 0 } by default. */
385 : ulong bank_seq; /* ULONG_MAX until this block completes replay */
386 : uchar notarization_kind; /* one of FD_GUI_AG_NOTAR_* */
387 : uchar finalization_kind; /* one of FD_GUI_AG_FINAL_* */
388 : uchar skip; /* one of FD_GUI_SKIP_STATUS_* */
389 : };
390 :
391 : typedef struct fd_gui_ag_slot fd_gui_ag_slot_t;
392 :
393 : struct fd_gui_slot_ranking {
394 : ulong slot;
395 : ulong value;
396 : int type;
397 : };
398 : typedef struct fd_gui_slot_ranking fd_gui_slot_ranking_t;
399 :
400 : struct fd_gui_slot_rankings {
401 : fd_gui_slot_ranking_t largest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
402 : fd_gui_slot_ranking_t largest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
403 : fd_gui_slot_ranking_t largest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
404 : fd_gui_slot_ranking_t largest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
405 : fd_gui_slot_ranking_t largest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
406 : fd_gui_slot_ranking_t largest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
407 : fd_gui_slot_ranking_t largest_rewards_per_cu [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
408 : fd_gui_slot_ranking_t smallest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
409 : fd_gui_slot_ranking_t smallest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
410 : fd_gui_slot_ranking_t smallest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
411 : fd_gui_slot_ranking_t smallest_rewards_per_cu[ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
412 : fd_gui_slot_ranking_t smallest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
413 : fd_gui_slot_ranking_t smallest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
414 : fd_gui_slot_ranking_t smallest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
415 : };
416 :
417 : typedef struct fd_gui_slot_rankings fd_gui_slot_rankings_t;
418 :
419 0 : #define FD_GUI_EPOCH_SCHED_CNT ((MAX_SLOTS_PER_EPOCH+FD_EPOCH_SLOTS_PER_ROTATION-1UL)/FD_EPOCH_SLOTS_PER_ROTATION)
420 : #define FD_GUI_EPOCH_PUB_CNT (MAX_STAKE_WEIGHTS)
421 :
422 0 : #define FD_GUI_VOTE_LATENCY_NOT_VOTED ((uchar)(UCHAR_MAX)) /* vote missing */
423 0 : #define FD_GUI_VOTE_LATENCY_MAX ((uchar)(UCHAR_MAX-1UL)) /* largest observable vote latency */
424 :
425 : struct fd_gui_epoch {
426 : ulong epoch;
427 : ulong start_slot;
428 : ulong slot_cnt; /* end_slot = start_slot + slot_cnt - 1 */
429 : long start_time; /* epoch start wallclock ns (LONG_MAX if unknown) */
430 : long end_time; /* epoch end wallclock ns (LONG_MAX if unknown) */
431 : long target_slot_duration_ns; /* protocol target slot duration */
432 : ulong my_total_slots; /* our leader slots seen this epoch */
433 : ulong my_skipped_slots; /* our skipped leader slots this epoch */
434 : ulong rankings_slot; /* one more than the largest slot processed into rankings */
435 : fd_gui_slot_rankings_t rankings [ 1 ]; /* global slot rankings */
436 : fd_gui_slot_rankings_t my_rankings[ 1 ]; /* my slots only */
437 :
438 : uchar latency_exact[ MAX_SLOTS_PER_EPOCH ]; /* skip-discounted latency or FD_GUI_VOTE_LATENCY_* */
439 : uchar is_voter [ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_IS_VOTER_* */
440 : uchar skipped [ MAX_SLOTS_PER_EPOCH ]; /* 1 if the slot was skipped on the rooted fork */
441 : uchar vote_rewarded[ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_VOTE_REWARDED_* */
442 :
443 : fd_epoch_schedule_t epoch_schedule; /* slot<->epoch conversion (fd_slot_to_epoch) */
444 : ulong pub_cnt; /* number of deduped leader pubkeys in pub[] */
445 : ulong stakes_cnt; /* number of entries in stakes[] */
446 : uint sched[ FD_GUI_EPOCH_SCHED_CNT ]; /* rotation -> index into pub[] */
447 : fd_pubkey_t pub[ FD_GUI_EPOCH_PUB_CNT ]; /* deduped leaders */
448 : fd_vote_stake_weight_t stakes[ MAX_STAKE_WEIGHTS ];
449 : };
450 :
451 : typedef struct fd_gui_epoch fd_gui_epoch_t;
452 :
453 : struct fd_gui_ephemeral_slot {
454 : ulong slot; /* ULONG_MAX indicates invalid/evicted */
455 : long timestamp_arrival_nanos;
456 : };
457 : typedef struct fd_gui_ephemeral_slot fd_gui_ephemeral_slot_t;
458 :
459 : struct __attribute__((packed)) fd_gui_store_txn_start {
460 : ulong slot;
461 : ulong bank_seq; /* per-fork bank sequence */
462 : ulong txn_idx; /* per-(bank_seq) transaction index */
463 : uchar signature[ FD_TXN_SIGNATURE_SZ ];
464 : ulong transaction_fee;
465 : ulong priority_fee;
466 : long timestamp_arrival_nanos;
467 : long microblock_start_ns; /* absolute ns (begin tspub) */
468 : uint compute_units_requested;
469 : uint microblock_idx;
470 : uint source_ipv4;
471 : uchar source_tpu;
472 : uchar flags; /* STARTED | IS_SIMPLE_VOTE | FROM_BUNDLE */
473 : };
474 : typedef struct fd_gui_store_txn_start fd_gui_store_txn_start_t;
475 :
476 : struct __attribute__((packed)) fd_gui_store_txn_end {
477 : ulong slot;
478 : ulong bank_seq; /* per-fork bank sequence */
479 : ulong txn_idx;
480 : long timestamp_arrival_nanos;
481 : long microblock_end_ns; /* absolute ns (end tspub) */
482 : fd_txn_ns_dt_t txn_ns_dt; /* relative to microblock_start */
483 : ulong tips;
484 : uint compute_units_consumed;
485 : uint bank_idx;
486 : uint error_code;
487 : uchar flags; /* ENDED | LANDED_IN_BLOCK */
488 : };
489 : typedef struct fd_gui_store_txn_end fd_gui_store_txn_end_t;
490 :
491 : struct fd_gui_slot_txn_join {
492 : fd_gui_store_txn_start_t const * start;
493 : fd_gui_store_txn_end_t const * end;
494 : };
495 :
496 : typedef struct fd_gui_slot_txn_join fd_gui_slot_txn_join_t;
497 :
498 : struct fd_gui_txn_waterfall {
499 : long sample_time_nanos; /* wallclock ns this sample was taken. */
500 : struct {
501 : ulong quic;
502 : ulong udp;
503 : ulong gossip;
504 : ulong block_engine;
505 : ulong pack_cranked;
506 : } in;
507 :
508 : struct {
509 : ulong net_overrun;
510 : ulong quic_overrun;
511 : ulong quic_frag_drop;
512 : ulong quic_abandoned;
513 : ulong tpu_quic_invalid;
514 : ulong tpu_udp_invalid;
515 : ulong verify_overrun;
516 : ulong verify_parse;
517 : ulong verify_failed;
518 : ulong verify_duplicate;
519 : ulong dedup_duplicate;
520 : ulong resolv_lut_failed;
521 : ulong resolv_expired;
522 : ulong resolv_ancient;
523 : ulong resolv_no_ledger;
524 : ulong resolv_retained;
525 : ulong pack_invalid;
526 : ulong pack_invalid_bundle;
527 : ulong pack_expired;
528 : ulong pack_already_executed;
529 : ulong pack_retained;
530 : ulong pack_wait_full;
531 : ulong pack_leader_slow;
532 : ulong bank_invalid;
533 : ulong bank_nonce_already_advanced;
534 : ulong bank_nonce_advance_failed;
535 : ulong bank_nonce_wrong_blockhash;
536 : ulong block_success;
537 : ulong block_fail;
538 : } out;
539 : };
540 :
541 : typedef struct fd_gui_txn_waterfall fd_gui_txn_waterfall_t;
542 :
543 : struct __attribute__((packed)) fd_gui_leader_slot {
544 : ulong slot; /* this record's slot number (the record's own key). */
545 : ulong bank_seq; /* this block's fork discriminator (the record's own key bank_seq). */
546 : long leader_start_time; /* wallclock ns we became leader */
547 : long leader_end_time; /* wallclock ns we stopped being leader */
548 : fd_hash_t block_hash; /* block hash of the produced block */
549 : ulong max_microblocks; /* initial max microblocks packable into the slot */
550 : uint microblocks_upper_bound; /* final/exact microblock upper bound */
551 : uint begin_microblocks; /* microblocks started (pack -> bank) */
552 : uint end_microblocks; /* microblocks ended (bank -> poh) */
553 : fd_done_packing_t scheduler_stats[ 1 ]; /* pack "done packing" record (limits, usage, results) */
554 : fd_gui_txn_waterfall_t waterfall_reference[ 1 ];
555 : fd_gui_txn_waterfall_t waterfall[ 1 ];
556 : uchar has_waterfall; /* 1 once waterfall has been finalized */
557 : uchar unbecame_leader; /* 1 if we relinquished leadership for this slot */
558 : };
559 :
560 : typedef struct fd_gui_leader_slot fd_gui_leader_slot_t;
561 :
562 : struct fd_gui_tile_stats {
563 : long sample_time_nanos;
564 :
565 : ulong net_in_rx_bytes; /* Number of bytes received by the net or sock tile*/
566 : ulong quic_conn_cnt; /* Number of active QUIC connections */
567 : fd_histf_t bundle_rx_delay_hist; /* Histogram of bundle rx delay */
568 : ulong bundle_rtt_smoothed_nanos; /* RTT (nanoseconds) moving average */
569 : ulong verify_drop_cnt; /* Number of transactions dropped by verify tiles */
570 : ulong verify_total_cnt; /* Number of transactions received by verify tiles */
571 : ulong dedup_drop_cnt; /* Number of transactions dropped by dedup tile */
572 : ulong dedup_total_cnt; /* Number of transactions received by dedup tile */
573 : ulong pack_buffer_cnt; /* Number of buffered transactions in the pack tile */
574 : ulong pack_buffer_capacity; /* Total size of the pack transaction buffer */
575 : ulong bank_txn_exec_cnt; /* Number of transactions processed by the bank tile */
576 : ulong net_out_tx_bytes; /* Number of bytes sent by the net or sock tile */
577 : };
578 :
579 : typedef struct fd_gui_tile_stats fd_gui_tile_stats_t;
580 :
581 :
582 : struct fd_gui_boot_progress {
583 : uchar phase;
584 : long joining_gossip_time_nanos;
585 : struct {
586 : ulong slot;
587 : uint peer_addr;
588 : ushort peer_port;
589 : ulong total_bytes_compressed;
590 : long reset_time_nanos; /* UNIX nanosecond timestamp */
591 : long sample_time_nanos;
592 : ulong reset_cnt;
593 :
594 : ulong read_bytes_compressed;
595 : char read_path[ PATH_MAX+30UL ]; /* URL or filesystem path. 30 is fd_cstr_nlen( "https://255.255.255.255:12345/", ULONG_MAX ) */
596 :
597 : ulong decompress_bytes_decompressed;
598 : ulong decompress_bytes_compressed;
599 :
600 : ulong insert_bytes_decompressed;
601 : char insert_path[ PATH_MAX ];
602 : ulong insert_accounts_current;
603 :
604 : ulong snapwr_in_bytes_decompressed;
605 : ulong snapwr_out_bytes_decompressed;
606 : ulong snapwr_accounts_current;
607 : } loading_snapshot[ FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT ];
608 :
609 : ulong wfs_total_stake;
610 : ulong wfs_connected_stake;
611 : ulong wfs_total_peers;
612 : ulong wfs_connected_peers;
613 : ulong wfs_attempt;
614 :
615 : long catching_up_time_nanos;
616 : ulong catching_up_first_replay_slot;
617 : };
618 :
619 : typedef struct fd_gui_boot_progress fd_gui_boot_progress_t;
620 :
621 : #define FD_GUI_SNAPSV_PENDING_MAX (16384UL)
622 : #define FD_GUI_SNAPSV_PUBLISH_INTERVAL_NANOS ( 50L*1000L*1000L)
623 :
624 : struct fd_gui_snapsv_pending {
625 : fd_snapsv_msg_snap_t msg;
626 : long sample_time_nanos;
627 : int closed;
628 : };
629 :
630 : typedef struct fd_gui_snapsv_pending fd_gui_snapsv_pending_t;
631 :
632 : /* Triangular-weighted moving window over per-snap deltas. Snap
633 : cadence is ~100ms, window is FD_GUI_ACCDB_WIN_SAMPLES samples
634 : (~5s). The output rate for any metric is:
635 :
636 : rate = sum( w[i] * delta[i] ) / sum( w[i] * dt[i] )
637 :
638 : where w[i] is the triangular weight (newest sample heaviest,
639 : oldest weight=1). This is fully caught up to a new rate after
640 : the window length but smoother than a boxcar (no cliff edge as
641 : samples age out). */
642 0 : #define FD_GUI_ACCDB_WIN_SAMPLES 50UL
643 :
644 : /* Per-partition triangular-weighted windows for read/write rates.
645 : Sized to match the accdb partition pool ceiling (8192). Rates
646 : are derived in fd_gui_printf via fd_gui_accdb_weighted_rate. */
647 0 : #define FD_GUI_MAX_PARTITIONS 8192UL
648 :
649 0 : #define FD_GUI_ACCDB_EMA_HALF_LIFE_NS (600L*1000L*1000L*1000L) /* 10 minutes */
650 :
651 : /* Per-tile accdb stats. At init we walk the topology and assign a
652 : slot to each tile that uses the account database (execle, execrp,
653 : replay, tower, rpc, resolv, snapwr). Each slot keeps cumulative
654 : previous values for delta computation and a triangular-weighted
655 : delta ring (same cadence / weighting as the aggregate rings). */
656 : #define FD_GUI_MAX_ACCDB_TILES 64UL
657 :
658 : /* Tile kinds. Determines which subset of metrics to read. */
659 0 : #define FD_GUI_ACCDB_TILE_KIND_RW 0 /* execle, execrp, replay, tower */
660 0 : #define FD_GUI_ACCDB_TILE_KIND_RO 1 /* rpc, resolv */
661 0 : #define FD_GUI_ACCDB_TILE_KIND_SNAPWR 2 /* snapwr (direct disk writer during snapshot load) */
662 0 : #define FD_GUI_ACCDB_TILE_KIND_ACCDB 3 /* accdb tile itself (prewrite + compaction writes) */
663 :
664 : /* 60s-history rings for the per-tile sparkline. Each bucket is the
665 : sum of per-snap deltas that fell into that bucket window.
666 : index 0 = current bucket (in-flight), older buckets follow. When
667 : a bucket interval elapses we shift right (older buckets drop off
668 : the end) and start a new index-0 bucket. 240 buckets x 250ms =
669 : 60 second window. */
670 0 : #define FD_GUI_ACCDB_SPARKLINE_SAMPLES 240UL
671 0 : #define FD_GUI_ACCDB_SPARKLINE_BUCKET_NS 250000000L
672 :
673 : struct fd_gui_accdb_stats {
674 : ulong accdb_win_idx; /* next write index */
675 : ulong accdb_win_count; /* samples filled, capped at FD_GUI_ACCDB_WIN_SAMPLES */
676 : long accdb_win_dt_nanos [ FD_GUI_ACCDB_WIN_SAMPLES ];
677 :
678 : /* Aggregate delta rings (units per snap). */
679 : ulong agg_acquired_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
680 : ulong agg_acquired_writable_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
681 : ulong agg_bytes_read_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
682 : ulong agg_bytes_copied_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
683 : ulong agg_bytes_written_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
684 : ulong agg_bytes_written_accdb_win[FD_GUI_ACCDB_WIN_SAMPLES ];
685 : ulong agg_read_ops_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
686 : ulong agg_write_ops_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
687 : ulong agg_relocated_bytes_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
688 : ulong agg_misses_win [ FD_GUI_ACCDB_WIN_SAMPLES ];
689 :
690 : /* Per-class delta rings (units per snap). */
691 : ulong class_acq_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
692 : ulong class_acq_wr_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
693 : ulong class_not_found_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
694 : ulong class_evicted_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
695 : ulong class_preevicted_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
696 : ulong class_commit_new_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
697 : ulong class_commit_over_win [ FD_ACCDB_CACHE_CLASS_CNT ][ FD_GUI_ACCDB_WIN_SAMPLES ];
698 :
699 : ulong partition_cnt; /* live count from accdb_shmem; <= FD_GUI_MAX_PARTITIONS */
700 : ulong partition_read_ops_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
701 : ulong partition_bytes_read_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
702 : ulong partition_write_ops_win [ FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
703 : ulong partition_bytes_written_win[FD_GUI_MAX_PARTITIONS ][ FD_GUI_ACCDB_WIN_SAMPLES ];
704 :
705 : /* Per-partition snapshots (most recent values, for non-rate fields:
706 : offset, layer, write_offset, bytes_freed, ticks, compaction state). */
707 : fd_accdb_shmem_partition_info_t partitions[ FD_GUI_MAX_PARTITIONS ];
708 :
709 : /* Cumulative counters from the previous snap for delta computation. */
710 : ulong partition_prev_read_ops [ FD_GUI_MAX_PARTITIONS ];
711 : ulong partition_prev_bytes_read [ FD_GUI_MAX_PARTITIONS ];
712 : ulong partition_prev_write_ops [ FD_GUI_MAX_PARTITIONS ];
713 : ulong partition_prev_bytes_written[FD_GUI_MAX_PARTITIONS ];
714 :
715 : /* Per-tier (i.e. hot, warm, cold) EMAs (10 min half-life) used to project when the next
716 : compaction will trigger. */
717 : long tier_sample_nanos;
718 : fd_gui_ema_t tier_fill_bps_ema[ FD_ACCDB_COMPACTION_LAYER_CNT ];
719 : fd_gui_ema_t tier_free_bps_ema[ FD_ACCDB_COMPACTION_LAYER_CNT ];
720 : double next_compaction_remaining_secs;
721 : ulong next_compaction_partition_idx;
722 :
723 : ulong accdb_tile_cnt;
724 : ushort accdb_tile_topo_idx [ FD_GUI_MAX_ACCDB_TILES ]; /* index into topo->tiles */
725 : uchar accdb_tile_kind [ FD_GUI_MAX_ACCDB_TILES ];
726 :
727 : /* Most-recent cumulative values per tile, plus the snapshot from
728 : the previous snap for delta computation. */
729 : ulong tile_cur_acquired [ FD_GUI_MAX_ACCDB_TILES ];
730 : ulong tile_cur_acquired_writable [ FD_GUI_MAX_ACCDB_TILES ];
731 : ulong tile_cur_bytes_read [ FD_GUI_MAX_ACCDB_TILES ];
732 : ulong tile_cur_bytes_copied [ FD_GUI_MAX_ACCDB_TILES ];
733 : ulong tile_cur_bytes_written [ FD_GUI_MAX_ACCDB_TILES ];
734 : ulong tile_cur_read_ops [ FD_GUI_MAX_ACCDB_TILES ];
735 : ulong tile_cur_write_ops [ FD_GUI_MAX_ACCDB_TILES ];
736 : ulong tile_cur_misses [ FD_GUI_MAX_ACCDB_TILES ];
737 : ulong tile_cur_evicted [ FD_GUI_MAX_ACCDB_TILES ];
738 : ulong tile_cur_committed [ FD_GUI_MAX_ACCDB_TILES ];
739 : ulong tile_cur_acquire_calls [ FD_GUI_MAX_ACCDB_TILES ];
740 : uchar tile_cur_status [ FD_GUI_MAX_ACCDB_TILES ]; /* 1=running, 2=shutdown */
741 :
742 : ulong tile_prev_acquired [ FD_GUI_MAX_ACCDB_TILES ];
743 : ulong tile_prev_acquired_writable[ FD_GUI_MAX_ACCDB_TILES ];
744 : ulong tile_prev_bytes_read [ FD_GUI_MAX_ACCDB_TILES ];
745 : ulong tile_prev_bytes_copied [ FD_GUI_MAX_ACCDB_TILES ];
746 : ulong tile_prev_bytes_written [ FD_GUI_MAX_ACCDB_TILES ];
747 : ulong tile_prev_read_ops [ FD_GUI_MAX_ACCDB_TILES ];
748 : ulong tile_prev_write_ops [ FD_GUI_MAX_ACCDB_TILES ];
749 : ulong tile_prev_misses [ FD_GUI_MAX_ACCDB_TILES ];
750 : ulong tile_prev_evicted [ FD_GUI_MAX_ACCDB_TILES ];
751 : ulong tile_prev_committed [ FD_GUI_MAX_ACCDB_TILES ];
752 : ulong tile_prev_acquire_calls [ FD_GUI_MAX_ACCDB_TILES ];
753 :
754 : /* Per-tile delta rings. */
755 : ulong tile_acquired_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
756 : ulong tile_acquired_writable_win[ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
757 : ulong tile_bytes_read_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
758 : ulong tile_bytes_copied_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
759 : ulong tile_bytes_written_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
760 : ulong tile_read_ops_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
761 : ulong tile_write_ops_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
762 : ulong tile_misses_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
763 : ulong tile_evicted_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
764 : ulong tile_committed_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
765 : ulong tile_acquire_calls_win [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_WIN_SAMPLES ];
766 :
767 : long tile_sparkline_bucket_start_nanos [ FD_GUI_MAX_ACCDB_TILES ];
768 : ulong tile_sparkline_acq_bucket [ FD_GUI_MAX_ACCDB_TILES ];
769 : ulong tile_sparkline_acq_wr_bucket [ FD_GUI_MAX_ACCDB_TILES ];
770 : /* Per-second rates (units/second) for the last N completed buckets.
771 : Newest at index 0, oldest at the end. Filled lazily as snaps
772 : complete each bucket interval. */
773 : double tile_sparkline_acq_history [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_SPARKLINE_SAMPLES ];
774 : double tile_sparkline_acq_wr_history [ FD_GUI_MAX_ACCDB_TILES ][ FD_GUI_ACCDB_SPARKLINE_SAMPLES ];
775 : ulong tile_sparkline_count [ FD_GUI_MAX_ACCDB_TILES ]; /* completed buckets, capped at FD_GUI_ACCDB_SPARKLINE_SAMPLES */
776 : };
777 :
778 : typedef struct fd_gui_accdb_stats fd_gui_accdb_stats_t;
779 :
780 : /* fd_gui_summary_t holds the aggregate node-level state rendered by the
781 : "summary" websocket topic and the terminal dashboard. */
782 :
783 : struct fd_gui_summary {
784 : fd_pubkey_t identity_key[ 1 ];
785 : int has_vote_key;
786 : fd_pubkey_t vote_key[ 1 ];
787 : char vote_key_base58[ FD_BASE58_ENCODED_32_SZ ];
788 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
789 :
790 : int is_full_client;
791 : int is_alpenglow;
792 : char const * version;
793 : char const * cluster;
794 : char accounts_database_path[ PATH_MAX ];
795 : char gui_database_path [ PATH_MAX ];
796 :
797 : char wfs_bank_hash[ FD_BASE58_ENCODED_32_SZ ];
798 : ushort expected_shred_version;
799 : int wfs_enabled;
800 :
801 : ulong vote_distance;
802 : int vote_state;
803 :
804 : long startup_time_nanos;
805 :
806 : fd_gui_boot_progress_t boot_progress;
807 : fd_gui_boot_progress_t prev_boot_progress;
808 :
809 : int schedule_strategy;
810 :
811 : ulong identity_account_balance;
812 : ulong vote_account_balance;
813 : ushort vote_commission;
814 : ulong estimated_slot_duration_nanos;
815 :
816 : ulong sock_tile_cnt;
817 : ulong mlx5_tile_cnt;
818 : ulong net_tile_cnt;
819 : ulong quic_tile_cnt;
820 : ulong verify_tile_cnt;
821 : ulong resolh_tile_cnt;
822 : ulong resolv_tile_cnt;
823 : ulong bank_tile_cnt;
824 : ulong execle_tile_cnt;
825 : ulong execrp_tile_cnt;
826 : ulong shred_tile_cnt;
827 :
828 : ulong slot_rooted;
829 : ulong slot_finalized;
830 : ulong slot_notarized;
831 : ulong slot_optimistically_confirmed;
832 : ulong slot_estimated;
833 : ulong slot_caught_up;
834 : ulong slot_repair;
835 : ulong slot_turbine;
836 : ulong slot_reset;
837 : ulong slot_storage;
838 : ulong slot_tower;
839 : ulong slot_tower_bank_seq; /* tracks canonical fork frontier */
840 : ulong slot_voted;
841 : ulong active_fork_cnt;
842 :
843 : struct {
844 : ulong epoch;
845 : ulong skipped;
846 : ulong total;
847 : } skip_rate[ 2 ];
848 :
849 : fd_gui_ephemeral_slot_t slots_max_turbine[ FD_GUI_TURBINE_SLOT_HISTORY_SZ+1UL ];
850 : fd_gui_ephemeral_slot_t slots_max_repair [ FD_GUI_REPAIR_SLOT_HISTORY_SZ +1UL ];
851 :
852 : /* catchup_* is run-length encoded. i.e. adjacent pairs represent
853 : contiguous runs */
854 : ulong catch_up_turbine[ FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ ];
855 : ulong catch_up_turbine_sz;
856 :
857 : ulong catch_up_repair[ FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ ];
858 : ulong catch_up_repair_sz;
859 :
860 : ulong estimated_tps_history_idx;
861 : struct {
862 : ulong vote_failed;
863 : ulong vote_success;
864 : ulong nonvote_success;
865 : ulong nonvote_failed;
866 : } estimated_tps_history[ FD_GUI_TPS_HISTORY_SAMPLE_CNT ];
867 :
868 : fd_gui_network_stats_t network_stats_current[ 1 ];
869 : fd_gui_network_stats_t network_stats_prev [ 1 ];
870 : int network_stats_has_prev;
871 :
872 : /* EMA-smoothed network throughput (bytes/sec) with a 1-second
873 : half-life. */
874 : fd_gui_ema_t ingress_ema[ FD_GUI_NET_PROTO_CNT ];
875 : fd_gui_ema_t egress_ema[ FD_GUI_NET_PROTO_CNT ];
876 : long net_rate_prev_ts;
877 : fd_gui_rate_entry_t * ingress_maxq;
878 : fd_gui_rate_entry_t * egress_maxq;
879 :
880 : fd_gui_accdb_stats_t accdb[ 1 ];
881 :
882 : fd_gui_accounts_stats_t accounts_stats_reference[ 1 ];
883 : fd_gui_accounts_stats_t accounts_stats_current [ 1 ];
884 : int accounts_stats_have_reference;
885 :
886 : fd_gui_txn_waterfall_t txn_waterfall_reference[ 1 ];
887 : fd_gui_txn_waterfall_t txn_waterfall_current [ 1 ];
888 :
889 : fd_gui_tile_stats_t tile_stats_reference[ 1 ];
890 : fd_gui_tile_stats_t tile_stats_current [ 1 ];
891 :
892 : ulong progcache_history_idx;
893 : ulong progcache_hits_history [ FD_GUI_PROGCACHE_HISTORY_CNT ];
894 : ulong progcache_lookups_history[ FD_GUI_PROGCACHE_HISTORY_CNT ];
895 : ulong progcache_hits_1min;
896 : ulong progcache_lookups_1min;
897 :
898 : fd_gui_tile_timers_t tile_timers_reference[ FD_TOPO_MAX_TILES ];
899 : fd_gui_tile_timers_t tile_timers_current [ FD_TOPO_MAX_TILES ];
900 : fd_gui_tile_timers_hist_t tile_timers_packed [ FD_TOPO_MAX_TILES ];
901 :
902 : /* Topo tile indices in display order, built once on init. */
903 : ulong tile[ FD_TOPO_MAX_TILES ];
904 : ulong tile_cnt;
905 : };
906 :
907 : typedef struct fd_gui_summary fd_gui_summary_t;
908 :
909 : struct fd_gui {
910 : fd_http_server_t * http;
911 : fd_topo_t const * topo;
912 : fd_accdb_shmem_t const * accdb_shmem;
913 :
914 : void * db; /* GUI database */
915 : void * hist;
916 :
917 : double tick_per_ns;
918 : ulong tile_cnt;
919 :
920 : long next_sample_1sec;
921 : long next_sample_200millis;
922 : long next_sample_100millis;
923 : long next_sample_50millis;
924 : long next_sample_40millis;
925 : long next_sample_25millis;
926 : long next_sample_10millis;
927 :
928 : int leader_active;
929 : ulong leader_slot_pending;
930 : ulong leader_bank_seq_pending;
931 :
932 : fd_gui_summary_t summary;
933 :
934 : struct {
935 : int valid;
936 : fd_diag_system_resources_t resources;
937 : } system;
938 :
939 : /* Scratch record for the synthesized skipped slots returned by
940 : fd_gui_slot_get_canon_safe. */
941 : fd_gui_slot_t skipped_scratch[ 1 ];
942 :
943 : struct {
944 : ulong slot_cnt;
945 : fd_gui_ag_slot_t * slot; /* [slot_cnt] */
946 : } ag;
947 :
948 : /* used for estimating slot duration */
949 : fd_gui_turbine_slot_t turbine_slots[ FD_GUI_TURBINE_RECV_TIMESTAMPS ];
950 :
951 : /* Reusable scratch for reassembling a single slot's transactions at
952 : query time (fd_gui_printf_slot_transactions_request). */
953 : struct {
954 : ulong max;
955 : fd_gui_store_txn_start_t * starts; /* [max] */
956 : fd_gui_store_txn_end_t * ends; /* [max] */
957 : fd_gui_slot_txn_join_t * joined; /* [max] */
958 : } slot_txn_scratch;
959 :
960 : struct {
961 : ulong landed_slot;
962 : ulong landed_bank_seq;
963 : ulong voted_slot;
964 : } landed_votes[ FD_GUI_LANDED_VOTE_MAX ];
965 : ulong landed_vote_cnt;
966 :
967 : struct {
968 : int has_block_engine;
969 : char name[ 16 ];
970 : char url[ FD_URL_MAX ];
971 : char ip_cstr[ 40 ]; /* IPv4 or IPv6 cstr */
972 : int status;
973 : } block_engine;
974 :
975 : struct {
976 : /* The epoch we are currently in, advanced at epoch_info ingest. */
977 : ulong current_epoch;
978 : ulong stored_epoch_cnt;
979 :
980 : int has_epoch_schedule;
981 : fd_epoch_schedule_t epoch_schedule;
982 :
983 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) lsched_scratch[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_STAKE_WEIGHTS, MAX_SLOTS_PER_EPOCH) ];
984 : fd_vote_stake_weight_t stakes_scratch[ MAX_STAKE_WEIGHTS ];
985 : } epoch;
986 :
987 : fd_gui_peers_ctx_t * peers; /* full-client */
988 :
989 : struct {
990 : ulong leader_shred_cnt; /* A gauge counting the number of leader shreds seen on the SHRED_OUT link. Resets at
991 : the end of a leader slot. This works because leader fecs are published in order. */
992 : ulong leader_shred_slot; /* The slot leader_shred_cnt is currently counting, ULONG_MAX if none. Used to reset
993 : counter after an abandoned/ended slot. This works because leader fecs are published
994 : in order. */
995 :
996 : /* The wallclock-ns timestamp up to which shred events have already
997 : been pushed to clients. */
998 : long broadcast_watermark_ns;
999 : } shreds;
1000 :
1001 : struct {
1002 : ulong pending_cnt;
1003 : fd_gui_snapsv_pending_t pending[ FD_GUI_SNAPSV_PENDING_MAX ];
1004 : } snapsv;
1005 : };
1006 :
1007 : typedef struct fd_gui fd_gui_t;
1008 :
1009 : FD_PROTOTYPES_BEGIN
1010 :
1011 : /* fd_gui_tile_timers_diff computes the compact, display-ready diff of a
1012 : single tile's timers between two raw cumulative samples `prev` and
1013 : `cur`, writing it into `out`. */
1014 :
1015 : void
1016 : fd_gui_tile_timers_diff( fd_gui_tile_timers_hist_t * out,
1017 : fd_gui_tile_timers_t const * prev,
1018 : fd_gui_tile_timers_t const * cur,
1019 : ulong tile_idx,
1020 : long sample_time_nanos );
1021 :
1022 : FD_FN_CONST ulong
1023 : fd_gui_align( void );
1024 :
1025 : ulong
1026 : fd_gui_footprint( ulong tile_cnt,
1027 : ulong max_live_slots,
1028 : ulong max_txn_per_slot );
1029 :
1030 : void *
1031 : fd_gui_new( void * shmem,
1032 : fd_http_server_t * http,
1033 : char const * version,
1034 : char const * cluster,
1035 : uchar const * identity_key,
1036 : int has_vote_key,
1037 : uchar const * vote_key,
1038 : int is_full_client,
1039 : int is_alpenglow,
1040 : ulong max_live_slots,
1041 : ulong max_txn_per_slot,
1042 : int snapshots_enabled,
1043 : int is_voting,
1044 : int schedule_strategy,
1045 : char const * wfs_expected_bank_hash_cstr,
1046 : ushort expected_shred_version,
1047 : char const * accounts_database_path,
1048 : char const * gui_database_path,
1049 : void * db,
1050 : fd_topo_t const * topo,
1051 : fd_accdb_shmem_t const * accdb_shmem,
1052 : long now );
1053 :
1054 : fd_gui_t *
1055 : fd_gui_join( void * shmem );
1056 :
1057 : void
1058 : fd_gui_set_identity( fd_gui_t * gui,
1059 : uchar const * identity_pubkey );
1060 :
1061 : void
1062 : fd_gui_handle_diag_snapshot( fd_gui_t * gui,
1063 : void const * data,
1064 : ulong data_sz );
1065 :
1066 : void
1067 : fd_gui_ws_open( fd_gui_t * gui,
1068 : ulong conn_id,
1069 : long now );
1070 :
1071 : int
1072 : fd_gui_ws_message( fd_gui_t * gui,
1073 : ulong ws_conn_id,
1074 : uchar const * data,
1075 : ulong data_len );
1076 :
1077 : void
1078 : fd_gui_became_leader( fd_gui_t * gui,
1079 : ulong slot,
1080 : long start_time_nanos,
1081 : long end_time_nanos,
1082 : ulong max_compute_units,
1083 : ulong max_microblocks,
1084 : ulong bank_seq );
1085 :
1086 : void
1087 : fd_gui_unbecame_leader( fd_gui_t * gui,
1088 : ulong _slot,
1089 : fd_done_packing_t const * done_packing );
1090 :
1091 : void
1092 : fd_gui_done_draining( fd_gui_t * gui,
1093 : long now );
1094 :
1095 : void
1096 : fd_gui_microblock_execution_begin( fd_gui_t * gui,
1097 : long tspub_ns,
1098 : ulong _slot,
1099 : fd_txn_e_t * txns,
1100 : ulong txn_cnt,
1101 : uint microblock_idx,
1102 : ulong pack_txn_idx,
1103 : ulong bank_seq,
1104 : long now );
1105 :
1106 : void
1107 : fd_gui_microblock_execution_end( fd_gui_t * gui,
1108 : long tspub_ns,
1109 : ulong bank_idx,
1110 : ulong _slot,
1111 : ulong txn_cnt,
1112 : fd_txn_p_t * txns,
1113 : ulong pack_txn_idx,
1114 : fd_txn_ns_dt_t txn_ns_dt,
1115 : ulong tips,
1116 : ulong bank_seq,
1117 : long now );
1118 :
1119 : int
1120 : fd_gui_poll( fd_gui_t * gui, long now );
1121 :
1122 : void
1123 : fd_gui_handle_block_engine_update( fd_gui_t * gui,
1124 : fd_bundle_block_engine_update_t const * update );
1125 :
1126 : void
1127 : fd_gui_handle_shred( fd_gui_t * gui,
1128 : ulong slot,
1129 : ulong shred_idx,
1130 : int is_turbine,
1131 : long tsorig,
1132 : long now );
1133 :
1134 : void
1135 : fd_gui_handle_leader_fec( fd_gui_t * gui,
1136 : ulong slot,
1137 : ulong fec_shred_cnt,
1138 : int is_end_of_slot,
1139 : long tsorig,
1140 : long now );
1141 :
1142 : void
1143 : fd_gui_handle_exec_txn_done( fd_gui_t * gui,
1144 : ulong slot,
1145 : ulong start_shred_idx,
1146 : ulong end_shred_idx,
1147 : long tsorig_ns,
1148 : long tspub_ns,
1149 : long now );
1150 :
1151 : void
1152 : fd_gui_handle_repair_slot( fd_gui_t * gui, ulong slot, long now );
1153 :
1154 : void
1155 : fd_gui_handle_repair_request( fd_gui_t * gui, ulong slot, ulong shred_idx, long now );
1156 :
1157 : void
1158 : fd_gui_handle_snapshot_update( fd_gui_t * gui,
1159 : fd_snapct_update_t const * msg );
1160 :
1161 : void
1162 : fd_gui_handle_snapsv_update( fd_gui_t * gui,
1163 : ulong sig,
1164 : fd_snapsv_msg_t const * msg,
1165 : ulong sz,
1166 : int eom,
1167 : long now );
1168 :
1169 : void
1170 : fd_gui_stage_snapshot_manifest( fd_gui_t * gui,
1171 : fd_snapshot_manifest_t const * manifest );
1172 :
1173 : void
1174 : fd_gui_handle_epoch_info( fd_gui_t * gui,
1175 : fd_epoch_info_msg_t const * epoch_info,
1176 : long now );
1177 :
1178 : void
1179 : fd_gui_handle_tower_update( fd_gui_t * gui,
1180 : fd_tower_slot_done_t const * msg,
1181 : long now );
1182 :
1183 : void
1184 : fd_gui_handle_replay_update( fd_gui_t * gui,
1185 : fd_replay_slot_completed_t const * slot_completed,
1186 : ulong vote_slot,
1187 : long now );
1188 :
1189 : void
1190 : fd_gui_stage_landed_vote( fd_gui_t * gui,
1191 : ulong landed_slot,
1192 : ulong landed_bank_seq,
1193 : ulong voted_slot );
1194 :
1195 : void
1196 : fd_gui_handle_genesis_hash( fd_gui_t * gui,
1197 : fd_hash_t const * msg );
1198 :
1199 : /* fd_gui_handle_root_advanced is invoked on REPLAY_SIG_ROOT_ADVANCED.
1200 : It roots the (slot, bank_seq) fork and updates the dependent state. */
1201 : void
1202 : fd_gui_handle_root_advanced( fd_gui_t * gui,
1203 : ulong slot,
1204 : ulong bank_seq,
1205 : long now );
1206 :
1207 : /* fd_gui_handle_oc_advanced is invoked on REPLAY_SIG_OC_ADVANCED. It
1208 : marks the (slot, bank_seq) fork optimistically confirmed. */
1209 : void
1210 : fd_gui_handle_oc_advanced( fd_gui_t * gui,
1211 : ulong slot,
1212 : ulong bank_seq,
1213 : long now );
1214 :
1215 : void
1216 : fd_gui_handle_ag_notarized( fd_gui_t * gui,
1217 : ulong slot,
1218 : fd_hash_t const * block_id,
1219 : uchar notarization_kind );
1220 :
1221 : void
1222 : fd_gui_handle_ag_finalized( fd_gui_t * gui,
1223 : ulong slot,
1224 : fd_hash_t const * block_id,
1225 : uchar finalization_kind );
1226 :
1227 : void
1228 : fd_gui_handle_ag_skip_cert( fd_gui_t * gui,
1229 : ulong slot );
1230 :
1231 : void
1232 : fd_gui_handle_ag_leader( fd_gui_t * gui,
1233 : ulong parent_slot );
1234 :
1235 : void
1236 : fd_gui_ag_register_block( fd_gui_t * gui,
1237 : ulong slot,
1238 : fd_hash_t const * block_id,
1239 : ulong bank_seq );
1240 :
1241 : int
1242 : fd_gui_ag_slot_is_skip_notarized( fd_gui_t const * gui,
1243 : ulong slot );
1244 :
1245 : /* fd_gui_slot_get_canon_safe resolves slot number `_slot` on the
1246 : canonical fork and returns a renderable record. */
1247 : fd_gui_slot_t *
1248 : fd_gui_slot_get_canon_safe( fd_gui_t * gui, ulong _slot );
1249 :
1250 :
1251 : /* fd_gui_epoch returns the DB EPOCH record for `epoch`, or NULL if no
1252 : record for that epoch is durable in the store. The returned pointer
1253 : may be written in place. */
1254 : static inline fd_gui_epoch_t *
1255 0 : fd_gui_epoch( fd_gui_t * gui, ulong epoch ) {
1256 0 : fd_gui_hist_epoch_key_t key[ 1 ]; key->epoch = epoch;
1257 0 : return (fd_gui_epoch_t *)fd_gui_hist_kv_get( gui, FD_GUI_HIST_EPOCH, key );
1258 0 : }
1259 :
1260 : /* fd_gui_current_epoch returns the record for the current epoch,
1261 : or NULL if no epoch has been ingested yet. */
1262 : static inline fd_gui_epoch_t *
1263 0 : fd_gui_current_epoch( fd_gui_t * gui ) {
1264 0 : if( FD_UNLIKELY( gui->epoch.current_epoch==ULONG_MAX ) ) return NULL;
1265 0 : return fd_gui_epoch( gui, gui->epoch.current_epoch );
1266 0 : }
1267 :
1268 : /* fd_gui_epoch_get_or_create returns the mutable DB EPOCH record
1269 : for `epoch`, creating it if none exists yet. */
1270 : static inline fd_gui_epoch_t *
1271 : fd_gui_epoch_get_or_create( fd_gui_t * gui,
1272 : ulong epoch,
1273 0 : int * created_out ) {
1274 0 : fd_gui_epoch_t * rec = fd_gui_epoch( gui, epoch );
1275 0 : if( FD_LIKELY( rec ) ) { if( created_out ) *created_out = 0; return rec; }
1276 :
1277 0 : fd_gui_hist_epoch_key_t key[ 1 ]; key->epoch = epoch;
1278 0 : rec = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_EPOCH, key );
1279 0 : if( FD_UNLIKELY( !rec ) ) return NULL;
1280 0 : gui->epoch.stored_epoch_cnt++; /* account the new epoch on successful creation only */
1281 0 : if( created_out ) *created_out = 1;
1282 0 : return rec;
1283 0 : }
1284 :
1285 : /* fd_gui_first_replay_slot returns the lowest slot number the validator
1286 : has authoritative knowledge of during this run. */
1287 : static inline ulong
1288 0 : fd_gui_first_replay_slot( fd_gui_t const * gui ) {
1289 0 : ulong slot_incremental = gui->summary.boot_progress.loading_snapshot[ FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX ].slot;
1290 0 : ulong slot_full = gui->summary.boot_progress.loading_snapshot[ FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX ].slot;
1291 0 : return fd_ulong_if( slot_incremental!=ULONG_MAX, slot_incremental+1UL,
1292 0 : fd_ulong_if( slot_full!=ULONG_MAX, slot_full+1UL, ULONG_MAX ) );
1293 0 : }
1294 :
1295 : static inline fd_gui_leader_slot_t *
1296 : fd_gui_slot_leader_get( fd_gui_t * gui,
1297 : ulong slot,
1298 18 : ulong bank_seq ) {
1299 18 : if( FD_UNLIKELY( !gui->db || slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1300 18 : fd_gui_hist_leader_slot_key_t key = { .slot = slot, .bank_seq = bank_seq };
1301 18 : return fd_gui_hist_kv_get( gui, FD_GUI_HIST_LEADER_SLOT, &key );
1302 18 : }
1303 :
1304 : /* fd_gui_slot_leader_get_or_create returns the mutable returns the
1305 : DB LEADER_SLOT record for (slot, bank_seq), creating it if none
1306 : exists yet. */
1307 :
1308 : static inline fd_gui_leader_slot_t *
1309 : fd_gui_slot_leader_get_or_create( fd_gui_t * gui,
1310 : ulong slot,
1311 6 : ulong bank_seq ) {
1312 6 : if( FD_UNLIKELY( !gui->db || slot==ULONG_MAX ) ) return NULL;
1313 :
1314 6 : fd_gui_leader_slot_t * rec = fd_gui_slot_leader_get( gui, slot, bank_seq );
1315 6 : if( FD_LIKELY( rec ) ) return rec;
1316 :
1317 6 : fd_gui_hist_leader_slot_key_t key = { .slot = slot, .bank_seq = bank_seq };
1318 6 : fd_gui_leader_slot_t * seed = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_LEADER_SLOT, &key );
1319 6 : if( FD_UNLIKELY( !seed ) ) return NULL;
1320 :
1321 6 : *seed = (fd_gui_leader_slot_t){
1322 6 : .slot = slot,
1323 6 : .bank_seq = bank_seq,
1324 6 : .leader_start_time = LONG_MAX,
1325 6 : .leader_end_time = LONG_MAX,
1326 6 : .max_microblocks = ULONG_MAX,
1327 6 : .microblocks_upper_bound = UINT_MAX,
1328 6 : .begin_microblocks = 0U,
1329 6 : .end_microblocks = 0U,
1330 6 : .has_waterfall = 0,
1331 6 : .unbecame_leader = 0
1332 6 : };
1333 6 : return seed;
1334 6 : }
1335 :
1336 : /* fd_gui_slot_leader_get_any returns the DB record for key matching
1337 : (lslot, *), regardless of fork, or NULL if no record exists for lslot. */
1338 :
1339 : static inline fd_gui_leader_slot_t *
1340 9 : fd_gui_slot_leader_get_any( fd_gui_t * gui, ulong _lslot ) {
1341 9 : if( FD_UNLIKELY( !gui->db || _lslot==ULONG_MAX ) ) return NULL;
1342 9 : return fd_gui_hist_kv_get_slot_any( gui, FD_GUI_HIST_LEADER_SLOT, _lslot );
1343 9 : }
1344 :
1345 : /* fd_gui_slot_get returns the DB record for the exact key
1346 : (slot, bank_seq), or NULL if none exists. */
1347 :
1348 : static inline fd_gui_slot_t *
1349 0 : fd_gui_slot_get( fd_gui_t * gui, ulong slot, ulong bank_seq ) {
1350 0 : if( FD_UNLIKELY( slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1351 0 : fd_gui_hist_slot_key_t key;
1352 0 : key.slot = slot; key.bank_seq = bank_seq;
1353 0 : return (fd_gui_slot_t *)fd_gui_hist_kv_get( gui, FD_GUI_HIST_SLOT, &key );
1354 0 : }
1355 :
1356 : /* fd_gui_slot_parent_get returns the DB record for the parent of `c` on
1357 : c's own fork or NULL if the parent is unknown or has no record. */
1358 :
1359 : static inline fd_gui_slot_t *
1360 0 : fd_gui_slot_parent_get( fd_gui_t * gui, fd_gui_slot_t const * c ) {
1361 0 : if( FD_UNLIKELY( !c ) ) return NULL;
1362 0 : return fd_gui_slot_get( gui, c->parent_slot, c->parent_bank_seq );
1363 0 : }
1364 :
1365 : /* fd_gui_slot_get_canon returns the DB record for slot number `_slot`
1366 : on the canonical fork, or NULL if `_slot` is skipped, above the
1367 : canonical tip, or the tip is not yet resolved. The canonical fork is
1368 : the consensus fork chosen by tower fork choice. */
1369 :
1370 : static inline fd_gui_slot_t *
1371 0 : fd_gui_slot_get_canon( fd_gui_t * gui, ulong _slot ) {
1372 0 : if( FD_UNLIKELY( _slot==ULONG_MAX ) ) return NULL;
1373 :
1374 : /* At or below the root the canonical fork has settled, meaning there
1375 : should be exactly one NOT_SKIPPED entry per slot. */
1376 0 : if( FD_LIKELY( gui->summary.slot_rooted!=ULONG_MAX && _slot<=gui->summary.slot_rooted ) ) {
1377 0 : fd_gui_hist_kv_slot_iter_t it[ 1 ];
1378 0 : for( fd_gui_hist_kv_iter_begin( gui, it, FD_GUI_HIST_SLOT, _slot ); it->rec; fd_gui_hist_kv_iter_next( it ) ) {
1379 0 : fd_gui_slot_t const * rec = (fd_gui_slot_t const *)it->rec;
1380 0 : if( FD_LIKELY( rec->skip==FD_GUI_SKIP_STATUS_NOT_SKIPPED ) ) return fd_gui_slot_get( gui, _slot, it->bank_seq );
1381 0 : }
1382 0 : return NULL; /* skipped on the canonical fork, or no record */
1383 0 : }
1384 :
1385 : /* Otherwise do a fork-walk from the canonical frontier. */
1386 0 : ulong slot = gui->summary.slot_tower;
1387 0 : ulong bank_seq = gui->summary.slot_tower_bank_seq;
1388 0 : for(;;) {
1389 0 : if( FD_UNLIKELY( slot==ULONG_MAX || bank_seq==ULONG_MAX ) ) return NULL;
1390 0 : if( FD_UNLIKELY( slot<_slot ) ) return NULL; /* walked past _slot: skipped or above-tip */
1391 0 : fd_gui_slot_t * n = fd_gui_slot_get( gui, slot, bank_seq );
1392 0 : if( FD_UNLIKELY( !n ) ) return NULL; /* evicted / no record */
1393 0 : if( FD_LIKELY( slot==_slot ) ) return n;
1394 0 : slot = n->parent_slot;
1395 0 : bank_seq = n->parent_bank_seq;
1396 0 : }
1397 0 : }
1398 :
1399 : /* fd_gui_slot_get_any returns the DB record for slot number `_slot`
1400 : without any fork awareness: it is a simple store lookup that returns
1401 : the first record that exists for `_slot`. */
1402 :
1403 : static inline fd_gui_slot_t *
1404 0 : fd_gui_slot_get_any( fd_gui_t * gui, ulong _slot ) {
1405 0 : if( FD_UNLIKELY( _slot==ULONG_MAX ) ) return NULL;
1406 :
1407 0 : fd_gui_hist_kv_slot_iter_t it[ 1 ];
1408 0 : fd_gui_hist_kv_iter_begin( gui, it, FD_GUI_HIST_SLOT, _slot );
1409 0 : if( FD_UNLIKELY( !it->rec ) ) return NULL;
1410 0 : return fd_gui_slot_get( gui, _slot, it->bank_seq );
1411 0 : }
1412 :
1413 : /* fd_gui_slot_is_ancestor returns 1 if (ancestor_slot,
1414 : ancestor_bank_seq) is known to be an ancestor of (slot, bank_seq), 0
1415 : otherwise. */
1416 :
1417 : static inline int
1418 : fd_gui_slot_is_ancestor( fd_gui_t * gui,
1419 : ulong ancestor_slot,
1420 : ulong ancestor_bank_seq,
1421 : ulong slot,
1422 0 : ulong bank_seq ) {
1423 0 : for(;;) {
1424 0 : if( FD_UNLIKELY( slot==ancestor_slot ) ) return bank_seq==ancestor_bank_seq;
1425 0 : if( FD_UNLIKELY( slot<ancestor_slot ) ) return 0;
1426 :
1427 0 : fd_gui_slot_t const * rec = fd_gui_slot_get( gui, slot, bank_seq );
1428 0 : if( FD_UNLIKELY( !rec ) ) return 0;
1429 0 : slot = rec->parent_slot;
1430 0 : bank_seq = rec->parent_bank_seq;
1431 0 : }
1432 0 : }
1433 :
1434 : /* fd_gui_slot_skipped_get_parent returns the largest slot number
1435 : smaller than slot that is not skipped. */
1436 :
1437 : static inline ulong
1438 0 : fd_gui_slot_skipped_get_parent( fd_gui_t * gui, ulong slot ) {
1439 0 : fd_gui_slot_t * c = fd_gui_slot_get_canon( gui, gui->summary.slot_tower );
1440 0 : while( c ) {
1441 0 : ulong pslot = c->parent_slot;
1442 0 : fd_gui_slot_t * p = fd_gui_slot_parent_get( gui, c );
1443 0 : if( FD_UNLIKELY( p && pslot<slot ) ) return pslot;
1444 0 : c = p;
1445 0 : }
1446 0 : return ULONG_MAX;
1447 0 : }
1448 :
1449 : /* fd_gui_slot_is_skipped returns 1 if `slot` is skipped on the fork
1450 : whose tip is (des, des_bank_seq) and whose root is `root`, 0
1451 : otherwise. */
1452 :
1453 : static inline int
1454 0 : fd_gui_slot_is_skipped( fd_gui_t * gui, ulong root, ulong des, ulong des_bank_seq, ulong slot ) {
1455 0 : fd_gui_slot_t * c = fd_gui_slot_get( gui, des, des_bank_seq );
1456 0 : ulong cslot = des;
1457 0 : while( c ) {
1458 0 : if( FD_UNLIKELY( root==cslot ) ) return 0; /* on the fork, not skipped */
1459 0 : ulong pslot = c->parent_slot;
1460 0 : fd_gui_slot_t * p = fd_gui_slot_parent_get( gui, c );
1461 0 : if( FD_UNLIKELY( p && pslot<slot && cslot>slot ) ) return 1; /* in-between two on-fork records, skipped */
1462 0 : c = p;
1463 0 : cslot = pslot;
1464 0 : }
1465 0 : return 0; /* slot not between root and des, or is unknown */
1466 0 : }
1467 :
1468 : /* fd_gui_get_epoch_by_slot returns the mutable DB EPOCH record covering
1469 : `_slot` (resolved via the epoch schedule), or NULL if no epoch
1470 : schedule is known yet or no record for that epoch is present. */
1471 :
1472 : static inline fd_gui_epoch_t *
1473 9 : fd_gui_get_epoch_by_slot( fd_gui_t * gui, ulong _slot ) {
1474 9 : if( FD_UNLIKELY( !gui->epoch.has_epoch_schedule ) ) return NULL;
1475 0 : ulong epoch = fd_slot_to_epoch( &gui->epoch.epoch_schedule, _slot, NULL );
1476 0 : return fd_gui_epoch( gui, epoch );
1477 9 : }
1478 :
1479 : /* fd_gui_slot_voter_state returns one of FD_GUI_IS_VOTER_* for
1480 : `_slot`. */
1481 :
1482 : static inline uchar
1483 0 : fd_gui_slot_voter_state( fd_gui_t * gui, ulong _slot ) {
1484 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1485 0 : if( FD_UNLIKELY( !epoch || _slot<epoch->start_slot ) ) return FD_GUI_IS_VOTER_UNKNOWN;
1486 0 : ulong idx = _slot - epoch->start_slot;
1487 0 : return idx<epoch->slot_cnt ? epoch->is_voter[ idx ] : FD_GUI_IS_VOTER_UNKNOWN;
1488 0 : }
1489 :
1490 : static inline int
1491 0 : fd_gui_slot_is_voter( fd_gui_t * gui, ulong _slot ) {
1492 0 : return fd_gui_slot_voter_state( gui, _slot )==FD_GUI_IS_VOTER_YES;
1493 0 : }
1494 :
1495 : static inline uchar
1496 0 : fd_gui_slot_vote_rewarded_state( fd_gui_t * gui, ulong _slot ) {
1497 0 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1498 0 : if( FD_UNLIKELY( !epoch || _slot<epoch->start_slot ) ) return FD_GUI_VOTE_REWARDED_UNKNOWN;
1499 0 : ulong idx = _slot - epoch->start_slot;
1500 0 : return idx<epoch->slot_cnt ? epoch->vote_rewarded[ idx ] : FD_GUI_VOTE_REWARDED_UNKNOWN;
1501 0 : }
1502 :
1503 : /* fd_gui_get_slot_leader returns the leader pubkey scheduled for
1504 : `_slot`, or NULL if `epoch` is NULL, `_slot` is outside the epoch,
1505 : or the schedule index is indeterminate. */
1506 :
1507 : static inline fd_pubkey_t const *
1508 9 : fd_gui_get_slot_leader( fd_gui_epoch_t const * epoch, ulong _slot ) {
1509 9 : if( FD_UNLIKELY( !epoch ) ) return NULL;
1510 0 : if( FD_UNLIKELY( _slot<epoch->start_slot || _slot>=epoch->start_slot+epoch->slot_cnt ) ) return NULL;
1511 0 : ulong rot = (_slot - epoch->start_slot)/FD_EPOCH_SLOTS_PER_ROTATION;
1512 0 : uint idx = epoch->sched[ rot ];
1513 0 : if( FD_UNLIKELY( idx>=epoch->pub_cnt ) ) return NULL; /* indeterminate */
1514 0 : return &epoch->pub[ idx ];
1515 0 : }
1516 :
1517 : static inline int
1518 9 : fd_gui_slot_is_mine( fd_gui_t * gui, ulong _slot ) {
1519 9 : fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1520 9 : fd_pubkey_t const * slot_leader = fd_gui_get_slot_leader( epoch, _slot );
1521 9 : if( FD_UNLIKELY( !slot_leader ) ) return 0;
1522 0 : return !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL );
1523 9 : }
1524 :
1525 : /* fd_gui_slot_get_or_create returns the mutable returns the mutable DB
1526 : SLOT record for (_slot, bank_seq), creating it if none exists yet. */
1527 :
1528 : static inline fd_gui_slot_t *
1529 : fd_gui_slot_get_or_create( fd_gui_t * gui,
1530 : ulong _slot,
1531 : ulong _parent_slot,
1532 : ulong bank_seq,
1533 0 : ulong parent_bank_seq ) {
1534 0 : fd_gui_slot_t * rec = fd_gui_slot_get( gui, _slot, bank_seq );
1535 0 : if( FD_LIKELY( rec ) ) return rec;
1536 :
1537 0 : fd_gui_epoch_t * epoch = fd_gui_get_epoch_by_slot( gui, _slot );
1538 0 : fd_pubkey_t const * slot_leader = fd_gui_get_slot_leader( epoch, _slot );
1539 0 : int mine = fd_int_if( !!slot_leader, !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL ), 0 );
1540 :
1541 0 : fd_gui_hist_slot_key_t key;
1542 0 : key.slot = _slot; key.bank_seq = bank_seq;
1543 0 : fd_gui_slot_t * meta = fd_gui_hist_kv_get_or_create( gui, FD_GUI_HIST_SLOT, &key );
1544 0 : if( FD_UNLIKELY( !meta ) ) return NULL;
1545 :
1546 0 : meta->slot = _slot;
1547 0 : meta->bank_seq = bank_seq;
1548 0 : meta->parent_bank_seq = parent_bank_seq;
1549 0 : meta->parent_slot = _parent_slot;
1550 0 : meta->vote_slot = ULONG_MAX;
1551 0 : meta->vote_latency_exact = FD_GUI_VOTE_LATENCY_NOT_VOTED;
1552 0 : meta->max_compute_units = UINT_MAX;
1553 0 : meta->completed_time = LONG_MAX;
1554 0 : meta->parent_completed_time = LONG_MAX;
1555 0 : meta->mine = (uchar)(mine & 1);
1556 0 : meta->is_voter = FD_GUI_IS_VOTER_NO;
1557 0 : if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) meta->is_voter = FD_GUI_IS_VOTER_UNKNOWN;
1558 0 : meta->skip = FD_GUI_SKIP_STATUS_UNKNOWN;
1559 0 : meta->level = (uchar)( _slot ? FD_GUI_SLOT_LEVEL_INCOMPLETE : FD_GUI_SLOT_LEVEL_ROOTED ); /* slot 0 always rooted */
1560 0 : meta->notarization_kind = FD_GUI_AG_NOTAR_NONE;
1561 0 : meta->finalization_kind = FD_GUI_AG_FINAL_NONE;
1562 0 : meta->vote_rewarded = FD_GUI_VOTE_REWARDED_UNKNOWN;
1563 0 : meta->vote_failed = UINT_MAX;
1564 0 : meta->vote_success = UINT_MAX;
1565 0 : meta->nonvote_success = UINT_MAX;
1566 0 : meta->nonvote_failed = UINT_MAX;
1567 0 : meta->compute_units = UINT_MAX;
1568 0 : meta->transaction_fee = ULONG_MAX;
1569 0 : meta->priority_fee = ULONG_MAX;
1570 0 : meta->tips = ULONG_MAX;
1571 0 : meta->shred_cnt = UINT_MAX;
1572 0 : memset( meta->block_hash.uc, 0, sizeof(fd_hash_t) );
1573 :
1574 0 : if( FD_UNLIKELY( mine && epoch ) ) epoch->my_total_slots++;
1575 :
1576 0 : return meta;
1577 0 : }
1578 :
1579 : FD_PROTOTYPES_END
1580 :
1581 : #endif /* HEADER_fd_src_disco_gui_fd_gui_h */
|