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 :
6 : #include "../topo/fd_topo.h"
7 :
8 : #include "../../ballet/txn/fd_txn.h"
9 : #include "../../disco/fd_txn_p.h"
10 : #include "../../discof/replay/fd_replay_tile.h"
11 : #include "../../discof/restore/fd_snaprd_tile.h"
12 : #include "../../discof/tower/fd_tower_tile.h"
13 : #include "../../flamenco/leaders/fd_leaders.h"
14 : #include "../../flamenco/types/fd_types_custom.h"
15 : #include "../../util/fd_util_base.h"
16 : #include "../../util/hist/fd_histf.h"
17 : #include "../../waltz/http/fd_http_server.h"
18 :
19 : /* frankendancer only */
20 : #define FD_GUI_MAX_PEER_CNT ( 40200UL)
21 :
22 : /* frankendancer only */
23 0 : #define FD_GUI_START_PROGRESS_TYPE_INITIALIZING ( 0)
24 0 : #define FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_FULL_SNAPSHOT ( 1)
25 0 : #define FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_FULL_SNAPSHOT ( 2)
26 0 : #define FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_INCREMENTAL_SNAPSHOT ( 3)
27 0 : #define FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_INCREMENTAL_SNAPSHOT ( 4)
28 0 : #define FD_GUI_START_PROGRESS_TYPE_CLEANING_BLOCK_STORE ( 5)
29 0 : #define FD_GUI_START_PROGRESS_TYPE_CLEANING_ACCOUNTS ( 6)
30 0 : #define FD_GUI_START_PROGRESS_TYPE_LOADING_LEDGER ( 7)
31 0 : #define FD_GUI_START_PROGRESS_TYPE_PROCESSING_LEDGER ( 8)
32 0 : #define FD_GUI_START_PROGRESS_TYPE_STARTING_SERVICES ( 9)
33 0 : #define FD_GUI_START_PROGRESS_TYPE_HALTED (10)
34 0 : #define FD_GUI_START_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY (11)
35 0 : #define FD_GUI_START_PROGRESS_TYPE_RUNNING (12)
36 :
37 : /* frankendancer only */
38 : #define FD_GUI_SLOT_LEADER_UNSTARTED (0UL)
39 : #define FD_GUI_SLOT_LEADER_STARTED (1UL)
40 : #define FD_GUI_SLOT_LEADER_ENDED (2UL)
41 :
42 : /* frankendancer only */
43 : struct fd_gui_gossip_peer {
44 : fd_pubkey_t pubkey[ 1 ];
45 : ulong wallclock;
46 : ushort shred_version;
47 :
48 : int has_version;
49 : struct {
50 : ushort major;
51 : ushort minor;
52 : ushort patch;
53 :
54 : int has_commit;
55 : uint commit;
56 :
57 : uint feature_set;
58 : } version;
59 :
60 : struct {
61 : uint ipv4;
62 : ushort port;
63 : } sockets[ 12 ];
64 : };
65 :
66 : /* frankendancer only */
67 : struct fd_gui_vote_account {
68 : fd_pubkey_t pubkey[ 1 ];
69 : fd_pubkey_t vote_account[ 1 ];
70 :
71 : ulong activated_stake;
72 : ulong last_vote;
73 : ulong root_slot;
74 : ulong epoch_credits;
75 : uchar commission;
76 : int delinquent;
77 : };
78 :
79 : /* frankendancer only */
80 : struct fd_gui_validator_info {
81 : fd_pubkey_t pubkey[ 1 ];
82 :
83 : char name[ 64 ];
84 : char website[ 128 ];
85 : char details[ 256 ];
86 : char icon_uri[ 128 ];
87 : };
88 :
89 : /* frankendancer only */
90 : #define FD_GUI_SLOT_LEADER_UNSTARTED (0UL)
91 : #define FD_GUI_SLOT_LEADER_STARTED (1UL)
92 : #define FD_GUI_SLOT_LEADER_ENDED (2UL)
93 :
94 0 : #define FD_GUI_SLOTS_CNT (864000UL) /* 2x 432000 */
95 0 : #define FD_GUI_LEADER_CNT (4096UL)
96 :
97 0 : #define FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS (10L)
98 0 : #define FD_GUI_TPS_HISTORY_SAMPLE_CNT (150UL)
99 :
100 0 : #define FD_GUI_TILE_TIMER_SNAP_CNT (512UL)
101 0 : #define FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT (50UL) /* 500ms / 10ms */
102 : #define FD_GUI_TILE_TIMER_TILE_CNT (128UL)
103 :
104 0 : #define FD_GUI_VOTE_STATE_NON_VOTING (0)
105 0 : #define FD_GUI_VOTE_STATE_VOTING (1)
106 0 : #define FD_GUI_VOTE_STATE_DELINQUENT (2)
107 :
108 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_JOINING_GOSSIP (1)
109 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_FULL_SNAPSHOT (2)
110 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_LOADING_INCREMENTAL_SNAPSHOT (3)
111 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_CATCHING_UP (4)
112 0 : #define FD_GUI_BOOT_PROGRESS_TYPE_RUNNING (5)
113 :
114 0 : #define FD_GUI_BOOT_PROGRESS_FULL_SNAPSHOT_IDX (0UL)
115 0 : #define FD_GUI_BOOT_PROGRESS_INCREMENTAL_SNAPSHOT_IDX (1UL)
116 0 : #define FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT (2UL)
117 :
118 0 : #define FD_GUI_SLOT_LEVEL_INCOMPLETE (0)
119 0 : #define FD_GUI_SLOT_LEVEL_COMPLETED (1)
120 0 : #define FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED (2)
121 0 : #define FD_GUI_SLOT_LEVEL_ROOTED (3)
122 0 : #define FD_GUI_SLOT_LEVEL_FINALIZED (4)
123 :
124 : /* Ideally, we would store an entire epoch's worth of transactions. If
125 : we assume any given validator will have at most 5% stake, and average
126 : transactions per slot is around 10_000, then an epoch will have about
127 : 432_000*10_000*0.05 transactions (~2^28).
128 :
129 : Unfortunately, the transaction struct is 100+ bytes. If we sized the
130 : array to 2^28 entries then the memory required would be ~26GB. In
131 : order to keep memory usage to a more reasonable level, we'll
132 : arbitrarily use a fourth of that size. */
133 0 : #define FD_GUI_TXN_HISTORY_SZ (1UL<<26UL)
134 :
135 0 : #define FD_GUI_TXN_FLAGS_STARTED ( 1U)
136 0 : #define FD_GUI_TXN_FLAGS_ENDED ( 2U)
137 0 : #define FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE ( 4U)
138 0 : #define FD_GUI_TXN_FLAGS_FROM_BUNDLE ( 8U)
139 0 : #define FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK (16U)
140 :
141 : /* One use case for tracking ingress shred slot is to estimate when we
142 : have caught up to the tip of the blockchain. A naive approach would
143 : be to track the maximum seen slot.
144 :
145 : maximum_seen_slot = fd_ulong_max( maximum_seen_slot, new_slot_from_shred_tile );
146 :
147 : Unfortunately, this doesn't always work because a validator can send
148 : a slot number that is arbitrarily large on a false fork. Also, these
149 : shreds can be for a repair response, which can be arbitrarily small.
150 :
151 : The prospects here seem bleak, but not all hope is lost! We know
152 : that for a sufficiently large historical time window there is a high
153 : probability that at least some of the slots we observe will be valid
154 : recent turbine slots. For a sufficiently small window there is a high
155 : probability that all the observed shred slots are non-malicious (i.e.
156 : not arbitrarily large).
157 :
158 : In practice shred slots are almost always non-malicious. We can keep
159 : a history of the 12 largest slots we've seen in the past 4.8 seconds.
160 : We'll consider the "tip" of the blockchain to be the maximum slot in
161 : our history. This way, if we receive maliciously large slot number,
162 : it will be evicted after 4.8 seconds. If we receive a small slot from
163 : a repair response it will be ignored because we've seen other larger
164 : slots, meaning that our estimate is eventually consistent. For
165 : monitoring purposes this is sufficient.
166 :
167 : The worst case scenario is that this validator receives an incorrect
168 : shred slot slot more than once every 3 leader rotations. Before the
169 : previous incorrect slot is evicted from the history, a new one takes
170 : it's place and we wouldn't never get a correct estimate of the tip of
171 : the chain. We also would indefinitely think that that we haven't
172 : caught up. This would require the chain having perpetually malicious
173 : leaders with adjacent rotations. If this happens, Solana has bigger
174 : problems. */
175 0 : #define FD_GUI_TURBINE_SLOT_HISTORY_SZ ( 12UL )
176 :
177 : /* Like the turbine slot, the latest repair slot can also swing to
178 : arbitrarily large values due to a malicious fork switch. The gui
179 : provides the same guarantees for freshness and accuracy. This
180 : history is somewhat larger to handle the increased repair bandwidth
181 : during catch up. */
182 0 : #define FD_GUI_REPAIR_SLOT_HISTORY_SZ ( 512UL )
183 :
184 : /* FD_GUI_*_CATCH_UP_HISTORY_SZ is the capacity of the record of slots
185 : seen from repair or turbine during the catch up stage at startup.
186 : These buffers are run-length encoded, so they will typically be very
187 : small. The worst-case scenario is unbounded, so bounds here are
188 : determined heuristically. */
189 : #define FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ (4096UL)
190 : #define FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ (4096UL)
191 :
192 : /* FD_GUI_SHREDS_STAGING_SZ is number of shred events we'll retain in
193 : in a small staging area. The lifecycle of a shred looks something
194 : like the following
195 :
196 : states] turbine -> repairing (optional) -> processing -> waiting_for_siblings -> slot_complete
197 : events] ^-repair_requested ^-shred_received/shred_repaired ^-shred_replayed ^-max(shred_replayed)
198 :
199 : We're interested in recording timestamps for state transitions (which
200 : these docs call "shred events"). Unfortunately, due to forking,
201 : duplicate packets, etc we can't make any guarantees about ordering or
202 : uniqueness for these event timestamps. Instead the GUI just records
203 : timestamps for all events as they occur and put them into an array.
204 : Newly recorded event timestamps are also broadcast live to WebSocket
205 : consumers.
206 :
207 : The amount of shred events for non-finalized blocks can't really be
208 : bounded, so we use generous estimates here to set a memory bound. */
209 : #define FD_GUI_MAX_SHREDS_PER_BLOCK (32UL*1024UL)
210 : #define FD_GUI_MAX_EVENTS_PER_SHRED ( 32UL)
211 : #define FD_GUI_SHREDS_STAGING_SZ (32UL * FD_GUI_MAX_SHREDS_PER_BLOCK * FD_GUI_MAX_EVENTS_PER_SHRED)
212 :
213 : /* FD_GUI_SHREDS_HISTORY_SZ the number of shred events in our historical
214 : shred store. Shred events here belong to finalized slots which means
215 : we won't record any additional shred updates for these slots.
216 :
217 : All shred events for a given slot will be places in a contiguous
218 : chunk in the array, and the bounding indicies are stored in the
219 : fd_gui_slot_t slot history. Within a slot chunk, shred events are
220 : ordered in the ordered they were recorded by the gui tile.
221 :
222 : Ideally, we have enough space to store an epoch's worth of events,
223 : but we are limited by realistic memory consumption. Instead, we pick
224 : bound heuristically. */
225 : #define FD_GUI_SHREDS_HISTORY_SZ (432000UL*2000UL*4UL / 6UL)
226 :
227 : #define FD_GUI_SLOT_SHRED_REPAIR_REQUEST (0UL)
228 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_TURBINE (1UL)
229 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_REPAIR (2UL)
230 : #define FD_GUI_SLOT_SHRED_SHRED_REPLAYED (3UL)
231 : #define FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE (4UL)
232 :
233 0 : #define FD_GUI_SLOT_RANKINGS_SZ (100UL)
234 0 : #define FD_GUI_SLOT_RANKING_TYPE_ASC (0)
235 0 : #define FD_GUI_SLOT_RANKING_TYPE_DESC (1)
236 :
237 : /* FD_GUI_SHREDS_STAGING_SZ is number of shred events we'll retain in
238 : in a small staging area. The lifecycle of a shred looks something
239 : like the following
240 :
241 : states] turbine -> repairing (optional) -> processing -> waiting_for_siblings -> slot_complete
242 : events] ^-repair_requested ^-shred_received/shred_repaired ^-shred_replayed ^-max(shred_replayed)
243 :
244 : We're interested in recording timestamps for state transitions (which
245 : these docs call "shred events"). Unfortunately, due to forking,
246 : duplicate packets, etc we can't make any guarantees about ordering or
247 : uniqueness for these event timestamps. Instead the GUI just records
248 : timestamps for all events as they occur and put them into an array.
249 : Newly recorded event timestamps are also broadcast live to WebSocket
250 : consumers.
251 :
252 : The amount of shred events for non-finalized blocks can't really be
253 : bounded, so we use generous estimates here to set a memory bound. */
254 0 : #define FD_GUI_MAX_SHREDS_PER_BLOCK (32UL*1024UL)
255 0 : #define FD_GUI_MAX_EVENTS_PER_SHRED ( 32UL)
256 0 : #define FD_GUI_SHREDS_STAGING_SZ (32UL * FD_GUI_MAX_SHREDS_PER_BLOCK * FD_GUI_MAX_EVENTS_PER_SHRED)
257 :
258 : /* FD_GUI_SHREDS_HISTORY_SZ the number of shred events in our historical
259 : shred store. Shred events here belong to finalized slots which means
260 : we won't record any additional shred updates for these slots.
261 :
262 : All shred events for a given slot will be places in a contiguous
263 : chunk in the array, and the bounding indicies are stored in the
264 : fd_gui_slot_t slot history. Within a slot chunk, shred events are
265 : ordered in the ordered they were recorded by the gui tile.
266 :
267 : Ideally, we have enough space to store an epoch's worth of events,
268 : but we are limited by realistic memory consumption. Instead, we pick
269 : bound heuristically. */
270 : #define FD_GUI_SHREDS_HISTORY_SZ (432000UL*2000UL*4UL / 6UL)
271 :
272 : #define FD_GUI_SLOT_SHRED_REPAIR_REQUEST (0UL)
273 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_TURBINE (1UL)
274 0 : #define FD_GUI_SLOT_SHRED_SHRED_RECEIVED_REPAIR (2UL)
275 : #define FD_GUI_SLOT_SHRED_SHRED_REPLAYED (3UL)
276 : #define FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE (4UL)
277 :
278 : struct fd_gui_tile_timers {
279 : ulong caughtup_housekeeping_ticks;
280 : ulong processing_housekeeping_ticks;
281 : ulong backpressure_housekeeping_ticks;
282 :
283 : ulong caughtup_prefrag_ticks;
284 : ulong processing_prefrag_ticks;
285 : ulong backpressure_prefrag_ticks;
286 :
287 : ulong caughtup_postfrag_ticks;
288 : ulong processing_postfrag_ticks;
289 : };
290 :
291 : typedef struct fd_gui_tile_timers fd_gui_tile_timers_t;
292 :
293 : struct fd_gui_leader_slot {
294 : ulong slot;
295 : long leader_start_time; /* UNIX timestamp of when we first became leader in this slot */
296 : long leader_end_time; /* UNIX timestamp of when we stopped being leader in this slot */
297 :
298 : /* Stem tiles can exist in one of 8 distinct activity regimes at any
299 : given moment. One of these regimes, caughtup_postfrag, is the
300 : only regime where a tile is in a spin loop without doing any
301 : useful work. This info is useful from a monitoring perspective
302 : because it lets us estimate CPU utilization on a pinned core.
303 :
304 : Every 10ms, the gui tile samples the amount of time tiles spent
305 : in each regime in the past 10ms. This sample is used to infer
306 : the CPU utilization in the past 10ms. This utilization is
307 : streamed live to WebSocket clients.
308 :
309 : In additional to live utilization, we are interested in recording
310 : utilization during one of this validator's leader slots. The gui
311 : tile is continuously recording samples to storage with capacity
312 : FD_GUI_TILE_TIMER_SNAP_CNT. The sample index is recorded at the
313 : start and end of a leader slot, and the number of samples is
314 : downsampled to be at most FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT
315 : samples (e.g. if there was an unusually long leader slot) and
316 : inserted into historical storage with capacity FD_GUI_LEADER_CNT.
317 : FD_GUI_TILE_TIMER_TILE_CNT is the maximum number of tiles supported. */
318 : fd_gui_tile_timers_t tile_timers[ FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT ][ FD_GUI_TILE_TIMER_TILE_CNT ];
319 : ulong tile_timers_sample_cnt;
320 :
321 : struct {
322 : uint microblocks_upper_bound; /* An upper bound on the number of microblocks in the slot. If the number of
323 : microblocks observed is equal to this, the slot can be considered over.
324 : Generally, the bound is set to a "final" state by a done packing message,
325 : which sets it to the exact number of microblocks, but sometimes this message
326 : is not sent, if the max upper bound published by poh was already correct. */
327 : uint begin_microblocks; /* The number of microblocks we have seen be started (sent) from pack to banks. */
328 : uint end_microblocks; /* The number of microblocks we have seen be ended (sent) from banks to poh. The
329 : slot is only considered over if the begin and end microblocks seen are both equal
330 : to the microblock upper bound. */
331 :
332 : ulong start_offset; /* The smallest pack transaction index for this slot. The first transaction for this slot will
333 : be written to gui->txs[ start_offset%FD_GUI_TXN_HISTORY_SZ ]. */
334 : ulong end_offset; /* The largest pack transaction index for this slot, plus 1. The last transaction for this
335 : slot will be written to gui->txs[ (start_offset-1)%FD_GUI_TXN_HISTORY_SZ ]. */
336 : } txs;
337 : };
338 :
339 : typedef struct fd_gui_leader_slot fd_gui_leader_slot_t;
340 :
341 : struct fd_gui_slot_staged_shred_event {
342 : long timestamp;
343 : ulong slot;
344 : ushort shred_idx;
345 : ushort fec_idx;
346 : uchar event;
347 : };
348 :
349 : typedef struct fd_gui_slot_staged_shred_event fd_gui_slot_staged_shred_event_t;
350 :
351 : #define SORT_NAME fd_gui_slot_staged_shred_event_sort
352 : #define SORT_KEY_T fd_gui_slot_staged_shred_event_t
353 : #define SORT_BEFORE(a,b) (__extension__({ (void)(b); (a).slot==ULONG_MAX; }))
354 : #include "../../util/tmpl/fd_sort.c"
355 :
356 : struct __attribute__((packed)) fd_gui_slot_history_shred_event {
357 : long timestamp;
358 : ushort shred_idx;
359 : uchar event;
360 : };
361 :
362 : typedef struct fd_gui_slot_history_shred_event fd_gui_slot_history_shred_event_t;
363 :
364 : struct fd_gui_slot_ranking {
365 : ulong slot;
366 : ulong value;
367 : int type;
368 : };
369 : typedef struct fd_gui_slot_ranking fd_gui_slot_ranking_t;
370 :
371 : /* All rankings are initialized / reset to ULONG_MAX. These sentinels
372 : sort AFTER non-sentinel ranking entries. Equal slots are sorted by
373 : oldest slot AFTER. Otherwise sort by value according to ranking
374 : type. */
375 : #define SORT_NAME fd_gui_slot_ranking_sort
376 0 : #define SORT_KEY_T fd_gui_slot_ranking_t
377 0 : #define SORT_BEFORE(a,b) fd_int_if( (a).slot==ULONG_MAX, 0, fd_int_if( (b).slot==ULONG_MAX, 1, fd_int_if( (a).value==(b).value, (a).slot>(b).slot, fd_int_if( (a).type==FD_GUI_SLOT_RANKING_TYPE_DESC, (a).value>(b).value, (a).value<(b).value ) ) ) )
378 : #include "../../util/tmpl/fd_sort.c"
379 :
380 : struct fd_gui_slot_rankings {
381 : fd_gui_slot_ranking_t largest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
382 : fd_gui_slot_ranking_t largest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
383 : fd_gui_slot_ranking_t largest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
384 : fd_gui_slot_ranking_t largest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
385 : fd_gui_slot_ranking_t largest_compute_units [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
386 : fd_gui_slot_ranking_t largest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
387 : fd_gui_slot_ranking_t smallest_tips [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
388 : fd_gui_slot_ranking_t smallest_fees [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
389 : fd_gui_slot_ranking_t smallest_rewards [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
390 : fd_gui_slot_ranking_t smallest_duration [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
391 : fd_gui_slot_ranking_t smallest_compute_units[ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
392 : fd_gui_slot_ranking_t smallest_skipped [ FD_GUI_SLOT_RANKINGS_SZ+1UL ];
393 : };
394 :
395 : typedef struct fd_gui_slot_rankings fd_gui_slot_rankings_t;
396 :
397 : struct fd_gui_ephemeral_slot {
398 : ulong slot; /* ULONG_MAX indicates invalid/evicted */
399 : long timestamp_arrival_nanos;
400 : };
401 : typedef struct fd_gui_ephemeral_slot fd_gui_ephemeral_slot_t;
402 :
403 : #define SORT_NAME fd_gui_ephemeral_slot_sort
404 0 : #define SORT_KEY_T fd_gui_ephemeral_slot_t
405 0 : #define SORT_BEFORE(a,b) fd_int_if( (a).slot==ULONG_MAX, 0, fd_int_if( (b).slot==ULONG_MAX, 1, fd_int_if( (a).slot==(b).slot, (a).timestamp_arrival_nanos>(b).timestamp_arrival_nanos, (a).slot>(b).slot ) ) )
406 : #include "../../util/tmpl/fd_sort.c"
407 :
408 : struct __attribute__((packed)) fd_gui_txn {
409 : uchar signature[ FD_TXN_SIGNATURE_SZ ];
410 : ulong transaction_fee;
411 : ulong priority_fee;
412 : ulong tips;
413 : long timestamp_arrival_nanos;
414 :
415 : /* compute_units_requested has both execution and non-execution cus */
416 : uint compute_units_requested : 21; /* <= 1.4M */
417 : uint compute_units_consumed : 21; /* <= 1.4M */
418 : uint bank_idx : 6; /* in [0, 64) */
419 : uint error_code : 6; /* in [0, 64) */
420 : int timestamp_delta_start_nanos;
421 : int timestamp_delta_end_nanos;
422 :
423 : /* txn_{}_pct is used as a fraction of the total microblock
424 : duration. For example, txn_load_end_pct can be used to find the
425 : time when this transaction started executing:
426 :
427 : timestamp_delta_start_exec_nanos = (
428 : (timestamp_delta_end_nanos-timestamp_delta_start_nanos) *
429 : ((double)txn_{}_pct/USHORT_MAX)
430 : ) */
431 : uchar txn_start_pct;
432 : uchar txn_load_end_pct;
433 : uchar txn_end_pct;
434 : uchar txn_preload_end_pct;
435 : uchar flags; /* assigned with the FD_GUI_TXN_FLAGS_* macros */
436 : uchar source_tpu; /* FD_TXN_M_TPU_SOURCE_* */
437 : uint source_ipv4;
438 : uint microblock_idx;
439 : };
440 :
441 : typedef struct fd_gui_txn fd_gui_txn_t;
442 :
443 : struct fd_gui_txn_waterfall {
444 : struct {
445 : ulong quic;
446 : ulong udp;
447 : ulong gossip;
448 : ulong block_engine;
449 : ulong pack_cranked;
450 : } in;
451 :
452 : struct {
453 : ulong net_overrun;
454 : ulong quic_overrun;
455 : ulong quic_frag_drop;
456 : ulong quic_abandoned;
457 : ulong tpu_quic_invalid;
458 : ulong tpu_udp_invalid;
459 : ulong verify_overrun;
460 : ulong verify_parse;
461 : ulong verify_failed;
462 : ulong verify_duplicate;
463 : ulong dedup_duplicate;
464 : ulong resolv_lut_failed;
465 : ulong resolv_expired;
466 : ulong resolv_ancient;
467 : ulong resolv_no_ledger;
468 : ulong resolv_retained;
469 : ulong pack_invalid;
470 : ulong pack_invalid_bundle;
471 : ulong pack_expired;
472 : ulong pack_retained;
473 : ulong pack_wait_full;
474 : ulong pack_leader_slow;
475 : ulong bank_invalid;
476 : ulong block_success;
477 : ulong block_fail;
478 : } out;
479 : };
480 :
481 : typedef struct fd_gui_txn_waterfall fd_gui_txn_waterfall_t;
482 :
483 : struct fd_gui_tile_stats {
484 : long sample_time_nanos;
485 :
486 : ulong net_in_rx_bytes; /* Number of bytes received by the net or sock tile*/
487 : ulong quic_conn_cnt; /* Number of active QUIC connections */
488 : fd_histf_t bundle_rx_delay_hist; /* Histogram of bundle rx delay */
489 : ulong bundle_rtt_smoothed_nanos; /* RTT (nanoseconds) moving average */
490 : ulong verify_drop_cnt; /* Number of transactions dropped by verify tiles */
491 : ulong verify_total_cnt; /* Number of transactions received by verify tiles */
492 : ulong dedup_drop_cnt; /* Number of transactions dropped by dedup tile */
493 : ulong dedup_total_cnt; /* Number of transactions received by dedup tile */
494 : ulong pack_buffer_cnt; /* Number of buffered transactions in the pack tile */
495 : ulong pack_buffer_capacity; /* Total size of the pack transaction buffer */
496 : ulong bank_txn_exec_cnt; /* Number of transactions processed by the bank tile */
497 : ulong net_out_tx_bytes; /* Number of bytes sent by the net or sock tile */
498 : };
499 :
500 : typedef struct fd_gui_tile_stats fd_gui_tile_stats_t;
501 :
502 : struct fd_gui_slot {
503 : ulong slot;
504 : ulong parent_slot;
505 : uint max_compute_units;
506 : long completed_time;
507 : int mine;
508 : int skipped;
509 : int must_republish;
510 : int level;
511 : uint total_txn_cnt;
512 : uint vote_txn_cnt;
513 : uint failed_txn_cnt;
514 : uint nonvote_failed_txn_cnt;
515 : uint compute_units;
516 : ulong transaction_fee;
517 : ulong priority_fee;
518 : ulong tips;
519 : uint shred_cnt;
520 :
521 : /* Some slot info is only tracked for our own leader slots. These
522 : slots are kept in a separate buffer. */
523 : ulong leader_history_idx;
524 :
525 : fd_gui_txn_waterfall_t waterfall_begin[ 1 ];
526 : fd_gui_txn_waterfall_t waterfall_end[ 1 ];
527 :
528 : fd_gui_tile_stats_t tile_stats_begin[ 1 ];
529 : fd_gui_tile_stats_t tile_stats_end[ 1 ];
530 :
531 : struct {
532 : ulong start_offset; /* gui->shreds.history[ start_offset % FD_GUI_SHREDS_HISTORY_SZ ] is the first shred event in
533 : contiguous chunk of events in the shred history corresponding to this slot. */
534 : ulong end_offset; /* gui->shreds.history[ end_offset % FD_GUI_SHREDS_HISTORY_SZ ] is the last shred event in
535 : contiguous chunk of events in the shred history corresponding to this slot. */
536 : } shreds;
537 : };
538 :
539 : typedef struct fd_gui_slot fd_gui_slot_t;
540 :
541 : struct fd_gui {
542 : fd_http_server_t * http;
543 : fd_topo_t * topo;
544 :
545 : long next_sample_400millis;
546 : long next_sample_100millis;
547 : long next_sample_10millis;
548 :
549 : ulong debug_in_leader_slot;
550 :
551 : struct {
552 : fd_pubkey_t identity_key[ 1 ];
553 : int has_vote_key;
554 : fd_pubkey_t vote_key[ 1 ];
555 : char vote_key_base58[ FD_BASE58_ENCODED_32_SZ ];
556 : char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ];
557 :
558 : int is_full_client;
559 : char const * version;
560 : char const * cluster;
561 :
562 : ulong vote_distance;
563 : int vote_state;
564 :
565 : long startup_time_nanos;
566 :
567 : union {
568 : struct { /* frankendancer only */
569 : uchar phase;
570 : int startup_got_full_snapshot;
571 :
572 : ulong startup_incremental_snapshot_slot;
573 : uint startup_incremental_snapshot_peer_ip_addr;
574 : ushort startup_incremental_snapshot_peer_port;
575 : double startup_incremental_snapshot_elapsed_secs;
576 : double startup_incremental_snapshot_remaining_secs;
577 : double startup_incremental_snapshot_throughput;
578 : ulong startup_incremental_snapshot_total_bytes;
579 : ulong startup_incremental_snapshot_current_bytes;
580 :
581 : ulong startup_full_snapshot_slot;
582 : uint startup_full_snapshot_peer_ip_addr;
583 : ushort startup_full_snapshot_peer_port;
584 : double startup_full_snapshot_elapsed_secs;
585 : double startup_full_snapshot_remaining_secs;
586 : double startup_full_snapshot_throughput;
587 : ulong startup_full_snapshot_total_bytes;
588 : ulong startup_full_snapshot_current_bytes;
589 :
590 : ulong startup_ledger_slot;
591 : ulong startup_ledger_max_slot;
592 :
593 : ulong startup_waiting_for_supermajority_slot;
594 : ulong startup_waiting_for_supermajority_stake_pct;
595 : } startup_progress;
596 : struct { /* used in the full client */
597 : uchar phase;
598 : long joining_gossip_time_nanos;
599 : struct {
600 : ulong slot;
601 : uint peer_addr;
602 : ushort peer_port;
603 : ulong total_bytes_compressed;
604 : long reset_time_nanos; /* UNIX nanosecond timestamp */
605 : long sample_time_nanos;
606 : ulong reset_cnt;
607 :
608 : ulong read_bytes_compressed;
609 : char read_path[ PATH_MAX+30UL ]; /* URL or filesystem path. 30 is fd_cstr_nlen( "https://255.255.255.255:12345/", ULONG_MAX ) */
610 :
611 : ulong decompress_bytes_decompressed;
612 : ulong decompress_bytes_compressed;
613 :
614 : ulong insert_bytes_decompressed;
615 : char insert_path[ PATH_MAX ];
616 : ulong insert_accounts_current;
617 : } loading_snapshot[ FD_GUI_BOOT_PROGRESS_SNAPSHOT_CNT ];
618 :
619 : long catching_up_time_nanos;
620 : } boot_progress;
621 : };
622 :
623 : int schedule_strategy;
624 :
625 : ulong identity_account_balance;
626 : ulong vote_account_balance;
627 : ulong estimated_slot_duration_nanos;
628 :
629 : ulong sock_tile_cnt;
630 : ulong net_tile_cnt;
631 : ulong quic_tile_cnt;
632 : ulong verify_tile_cnt;
633 : ulong resolv_tile_cnt;
634 : ulong bank_tile_cnt;
635 : ulong shred_tile_cnt;
636 :
637 : ulong slot_rooted;
638 : ulong slot_optimistically_confirmed;
639 : ulong slot_completed;
640 : ulong slot_estimated;
641 : ulong slot_caught_up;
642 : ulong slot_repair;
643 : ulong slot_turbine;
644 :
645 : fd_gui_ephemeral_slot_t slots_max_turbine[ FD_GUI_TURBINE_SLOT_HISTORY_SZ+1UL ];
646 : fd_gui_ephemeral_slot_t slots_max_repair [ FD_GUI_REPAIR_SLOT_HISTORY_SZ +1UL ];
647 :
648 : /* catchup_* is run-length encoded. i.e. adjacent pairs represent
649 : contiguous runs */
650 : ulong catch_up_turbine[ FD_GUI_TURBINE_CATCH_UP_HISTORY_SZ ];
651 : ulong catch_up_turbine_sz;
652 :
653 : ulong catch_up_repair[ FD_GUI_REPAIR_CATCH_UP_HISTORY_SZ ];
654 : ulong catch_up_repair_sz;
655 :
656 : ulong estimated_tps_history_idx;
657 : ulong estimated_tps_history[ FD_GUI_TPS_HISTORY_SAMPLE_CNT ][ 3UL ];
658 :
659 : fd_gui_txn_waterfall_t txn_waterfall_reference[ 1 ];
660 : fd_gui_txn_waterfall_t txn_waterfall_current[ 1 ];
661 :
662 : fd_gui_tile_stats_t tile_stats_reference[ 1 ];
663 : fd_gui_tile_stats_t tile_stats_current[ 1 ];
664 :
665 : ulong tile_timers_snap_idx;
666 : ulong tile_timers_snap_idx_slot_start;
667 : /* Temporary storage for samples. Will be downsampled into leader history on slot end. */
668 : fd_gui_tile_timers_t tile_timers_snap[ FD_GUI_TILE_TIMER_SNAP_CNT ][ FD_GUI_TILE_TIMER_TILE_CNT ];
669 : } summary;
670 :
671 : fd_gui_slot_t slots[ FD_GUI_SLOTS_CNT ][ 1 ];
672 :
673 : fd_gui_leader_slot_t leader_slots[ FD_GUI_LEADER_CNT ][ 1 ];
674 : ulong leader_slots_cnt;
675 :
676 : fd_gui_txn_t txs[ FD_GUI_TXN_HISTORY_SZ ][ 1 ];
677 : ulong pack_txn_idx; /* The pack index of the most recently received transaction */
678 :
679 : struct {
680 : int has_block_engine;
681 : char name[ 16 ];
682 : char url[ 256 ];
683 : char ip_cstr[ 40 ]; /* IPv4 or IPv6 cstr */
684 : int status;
685 : } block_engine;
686 :
687 : struct {
688 : int has_epoch[ 2 ];
689 :
690 : struct {
691 : ulong epoch;
692 : long start_time;
693 : long end_time;
694 :
695 : ulong my_total_slots;
696 : ulong my_skipped_slots;
697 :
698 : ulong start_slot;
699 : ulong end_slot;
700 : ulong excluded_stake;
701 : fd_epoch_leaders_t * lsched;
702 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) _lsched[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_STAKED_LEADERS, MAX_SLOTS_PER_EPOCH) ];
703 : fd_vote_stake_weight_t stakes[ MAX_STAKED_LEADERS ];
704 :
705 : ulong rankings_slot; /* One more than the largest slot we've processed into our rankings */
706 : fd_gui_slot_rankings_t rankings[ 1 ]; /* global slot rankings */
707 : fd_gui_slot_rankings_t my_rankings[ 1 ]; /* my slots only */
708 : } epochs[ 2 ];
709 : } epoch;
710 :
711 : struct { /* frankendancer only */
712 : ulong peer_cnt;
713 : struct fd_gui_gossip_peer peers[ FD_GUI_MAX_PEER_CNT ];
714 : } gossip;
715 :
716 : struct { /* frankendancer only */
717 : ulong vote_account_cnt;
718 : struct fd_gui_vote_account vote_accounts[ FD_GUI_MAX_PEER_CNT ];
719 : } vote_account;
720 :
721 : struct { /* frankendancer only */
722 : ulong info_cnt;
723 : struct fd_gui_validator_info info[ FD_GUI_MAX_PEER_CNT ];
724 : } validator_info;
725 :
726 : fd_gui_peers_ctx_t * peers; /* full-client */
727 :
728 : struct {
729 : ulong staged_next_broadcast; /* staged[ staged_next_broadcast % FD_GUI_SHREDS_STAGING_SZ ] is the first shred event
730 : that hasn't yet been broadcast to WebSocket clients */
731 : ulong staged_head; /* staged_head % FD_GUI_SHREDS_STAGING_SZ is the valid event in staged */
732 : ulong staged_tail; /* staged_head % FD_GUI_SHREDS_STAGING_SZ is the last valid event in staged */
733 : fd_gui_slot_staged_shred_event_t staged [ FD_GUI_SHREDS_STAGING_SZ ];
734 :
735 : ulong history_slot; /* the largest slot store in history */
736 : ulong history_tail; /* history_tail % FD_GUI_SHREDS_STAGING_SZ is the last valid event in history +1 */
737 : fd_gui_slot_history_shred_event_t history[ FD_GUI_SHREDS_HISTORY_SZ ];
738 : } shreds; /* full client */
739 : };
740 :
741 : typedef struct fd_gui fd_gui_t;
742 :
743 : FD_PROTOTYPES_BEGIN
744 :
745 : FD_FN_CONST ulong
746 : fd_gui_align( void );
747 :
748 : FD_FN_CONST ulong
749 : fd_gui_footprint( void );
750 :
751 : void *
752 : fd_gui_new( void * shmem,
753 : fd_http_server_t * http,
754 : char const * version,
755 : char const * cluster,
756 : uchar const * identity_key,
757 : int has_vote_key,
758 : uchar const * vote_key,
759 : int is_full_client,
760 : int snapshots_enabled,
761 : int is_voting,
762 : int schedule_strategy,
763 : fd_topo_t * topo,
764 : long now );
765 :
766 : fd_gui_t *
767 : fd_gui_join( void * shmem );
768 :
769 : void
770 : fd_gui_set_identity( fd_gui_t * gui,
771 : uchar const * identity_pubkey );
772 :
773 : void
774 : fd_gui_ws_open( fd_gui_t * gui,
775 : ulong conn_id );
776 :
777 : int
778 : fd_gui_ws_message( fd_gui_t * gui,
779 : ulong ws_conn_id,
780 : uchar const * data,
781 : ulong data_len );
782 :
783 : void
784 : fd_gui_plugin_message( fd_gui_t * gui,
785 : ulong plugin_msg,
786 : uchar const * msg,
787 : long now );
788 :
789 : void
790 : fd_gui_became_leader( fd_gui_t * gui,
791 : ulong slot,
792 : long start_time_nanos,
793 : long end_time_nanos,
794 : ulong max_compute_units,
795 : ulong max_microblocks );
796 :
797 : void
798 : fd_gui_unbecame_leader( fd_gui_t * gui,
799 : ulong slot,
800 : ulong microblocks_in_slot );
801 :
802 : void
803 : fd_gui_microblock_execution_begin( fd_gui_t * gui,
804 : long now,
805 : ulong _slot,
806 : fd_txn_p_t * txns,
807 : ulong txn_cnt,
808 : uint microblock_idx,
809 : ulong pack_txn_idx );
810 :
811 : void
812 : fd_gui_microblock_execution_end( fd_gui_t * gui,
813 : long now,
814 : ulong bank_idx,
815 : ulong _slot,
816 : ulong txn_cnt,
817 : fd_txn_p_t * txns,
818 : ulong pack_txn_idx,
819 : uchar txn_start_pct,
820 : uchar txn_load_end_pct,
821 : uchar txn_end_pct,
822 : uchar txn_preload_end_pct,
823 : ulong tips );
824 :
825 : int
826 : fd_gui_poll( fd_gui_t * gui, long now );
827 :
828 : void
829 : fd_gui_handle_shred( fd_gui_t * gui,
830 : ulong slot,
831 : ulong shred_idx,
832 : ulong fec_idx,
833 : int is_turbine,
834 : long tsorig );
835 :
836 : void
837 : fd_gui_handle_repair_slot( fd_gui_t * gui, ulong slot, long now );
838 :
839 : void
840 : fd_gui_handle_snapshot_update( fd_gui_t * gui,
841 : fd_snaprd_update_t const * msg );
842 :
843 : void
844 : fd_gui_handle_tower_update( fd_gui_t * gui,
845 : fd_tower_slot_done_t const * msg,
846 : long now );
847 :
848 : void
849 : fd_gui_handle_replay_update( fd_gui_t * gui,
850 : fd_replay_slot_completed_t const * msg,
851 : long now );
852 :
853 : static inline fd_gui_slot_t *
854 0 : fd_gui_get_slot( fd_gui_t const * gui, ulong _slot ) {
855 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
856 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || _slot==ULONG_MAX || slot->slot!=_slot ) ) return NULL;
857 0 : return (fd_gui_slot_t *)slot;
858 0 : }
859 :
860 : static inline fd_gui_slot_t const *
861 0 : fd_gui_get_slot_const( fd_gui_t const * gui, ulong _slot ) {
862 0 : return fd_gui_get_slot( gui, _slot );
863 0 : }
864 :
865 : static inline fd_gui_leader_slot_t *
866 0 : fd_gui_get_leader_slot( fd_gui_t const * gui, ulong _slot ) {
867 0 : fd_gui_slot_t const * slot = fd_gui_get_slot( gui, _slot );
868 0 : if( FD_UNLIKELY( !slot
869 0 : || !slot->mine
870 0 : || slot->leader_history_idx==ULONG_MAX
871 0 : || slot->leader_history_idx + FD_GUI_LEADER_CNT < gui->leader_slots_cnt
872 0 : || gui->leader_slots[ slot->leader_history_idx % FD_GUI_LEADER_CNT ]->slot!=_slot ) ) return NULL;
873 0 : return (fd_gui_leader_slot_t *)gui->leader_slots[ slot->leader_history_idx % FD_GUI_LEADER_CNT ];
874 0 : }
875 :
876 : static inline fd_gui_leader_slot_t const *
877 0 : fd_gui_get_leader_slot_const( fd_gui_t const * gui, ulong _slot ) {
878 0 : return fd_gui_get_leader_slot( gui, _slot );
879 0 : }
880 :
881 : /* fd_gui_get_root_slot returns a handle to the closest ancestor of slot
882 : that is a root, if available, otherwise NULL. */
883 : static inline fd_gui_slot_t *
884 : fd_gui_get_root_slot( fd_gui_t const * gui,
885 0 : ulong slot ) {
886 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, slot );
887 0 : while( c ) {
888 0 : if( FD_UNLIKELY( c->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) return c;
889 0 : c = fd_gui_get_slot( gui, c->parent_slot );
890 0 : }
891 0 : return NULL;
892 0 : }
893 :
894 : /* fd_gui_slot_is_ancestor returns 1 if anc is known to be an ancestor
895 : of slot (on the same fork), 0 otherwise. */
896 : static inline int
897 : fd_gui_slot_is_ancestor( fd_gui_t const * gui,
898 : ulong anc,
899 0 : ulong slot ) {
900 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, slot );
901 0 : while( c ) {
902 0 : if( FD_UNLIKELY( c->slot==anc ) ) return 1;
903 0 : c = fd_gui_get_slot( gui, c->parent_slot );
904 0 : }
905 0 : return 0;
906 0 : }
907 :
908 : /* fd_gui_get_parent_slot_on_fork returns a handle to the parent of slot
909 : on the fork ending on frontier_slot. If slot is unknown or skipped,
910 : the closest (by slot number) valid parent on the fork is returned.
911 :
912 : NULL if slot is not an ancestor of frontier slot or if the parent is
913 : unknown. */
914 : static inline fd_gui_slot_t *
915 : fd_gui_get_parent_slot_on_fork( fd_gui_t const * gui,
916 : ulong frontier_slot,
917 0 : ulong slot ) {
918 0 : fd_gui_slot_t * c = fd_gui_get_slot( gui, frontier_slot );
919 0 : while( c ) {
920 0 : if( FD_UNLIKELY( c->slot<=slot ) ) return NULL;
921 0 : fd_gui_slot_t * p = fd_gui_get_slot( gui, c->parent_slot );
922 0 : if( FD_UNLIKELY( p && p->slot<=slot-1UL ) ) return p;
923 0 : c = p;
924 0 : }
925 0 : return NULL;
926 0 : }
927 :
928 : /* fd_gui_is_skipped_on_fork returns 1 if slot is skipped on the fork
929 : starting at anc and ending at des, 0 otherwise. */
930 : static inline int
931 : fd_gui_is_skipped_on_fork( fd_gui_t const * gui,
932 : ulong anc,
933 : ulong des,
934 0 : ulong slot ) {
935 0 : fd_gui_slot_t const * c = fd_gui_get_slot( gui, des );
936 0 : while( c ) {
937 0 : if( FD_UNLIKELY( anc==c->slot ) ) return 0; /* on the fork, not skipped */
938 0 : fd_gui_slot_t const * p = fd_gui_get_slot( gui, c->parent_slot );
939 0 : if( FD_UNLIKELY( p && p->slot<slot && c->slot>slot ) ) return 1; /* in-between two nodes, skipped */
940 0 : c = p;
941 0 : }
942 :
943 0 : return 0; /* slot not between anc and des, or is unknown */
944 0 : }
945 :
946 : FD_PROTOTYPES_END
947 :
948 : #endif /* HEADER_fd_src_disco_gui_fd_gui_h */
|