Line data Source code
1 : #include "fd_gui.h"
2 : #include "fd_gui_printf.h"
3 :
4 : #include "../metrics/fd_metrics.h"
5 : #include "../plugin/fd_plugin.h"
6 :
7 : #include "../../ballet/base58/fd_base58.h"
8 : #include "../../ballet/json/cJSON.h"
9 : #include "../../disco/genesis/fd_genesis_cluster.h"
10 : #include "../../disco/pack/fd_pack.h"
11 : #include "../../disco/pack/fd_pack_cost.h"
12 : #include "../../disco/shred/fd_stake_ci.h"
13 :
14 : FD_FN_CONST ulong
15 0 : fd_gui_align( void ) {
16 0 : return 128UL;
17 0 : }
18 :
19 : FD_FN_CONST ulong
20 0 : fd_gui_footprint( void ) {
21 0 : return sizeof(fd_gui_t);
22 0 : }
23 :
24 : void *
25 : fd_gui_new( void * shmem,
26 : fd_http_server_t * http,
27 : char const * version,
28 : char const * cluster,
29 : uchar const * identity_key,
30 : int has_vote_key,
31 : uchar const * vote_key,
32 : int is_voting,
33 : int schedule_strategy,
34 0 : fd_topo_t * topo ) {
35 :
36 0 : if( FD_UNLIKELY( !shmem ) ) {
37 0 : FD_LOG_WARNING(( "NULL shmem" ));
38 0 : return NULL;
39 0 : }
40 :
41 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_align() ) ) ) {
42 0 : FD_LOG_WARNING(( "misaligned shmem" ));
43 0 : return NULL;
44 0 : }
45 :
46 0 : if( FD_UNLIKELY( topo->tile_cnt>FD_GUI_TILE_TIMER_TILE_CNT ) ) {
47 0 : FD_LOG_WARNING(( "too many tiles" ));
48 0 : return NULL;
49 0 : }
50 :
51 0 : fd_gui_t * gui = (fd_gui_t *)shmem;
52 :
53 0 : gui->http = http;
54 0 : gui->topo = topo;
55 :
56 0 : gui->debug_in_leader_slot = ULONG_MAX;
57 0 : gui->summary.schedule_strategy = schedule_strategy;
58 :
59 :
60 0 : gui->next_sample_400millis = fd_log_wallclock();
61 0 : gui->next_sample_100millis = gui->next_sample_400millis;
62 0 : gui->next_sample_10millis = gui->next_sample_400millis;
63 :
64 0 : memcpy( gui->summary.identity_key->uc, identity_key, 32UL );
65 0 : fd_base58_encode_32( identity_key, NULL, gui->summary.identity_key_base58 );
66 0 : gui->summary.identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
67 :
68 0 : if( FD_LIKELY( has_vote_key ) ) {
69 0 : gui->summary.has_vote_key = 1;
70 0 : memcpy( gui->summary.vote_key->uc, vote_key, 32UL );
71 0 : fd_base58_encode_32( vote_key, NULL, gui->summary.vote_key_base58 );
72 0 : gui->summary.vote_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
73 0 : } else {
74 0 : gui->summary.has_vote_key = 0;
75 0 : memset( gui->summary.vote_key_base58, 0, sizeof(gui->summary.vote_key_base58) );
76 0 : }
77 :
78 0 : gui->summary.version = version;
79 0 : gui->summary.cluster = cluster;
80 0 : gui->summary.startup_time_nanos = gui->next_sample_400millis;
81 :
82 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_INITIALIZING;
83 0 : gui->summary.startup_progress.startup_got_full_snapshot = 0;
84 0 : gui->summary.startup_progress.startup_full_snapshot_slot = 0;
85 0 : gui->summary.startup_progress.startup_incremental_snapshot_slot = 0;
86 0 : gui->summary.startup_progress.startup_waiting_for_supermajority_slot = ULONG_MAX;
87 0 : gui->summary.startup_progress.startup_ledger_max_slot = ULONG_MAX;
88 :
89 0 : gui->summary.identity_account_balance = 0UL;
90 0 : gui->summary.vote_account_balance = 0UL;
91 0 : gui->summary.estimated_slot_duration_nanos = 0UL;
92 :
93 0 : gui->summary.vote_distance = 0UL;
94 0 : gui->summary.vote_state = is_voting ? FD_GUI_VOTE_STATE_VOTING : FD_GUI_VOTE_STATE_NON_VOTING;
95 :
96 0 : gui->summary.sock_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "sock" );
97 0 : gui->summary.net_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "net" );
98 0 : gui->summary.quic_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "quic" );
99 0 : gui->summary.verify_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "verify" );
100 0 : gui->summary.resolv_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "resolv" );
101 0 : gui->summary.bank_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "bank" );
102 0 : gui->summary.shred_tile_cnt = fd_topo_tile_name_cnt( gui->topo, "shred" );
103 :
104 0 : gui->summary.slot_rooted = 0UL;
105 0 : gui->summary.slot_optimistically_confirmed = 0UL;
106 0 : gui->summary.slot_completed = 0UL;
107 0 : gui->summary.slot_estimated = 0UL;
108 :
109 0 : gui->summary.estimated_tps_history_idx = 0UL;
110 0 : memset( gui->summary.estimated_tps_history, 0, sizeof(gui->summary.estimated_tps_history) );
111 :
112 0 : memset( gui->summary.txn_waterfall_reference, 0, sizeof(gui->summary.txn_waterfall_reference) );
113 0 : memset( gui->summary.txn_waterfall_current, 0, sizeof(gui->summary.txn_waterfall_current) );
114 :
115 0 : memset( gui->summary.tile_stats_reference, 0, sizeof(gui->summary.tile_stats_reference) );
116 0 : memset( gui->summary.tile_stats_current, 0, sizeof(gui->summary.tile_stats_current) );
117 :
118 0 : memset( gui->summary.tile_timers_snap[ 0 ], 0, sizeof(gui->summary.tile_timers_snap[ 0 ]) );
119 0 : memset( gui->summary.tile_timers_snap[ 1 ], 0, sizeof(gui->summary.tile_timers_snap[ 1 ]) );
120 0 : gui->summary.tile_timers_snap_idx = 2UL;
121 0 : gui->summary.tile_timers_history_idx = 0UL;
122 0 : for( ulong i=0UL; i<FD_GUI_TILE_TIMER_LEADER_CNT; i++ ) gui->summary.tile_timers_leader_history_slot[ i ] = ULONG_MAX;
123 :
124 0 : gui->block_engine.has_block_engine = 0;
125 :
126 0 : gui->epoch.has_epoch[ 0 ] = 0;
127 0 : gui->epoch.has_epoch[ 1 ] = 0;
128 :
129 0 : gui->gossip.peer_cnt = 0UL;
130 0 : gui->vote_account.vote_account_cnt = 0UL;
131 0 : gui->validator_info.info_cnt = 0UL;
132 :
133 0 : for( ulong i=0UL; i<FD_GUI_SLOTS_CNT; i++ ) gui->slots[ i ]->slot = ULONG_MAX;
134 0 : gui->pack_txn_idx = 0UL;
135 :
136 0 : return gui;
137 0 : }
138 :
139 : fd_gui_t *
140 0 : fd_gui_join( void * shmem ) {
141 0 : return (fd_gui_t *)shmem;
142 0 : }
143 :
144 : void
145 : fd_gui_set_identity( fd_gui_t * gui,
146 0 : uchar const * identity_pubkey ) {
147 0 : memcpy( gui->summary.identity_key->uc, identity_pubkey, 32UL );
148 0 : fd_base58_encode_32( identity_pubkey, NULL, gui->summary.identity_key_base58 );
149 0 : gui->summary.identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0';
150 :
151 0 : fd_gui_printf_identity_key( gui );
152 0 : fd_http_server_ws_broadcast( gui->http );
153 :
154 0 : fd_gui_printf_identity_balance( gui );
155 0 : fd_http_server_ws_broadcast( gui->http );
156 0 : }
157 :
158 : void
159 : fd_gui_ws_open( fd_gui_t * gui,
160 0 : ulong ws_conn_id ) {
161 0 : void (* printers[] )( fd_gui_t * gui ) = {
162 0 : fd_gui_printf_startup_progress,
163 0 : fd_gui_printf_version,
164 0 : fd_gui_printf_cluster,
165 0 : fd_gui_printf_commit_hash,
166 0 : fd_gui_printf_identity_key,
167 0 : fd_gui_printf_vote_key,
168 0 : fd_gui_printf_startup_time_nanos,
169 0 : fd_gui_printf_vote_state,
170 0 : fd_gui_printf_vote_distance,
171 0 : fd_gui_printf_skipped_history,
172 0 : fd_gui_printf_skipped_history_cluster,
173 0 : fd_gui_printf_tps_history,
174 0 : fd_gui_printf_tiles,
175 0 : fd_gui_printf_schedule_strategy,
176 0 : fd_gui_printf_identity_balance,
177 0 : fd_gui_printf_vote_balance,
178 0 : fd_gui_printf_estimated_slot_duration_nanos,
179 0 : fd_gui_printf_root_slot,
180 0 : fd_gui_printf_optimistically_confirmed_slot,
181 0 : fd_gui_printf_completed_slot,
182 0 : fd_gui_printf_estimated_slot,
183 0 : fd_gui_printf_live_tile_timers,
184 0 : };
185 :
186 0 : ulong printers_len = sizeof(printers) / sizeof(printers[0]);
187 0 : for( ulong i=0UL; i<printers_len; i++ ) {
188 0 : printers[ i ]( gui );
189 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
190 0 : }
191 :
192 0 : if( FD_LIKELY( gui->block_engine.has_block_engine ) ) {
193 0 : fd_gui_printf_block_engine( gui );
194 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
195 0 : }
196 :
197 0 : for( ulong i=0UL; i<2UL; i++ ) {
198 0 : if( FD_LIKELY( gui->epoch.has_epoch[ i ] ) ) {
199 0 : fd_gui_printf_skip_rate( gui, i );
200 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
201 0 : fd_gui_printf_epoch( gui, i );
202 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
203 0 : }
204 0 : }
205 :
206 : /* Print peers last because it's the largest message and would
207 : block other information. */
208 0 : fd_gui_printf_peers_all( gui );
209 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
210 0 : }
211 :
212 : static void
213 0 : fd_gui_tile_timers_snap( fd_gui_t * gui ) {
214 0 : fd_gui_tile_timers_t * cur = gui->summary.tile_timers_snap[ gui->summary.tile_timers_snap_idx ];
215 0 : gui->summary.tile_timers_snap_idx = (gui->summary.tile_timers_snap_idx+1UL)%FD_GUI_TILE_TIMER_SNAP_CNT;
216 0 : for( ulong i=0UL; i<gui->topo->tile_cnt; i++ ) {
217 0 : fd_topo_tile_t * tile = &gui->topo->tiles[ i ];
218 0 : if ( FD_UNLIKELY( !tile->metrics ) ) {
219 : /* bench tiles might not have been booted initially.
220 : This check shouldn't be necessary if all tiles barrier after boot. */
221 : // TODO(FIXME) this probably isn't the right fix but it makes fddev bench work for now
222 0 : return;
223 0 : }
224 0 : volatile ulong const * tile_metrics = fd_metrics_tile( tile->metrics );
225 :
226 0 : cur[ i ].caughtup_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_HOUSEKEEPING ) ];
227 0 : cur[ i ].processing_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_HOUSEKEEPING ) ];
228 0 : cur[ i ].backpressure_housekeeping_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_HOUSEKEEPING ) ];
229 0 : cur[ i ].caughtup_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_PREFRAG ) ];
230 0 : cur[ i ].processing_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_PREFRAG ) ];
231 0 : cur[ i ].backpressure_prefrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_BACKPRESSURE_PREFRAG ) ];
232 0 : cur[ i ].caughtup_postfrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_CAUGHT_UP_POSTFRAG ) ];
233 0 : cur[ i ].processing_postfrag_ticks = tile_metrics[ MIDX( COUNTER, TILE, REGIME_DURATION_NANOS_PROCESSING_POSTFRAG ) ];
234 0 : }
235 0 : }
236 :
237 : static void
238 0 : fd_gui_estimated_tps_snap( fd_gui_t * gui ) {
239 0 : ulong total_txn_cnt = 0UL;
240 0 : ulong vote_txn_cnt = 0UL;
241 0 : ulong nonvote_failed_txn_cnt = 0UL;
242 :
243 0 : for( ulong i=0UL; i<fd_ulong_min( gui->summary.slot_completed+1UL, FD_GUI_SLOTS_CNT ); i++ ) {
244 0 : ulong _slot = gui->summary.slot_completed-i;
245 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
246 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || slot->slot!=_slot ) ) break; /* Slot no longer exists, no TPS. */
247 0 : if( FD_UNLIKELY( slot->completed_time==LONG_MAX ) ) continue; /* Slot is on this fork but was never completed, must have been in root path on boot. */
248 0 : if( FD_UNLIKELY( slot->completed_time+FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS*1000L*1000L*1000L<gui->next_sample_400millis ) ) break; /* Slot too old. */
249 0 : if( FD_UNLIKELY( slot->skipped ) ) continue; /* Skipped slots don't count to TPS. */
250 :
251 0 : total_txn_cnt += slot->total_txn_cnt;
252 0 : vote_txn_cnt += slot->vote_txn_cnt;
253 0 : nonvote_failed_txn_cnt += slot->nonvote_failed_txn_cnt;
254 0 : }
255 :
256 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 0 ] = total_txn_cnt;
257 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 1 ] = vote_txn_cnt;
258 0 : gui->summary.estimated_tps_history[ gui->summary.estimated_tps_history_idx ][ 2 ] = nonvote_failed_txn_cnt;
259 0 : gui->summary.estimated_tps_history_idx = (gui->summary.estimated_tps_history_idx+1UL) % FD_GUI_TPS_HISTORY_SAMPLE_CNT;
260 0 : }
261 :
262 : /* Snapshot all of the data from metrics to construct a view of the
263 : transaction waterfall.
264 :
265 : Tiles are sampled in reverse pipeline order: this helps prevent data
266 : discrepancies where a later tile has "seen" more transactions than an
267 : earlier tile, which shouldn't typically happen. */
268 :
269 : static void
270 : fd_gui_txn_waterfall_snap( fd_gui_t * gui,
271 0 : fd_gui_txn_waterfall_t * cur ) {
272 0 : fd_topo_t * topo = gui->topo;
273 :
274 0 : cur->out.block_success = 0UL;
275 0 : cur->out.block_fail = 0UL;
276 :
277 0 : cur->out.bank_invalid = 0UL;
278 0 : for( ulong i=0UL; i<gui->summary.bank_tile_cnt; i++ ) {
279 0 : fd_topo_tile_t const * bank = &topo->tiles[ fd_topo_find_tile( topo, "bank", i ) ];
280 :
281 0 : volatile ulong const * bank_metrics = fd_metrics_tile( bank->metrics );
282 :
283 0 : cur->out.block_success += bank_metrics[ MIDX( COUNTER, BANK, SUCCESSFUL_TRANSACTIONS ) ];
284 :
285 0 : cur->out.block_fail +=
286 0 : bank_metrics[ MIDX( COUNTER, BANK, EXECUTED_FAILED_TRANSACTIONS ) ]
287 0 : + bank_metrics[ MIDX( COUNTER, BANK, FEE_ONLY_TRANSACTIONS ) ];
288 :
289 0 : cur->out.bank_invalid +=
290 0 : bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_SLOT_HASHES_SYSVAR_NOT_FOUND ) ]
291 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_ACCOUNT_NOT_FOUND ) ]
292 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_ACCOUNT_OWNER ) ]
293 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_ACCOUNT_DATA ) ]
294 0 : + bank_metrics[ MIDX( COUNTER, BANK, TRANSACTION_LOAD_ADDRESS_TABLES_INVALID_INDEX ) ];
295 :
296 0 : cur->out.bank_invalid +=
297 0 : bank_metrics[ MIDX( COUNTER, BANK, PROCESSING_FAILED ) ];
298 0 : }
299 :
300 :
301 0 : fd_topo_tile_t const * pack = &topo->tiles[ fd_topo_find_tile( topo, "pack", 0UL ) ];
302 0 : volatile ulong const * pack_metrics = fd_metrics_tile( pack->metrics );
303 :
304 0 : cur->out.pack_invalid_bundle =
305 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_PARTIAL_BUNDLE ) ]
306 0 : + pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_INSERTION_FAILED ) ]
307 0 : + pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_CREATION_FAILED ) ];
308 :
309 0 : cur->out.pack_invalid =
310 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_NONCE_CONFLICT ) ]
311 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_BUNDLE_BLACKLIST ) ]
312 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_INVALID_NONCE ) ]
313 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_WRITE_SYSVAR ) ]
314 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_ESTIMATION_FAIL ) ]
315 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_DUPLICATE_ACCOUNT ) ]
316 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TOO_MANY_ACCOUNTS ) ]
317 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TOO_LARGE ) ]
318 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_ADDR_LUT ) ]
319 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_UNAFFORDABLE ) ]
320 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_DUPLICATE ) ]
321 0 : - pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_INSERTION_FAILED ) ]; /* so we don't double count this, since its already accounted for in invalid_bundle */
322 :
323 0 : cur->out.pack_expired = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_EXPIRED ) ] +
324 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_EXPIRED ) ] +
325 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DELETED ) ] +
326 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_NONCE_PRIORITY ) ];
327 :
328 0 : cur->out.pack_leader_slow = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_PRIORITY ) ];
329 :
330 0 : cur->out.pack_wait_full =
331 0 : pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_FROM_EXTRA ) ];
332 :
333 0 : cur->out.pack_retained = pack_metrics[ MIDX( GAUGE, PACK, AVAILABLE_TRANSACTIONS ) ];
334 :
335 0 : ulong inserted_to_extra = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_TO_EXTRA ) ];
336 0 : ulong inserted_from_extra = pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_INSERTED_FROM_EXTRA ) ]
337 0 : + pack_metrics[ MIDX( COUNTER, PACK, TRANSACTION_DROPPED_FROM_EXTRA ) ];
338 0 : cur->out.pack_retained += fd_ulong_if( inserted_to_extra>=inserted_from_extra, inserted_to_extra-inserted_from_extra, 0UL );
339 :
340 0 : cur->out.resolv_lut_failed = 0UL;
341 0 : cur->out.resolv_expired = 0UL;
342 0 : cur->out.resolv_ancient = 0UL;
343 0 : cur->out.resolv_no_ledger = 0UL;
344 0 : cur->out.resolv_retained = 0UL;
345 0 : for( ulong i=0UL; i<gui->summary.resolv_tile_cnt; i++ ) {
346 0 : fd_topo_tile_t const * resolv = &topo->tiles[ fd_topo_find_tile( topo, "resolv", i ) ];
347 0 : volatile ulong const * resolv_metrics = fd_metrics_tile( resolv->metrics );
348 :
349 0 : cur->out.resolv_no_ledger += resolv_metrics[ MIDX( COUNTER, RESOLV, NO_BANK_DROP ) ];
350 0 : cur->out.resolv_expired += resolv_metrics[ MIDX( COUNTER, RESOLV, BLOCKHASH_EXPIRED ) ]
351 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
352 0 : cur->out.resolv_lut_failed += resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_ACCOUNT_NOT_FOUND ) ]
353 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_ACCOUNT_OWNER ) ]
354 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_ACCOUNT_DATA ) ]
355 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_ACCOUNT_UNINITIALIZED ) ]
356 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, LUT_RESOLVED_INVALID_LOOKUP_INDEX ) ];
357 0 : cur->out.resolv_ancient += resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_OVERRUN ) ];
358 :
359 0 : ulong inserted_to_resolv = resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_INSERTED ) ];
360 0 : ulong removed_from_resolv = resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_OVERRUN ) ]
361 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_PUBLISHED ) ]
362 0 : + resolv_metrics[ MIDX( COUNTER, RESOLV, STASH_OPERATION_REMOVED ) ];
363 0 : cur->out.resolv_retained += fd_ulong_if( inserted_to_resolv>=removed_from_resolv, inserted_to_resolv-removed_from_resolv, 0UL );
364 0 : }
365 :
366 :
367 0 : fd_topo_tile_t const * dedup = &topo->tiles[ fd_topo_find_tile( topo, "dedup", 0UL ) ];
368 0 : volatile ulong const * dedup_metrics = fd_metrics_tile( dedup->metrics );
369 :
370 0 : cur->out.dedup_duplicate = dedup_metrics[ MIDX( COUNTER, DEDUP, TRANSACTION_DEDUP_FAILURE ) ]
371 0 : + dedup_metrics[ MIDX( COUNTER, DEDUP, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
372 :
373 :
374 0 : cur->out.verify_overrun = 0UL;
375 0 : cur->out.verify_duplicate = 0UL;
376 0 : cur->out.verify_parse = 0UL;
377 0 : cur->out.verify_failed = 0UL;
378 :
379 0 : for( ulong i=0UL; i<gui->summary.verify_tile_cnt; i++ ) {
380 0 : fd_topo_tile_t const * verify = &topo->tiles[ fd_topo_find_tile( topo, "verify", i ) ];
381 0 : volatile ulong const * verify_metrics = fd_metrics_tile( verify->metrics );
382 :
383 0 : for( ulong j=0UL; j<gui->summary.quic_tile_cnt; j++ ) {
384 : /* TODO: Not precise... even if 1 frag gets skipped, it could have been for this verify tile. */
385 0 : cur->out.verify_overrun += fd_metrics_link_in( verify->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_POLLING_FRAG_COUNT_OFF ] / gui->summary.verify_tile_cnt;
386 0 : cur->out.verify_overrun += fd_metrics_link_in( verify->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_READING_FRAG_COUNT_OFF ];
387 0 : }
388 :
389 0 : cur->out.verify_failed += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_VERIFY_FAILURE ) ] +
390 0 : verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_BUNDLE_PEER_FAILURE ) ];
391 0 : cur->out.verify_parse += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_PARSE_FAILURE ) ];
392 0 : cur->out.verify_duplicate += verify_metrics[ MIDX( COUNTER, VERIFY, TRANSACTION_DEDUP_FAILURE ) ];
393 0 : }
394 :
395 :
396 0 : cur->out.quic_overrun = 0UL;
397 0 : cur->out.quic_frag_drop = 0UL;
398 0 : cur->out.quic_abandoned = 0UL;
399 0 : cur->out.tpu_quic_invalid = 0UL;
400 0 : cur->out.tpu_udp_invalid = 0UL;
401 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
402 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
403 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
404 :
405 0 : cur->out.tpu_udp_invalid += quic_metrics[ MIDX( COUNTER, QUIC, LEGACY_TXN_UNDERSZ ) ];
406 0 : cur->out.tpu_udp_invalid += quic_metrics[ MIDX( COUNTER, QUIC, LEGACY_TXN_OVERSZ ) ];
407 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_UNDERSZ ) ];
408 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_OVERSZ ) ];
409 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, TXN_OVERSZ ) ];
410 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_CRYPTO_FAILED ) ];
411 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_NO_CONN ) ];
412 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_NET_HEADER_INVALID ) ];
413 0 : cur->out.tpu_quic_invalid += quic_metrics[ MIDX( COUNTER, QUIC, PKT_QUIC_HEADER_INVALID ) ];
414 0 : cur->out.quic_abandoned += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_ABANDONED ) ];
415 0 : cur->out.quic_frag_drop += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_OVERRUN ) ];
416 :
417 0 : for( ulong j=0UL; j<gui->summary.net_tile_cnt; j++ ) {
418 : /* TODO: Not precise... net frags that were skipped might not have been destined for QUIC tile */
419 : /* TODO: Not precise... even if 1 frag gets skipped, it could have been for this QUIC tile */
420 0 : cur->out.quic_overrun += fd_metrics_link_in( quic->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_POLLING_FRAG_COUNT_OFF ] / gui->summary.quic_tile_cnt;
421 0 : cur->out.quic_overrun += fd_metrics_link_in( quic->metrics, j )[ FD_METRICS_COUNTER_LINK_OVERRUN_READING_FRAG_COUNT_OFF ];
422 0 : }
423 0 : }
424 :
425 0 : cur->out.net_overrun = 0UL;
426 0 : for( ulong i=0UL; i<gui->summary.net_tile_cnt; i++ ) {
427 0 : fd_topo_tile_t const * net = &topo->tiles[ fd_topo_find_tile( topo, "net", i ) ];
428 0 : volatile ulong * net_metrics = fd_metrics_tile( net->metrics );
429 :
430 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_RING_FULL ) ];
431 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_DROPPED_OTHER ) ];
432 0 : cur->out.net_overrun += net_metrics[ MIDX( COUNTER, NET, XDP_RX_FILL_RING_EMPTY_DESCS ) ];
433 0 : }
434 :
435 0 : ulong bundle_txns_received = 0UL;
436 0 : ulong bundle_tile_idx = fd_topo_find_tile( topo, "bundle", 0UL );
437 0 : if( FD_LIKELY( bundle_tile_idx!=ULONG_MAX ) ) {
438 0 : fd_topo_tile_t const * bundle = &topo->tiles[ bundle_tile_idx ];
439 0 : volatile ulong const * bundle_metrics = fd_metrics_tile( bundle->metrics );
440 :
441 0 : bundle_txns_received = bundle_metrics[ MIDX( COUNTER, BUNDLE, TRANSACTION_RECEIVED ) ];
442 0 : }
443 :
444 0 : cur->in.pack_cranked =
445 0 : pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_INSERTED ) ]
446 0 : + pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_INSERTION_FAILED ) ]
447 0 : + pack_metrics[ MIDX( COUNTER, PACK, BUNDLE_CRANK_STATUS_CREATION_FAILED ) ];
448 :
449 0 : cur->in.gossip = dedup_metrics[ MIDX( COUNTER, DEDUP, GOSSIPED_VOTES_RECEIVED ) ];
450 0 : cur->in.quic = cur->out.tpu_quic_invalid +
451 0 : cur->out.quic_overrun +
452 0 : cur->out.quic_frag_drop +
453 0 : cur->out.quic_abandoned +
454 0 : cur->out.net_overrun;
455 0 : cur->in.udp = cur->out.tpu_udp_invalid;
456 0 : cur->in.block_engine = bundle_txns_received;
457 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
458 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
459 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
460 :
461 0 : cur->in.quic += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_QUIC_FAST ) ];
462 0 : cur->in.quic += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_QUIC_FRAG ) ];
463 0 : cur->in.udp += quic_metrics[ MIDX( COUNTER, QUIC, TXNS_RECEIVED_UDP ) ];
464 0 : }
465 0 : }
466 :
467 : static void
468 : fd_gui_tile_stats_snap( fd_gui_t * gui,
469 : fd_gui_txn_waterfall_t const * waterfall,
470 0 : fd_gui_tile_stats_t * stats ) {
471 0 : fd_topo_t const * topo = gui->topo;
472 :
473 0 : stats->sample_time_nanos = fd_log_wallclock();
474 :
475 0 : stats->net_in_rx_bytes = 0UL;
476 0 : stats->net_out_tx_bytes = 0UL;
477 0 : for( ulong i=0UL; i<gui->summary.net_tile_cnt; i++ ) {
478 0 : fd_topo_tile_t const * net = &topo->tiles[ fd_topo_find_tile( topo, "net", i ) ];
479 0 : volatile ulong * net_metrics = fd_metrics_tile( net->metrics );
480 :
481 0 : stats->net_in_rx_bytes += net_metrics[ MIDX( COUNTER, NET, RX_BYTES_TOTAL ) ];
482 0 : stats->net_out_tx_bytes += net_metrics[ MIDX( COUNTER, NET, TX_BYTES_TOTAL ) ];
483 0 : }
484 :
485 0 : for( ulong i=0UL; i<gui->summary.sock_tile_cnt; i++ ) {
486 0 : fd_topo_tile_t const * sock = &topo->tiles[ fd_topo_find_tile( topo, "sock", i ) ];
487 0 : volatile ulong * sock_metrics = fd_metrics_tile( sock->metrics );
488 :
489 0 : stats->net_in_rx_bytes += sock_metrics[ MIDX( COUNTER, SOCK, RX_BYTES_TOTAL ) ];
490 0 : stats->net_out_tx_bytes += sock_metrics[ MIDX( COUNTER, SOCK, TX_BYTES_TOTAL ) ];
491 0 : }
492 :
493 0 : stats->quic_conn_cnt = 0UL;
494 0 : for( ulong i=0UL; i<gui->summary.quic_tile_cnt; i++ ) {
495 0 : fd_topo_tile_t const * quic = &topo->tiles[ fd_topo_find_tile( topo, "quic", i ) ];
496 0 : volatile ulong * quic_metrics = fd_metrics_tile( quic->metrics );
497 :
498 0 : stats->quic_conn_cnt += quic_metrics[ MIDX( GAUGE, QUIC, CONNECTIONS_ALLOC ) ];
499 0 : }
500 :
501 0 : ulong bundle_tile_idx = fd_topo_find_tile( topo, "bundle", 0UL );
502 0 : if( FD_LIKELY( bundle_tile_idx!=ULONG_MAX ) ) {
503 0 : fd_topo_tile_t const * bundle = &topo->tiles[ bundle_tile_idx ];
504 0 : volatile ulong * bundle_metrics = fd_metrics_tile( bundle->metrics );
505 0 : stats->bundle_rtt_smoothed_nanos = bundle_metrics[ MIDX( GAUGE, BUNDLE, RTT_SMOOTHED ) ];
506 :
507 0 : fd_histf_new( &stats->bundle_rx_delay_hist, FD_MHIST_MIN( BUNDLE, MESSAGE_RX_DELAY_NANOS ), FD_MHIST_MAX( BUNDLE, MESSAGE_RX_DELAY_NANOS ) );
508 0 : stats->bundle_rx_delay_hist.sum = bundle_metrics[ MIDX( HISTOGRAM, BUNDLE, MESSAGE_RX_DELAY_NANOS ) + FD_HISTF_BUCKET_CNT ];
509 0 : for( ulong b=0; b<FD_HISTF_BUCKET_CNT; b++ ) stats->bundle_rx_delay_hist.counts[ b ] = bundle_metrics[ MIDX( HISTOGRAM, BUNDLE, MESSAGE_RX_DELAY_NANOS ) + b ];
510 0 : }
511 :
512 0 : stats->verify_drop_cnt = waterfall->out.verify_duplicate +
513 0 : waterfall->out.verify_parse +
514 0 : waterfall->out.verify_failed;
515 0 : stats->verify_total_cnt = waterfall->in.gossip +
516 0 : waterfall->in.quic +
517 0 : waterfall->in.udp -
518 0 : waterfall->out.net_overrun -
519 0 : waterfall->out.tpu_quic_invalid -
520 0 : waterfall->out.tpu_udp_invalid -
521 0 : waterfall->out.quic_abandoned -
522 0 : waterfall->out.quic_frag_drop -
523 0 : waterfall->out.quic_overrun -
524 0 : waterfall->out.verify_overrun;
525 0 : stats->dedup_drop_cnt = waterfall->out.dedup_duplicate;
526 0 : stats->dedup_total_cnt = stats->verify_total_cnt -
527 0 : waterfall->out.verify_duplicate -
528 0 : waterfall->out.verify_parse -
529 0 : waterfall->out.verify_failed;
530 :
531 0 : fd_topo_tile_t const * pack = &topo->tiles[ fd_topo_find_tile( topo, "pack", 0UL ) ];
532 0 : volatile ulong const * pack_metrics = fd_metrics_tile( pack->metrics );
533 0 : stats->pack_buffer_cnt = pack_metrics[ MIDX( GAUGE, PACK, AVAILABLE_TRANSACTIONS ) ];
534 0 : stats->pack_buffer_capacity = pack->pack.max_pending_transactions;
535 :
536 0 : stats->bank_txn_exec_cnt = waterfall->out.block_fail + waterfall->out.block_success;
537 0 : }
538 :
539 : int
540 0 : fd_gui_poll( fd_gui_t * gui ) {
541 0 : long now = fd_log_wallclock();
542 :
543 0 : int did_work = 0;
544 :
545 0 : if( FD_LIKELY( now>gui->next_sample_400millis ) ) {
546 0 : fd_gui_estimated_tps_snap( gui );
547 0 : fd_gui_printf_estimated_tps( gui );
548 0 : fd_http_server_ws_broadcast( gui->http );
549 :
550 0 : gui->next_sample_400millis += 400L*1000L*1000L;
551 0 : did_work = 1;
552 0 : }
553 :
554 0 : if( FD_LIKELY( now>gui->next_sample_100millis ) ) {
555 0 : fd_gui_txn_waterfall_snap( gui, gui->summary.txn_waterfall_current );
556 0 : fd_gui_printf_live_txn_waterfall( gui, gui->summary.txn_waterfall_reference, gui->summary.txn_waterfall_current, 0UL /* TODO: REAL NEXT LEADER SLOT */ );
557 0 : fd_http_server_ws_broadcast( gui->http );
558 :
559 0 : *gui->summary.tile_stats_reference = *gui->summary.tile_stats_current;
560 0 : fd_gui_tile_stats_snap( gui, gui->summary.txn_waterfall_current, gui->summary.tile_stats_current );
561 0 : fd_gui_printf_live_tile_stats( gui, gui->summary.tile_stats_reference, gui->summary.tile_stats_current );
562 0 : fd_http_server_ws_broadcast( gui->http );
563 :
564 0 : gui->next_sample_100millis += 100L*1000L*1000L;
565 0 : did_work = 1;
566 0 : }
567 :
568 0 : if( FD_LIKELY( now>gui->next_sample_10millis ) ) {
569 0 : fd_gui_tile_timers_snap( gui );
570 :
571 0 : fd_gui_printf_live_tile_timers( gui );
572 0 : fd_http_server_ws_broadcast( gui->http );
573 :
574 0 : gui->next_sample_10millis += 10L*1000L*1000L;
575 0 : did_work = 1;
576 0 : }
577 :
578 0 : return did_work;
579 0 : }
580 :
581 : static void
582 : fd_gui_handle_gossip_update( fd_gui_t * gui,
583 0 : uchar const * msg ) {
584 0 : if( FD_UNLIKELY( gui->gossip.peer_cnt == FD_GUI_MAX_PEER_CNT ) ) {
585 0 : FD_LOG_DEBUG(("gossip peer cnt exceeds 40200 %lu, ignoring additional entries", gui->gossip.peer_cnt ));
586 0 : return;
587 0 : }
588 0 : ulong const * header = (ulong const *)fd_type_pun_const( msg );
589 0 : ulong peer_cnt = header[ 0 ];
590 :
591 0 : FD_TEST( peer_cnt<=FD_GUI_MAX_PEER_CNT );
592 :
593 0 : ulong added_cnt = 0UL;
594 0 : ulong added[ FD_GUI_MAX_PEER_CNT ] = {0};
595 :
596 0 : ulong update_cnt = 0UL;
597 0 : ulong updated[ FD_GUI_MAX_PEER_CNT ] = {0};
598 :
599 0 : ulong removed_cnt = 0UL;
600 0 : fd_pubkey_t removed[ FD_GUI_MAX_PEER_CNT ] = {0};
601 :
602 0 : uchar const * data = (uchar const *)(header+1UL);
603 0 : for( ulong i=0UL; i<gui->gossip.peer_cnt; i++ ) {
604 0 : int found = 0;
605 0 : for( ulong j=0UL; j<peer_cnt; j++ ) {
606 0 : if( FD_UNLIKELY( !memcmp( gui->gossip.peers[ i ].pubkey, data+j*(58UL+12UL*6UL), 32UL ) ) ) {
607 0 : found = 1;
608 0 : break;
609 0 : }
610 0 : }
611 :
612 0 : if( FD_UNLIKELY( !found ) ) {
613 0 : fd_memcpy( removed[ removed_cnt++ ].uc, gui->gossip.peers[ i ].pubkey->uc, 32UL );
614 0 : if( FD_LIKELY( i+1UL!=gui->gossip.peer_cnt ) ) {
615 0 : gui->gossip.peers[ i ] = gui->gossip.peers[ gui->gossip.peer_cnt-1UL ];
616 0 : gui->gossip.peer_cnt--;
617 0 : i--;
618 0 : }
619 0 : }
620 0 : }
621 :
622 0 : ulong before_peer_cnt = gui->gossip.peer_cnt;
623 0 : for( ulong i=0UL; i<peer_cnt; i++ ) {
624 0 : int found = 0;
625 0 : ulong found_idx = 0;
626 0 : for( ulong j=0UL; j<gui->gossip.peer_cnt; j++ ) {
627 0 : if( FD_UNLIKELY( !memcmp( gui->gossip.peers[ j ].pubkey, data+i*(58UL+12UL*6UL), 32UL ) ) ) {
628 0 : found_idx = j;
629 0 : found = 1;
630 0 : break;
631 0 : }
632 0 : }
633 :
634 0 : if( FD_UNLIKELY( !found ) ) {
635 0 : fd_memcpy( gui->gossip.peers[ gui->gossip.peer_cnt ].pubkey->uc, data+i*(58UL+12UL*6UL), 32UL );
636 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].wallclock = *(ulong const *)(data+i*(58UL+12UL*6UL)+32UL);
637 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].shred_version = *(ushort const *)(data+i*(58UL+12UL*6UL)+40UL);
638 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].has_version = *(data+i*(58UL+12UL*6UL)+42UL);
639 0 : if( FD_LIKELY( gui->gossip.peers[ gui->gossip.peer_cnt ].has_version ) ) {
640 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.major = *(ushort const *)(data+i*(58UL+12UL*6UL)+43UL);
641 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.minor = *(ushort const *)(data+i*(58UL+12UL*6UL)+45UL);
642 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.patch = *(ushort const *)(data+i*(58UL+12UL*6UL)+47UL);
643 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.has_commit = *(data+i*(58UL+12UL*6UL)+49UL);
644 0 : if( FD_LIKELY( gui->gossip.peers[ gui->gossip.peer_cnt ].version.has_commit ) ) {
645 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.commit = *(uint const *)(data+i*(58UL+12UL*6UL)+50UL);
646 0 : }
647 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].version.feature_set = *(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
648 0 : }
649 :
650 0 : for( ulong j=0UL; j<12UL; j++ ) {
651 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].sockets[ j ].ipv4 = *(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL);
652 0 : gui->gossip.peers[ gui->gossip.peer_cnt ].sockets[ j ].port = *(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
653 0 : }
654 :
655 0 : gui->gossip.peer_cnt++;
656 0 : } else {
657 0 : int peer_updated = gui->gossip.peers[ found_idx ].shred_version!=*(ushort const *)(data+i*(58UL+12UL*6UL)+40UL) ||
658 : // gui->gossip.peers[ found_idx ].wallclock!=*(ulong const *)(data+i*(58UL+12UL*6UL)+32UL) ||
659 0 : gui->gossip.peers[ found_idx ].has_version!=*(data+i*(58UL+12UL*6UL)+42UL);
660 :
661 0 : if( FD_LIKELY( !peer_updated && gui->gossip.peers[ found_idx ].has_version ) ) {
662 0 : peer_updated = gui->gossip.peers[ found_idx ].version.major!=*(ushort const *)(data+i*(58UL+12UL*6UL)+43UL) ||
663 0 : gui->gossip.peers[ found_idx ].version.minor!=*(ushort const *)(data+i*(58UL+12UL*6UL)+45UL) ||
664 0 : gui->gossip.peers[ found_idx ].version.patch!=*(ushort const *)(data+i*(58UL+12UL*6UL)+47UL) ||
665 0 : gui->gossip.peers[ found_idx ].version.has_commit!=*(data+i*(58UL+12UL*6UL)+49UL) ||
666 0 : (gui->gossip.peers[ found_idx ].version.has_commit && gui->gossip.peers[ found_idx ].version.commit!=*(uint const *)(data+i*(58UL+12UL*6UL)+50UL)) ||
667 0 : gui->gossip.peers[ found_idx ].version.feature_set!=*(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
668 0 : }
669 :
670 0 : if( FD_LIKELY( !peer_updated ) ) {
671 0 : for( ulong j=0UL; j<12UL; j++ ) {
672 0 : peer_updated = gui->gossip.peers[ found_idx ].sockets[ j ].ipv4!=*(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL) ||
673 0 : gui->gossip.peers[ found_idx ].sockets[ j ].port!=*(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
674 0 : if( FD_LIKELY( peer_updated ) ) break;
675 0 : }
676 0 : }
677 :
678 0 : if( FD_UNLIKELY( peer_updated ) ) {
679 0 : updated[ update_cnt++ ] = found_idx;
680 0 : gui->gossip.peers[ found_idx ].shred_version = *(ushort const *)(data+i*(58UL+12UL*6UL)+40UL);
681 0 : gui->gossip.peers[ found_idx ].wallclock = *(ulong const *)(data+i*(58UL+12UL*6UL)+32UL);
682 0 : gui->gossip.peers[ found_idx ].has_version = *(data+i*(58UL+12UL*6UL)+42UL);
683 0 : if( FD_LIKELY( gui->gossip.peers[ found_idx ].has_version ) ) {
684 0 : gui->gossip.peers[ found_idx ].version.major = *(ushort const *)(data+i*(58UL+12UL*6UL)+43UL);
685 0 : gui->gossip.peers[ found_idx ].version.minor = *(ushort const *)(data+i*(58UL+12UL*6UL)+45UL);
686 0 : gui->gossip.peers[ found_idx ].version.patch = *(ushort const *)(data+i*(58UL+12UL*6UL)+47UL);
687 0 : gui->gossip.peers[ found_idx ].version.has_commit = *(data+i*(58UL+12UL*6UL)+49UL);
688 0 : if( FD_LIKELY( gui->gossip.peers[ found_idx ].version.has_commit ) ) {
689 0 : gui->gossip.peers[ found_idx ].version.commit = *(uint const *)(data+i*(58UL+12UL*6UL)+50UL);
690 0 : }
691 0 : gui->gossip.peers[ found_idx ].version.feature_set = *(uint const *)(data+i*(58UL+12UL*6UL)+54UL);
692 0 : }
693 :
694 0 : for( ulong j=0UL; j<12UL; j++ ) {
695 0 : gui->gossip.peers[ found_idx ].sockets[ j ].ipv4 = *(uint const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL);
696 0 : gui->gossip.peers[ found_idx ].sockets[ j ].port = *(ushort const *)(data+i*(58UL+12UL*6UL)+58UL+j*6UL+4UL);
697 0 : }
698 0 : }
699 0 : }
700 0 : }
701 :
702 0 : added_cnt = gui->gossip.peer_cnt - before_peer_cnt;
703 0 : for( ulong i=before_peer_cnt; i<gui->gossip.peer_cnt; i++ ) added[ i-before_peer_cnt ] = i;
704 :
705 0 : fd_gui_printf_peers_gossip_update( gui, updated, update_cnt, removed, removed_cnt, added, added_cnt );
706 0 : fd_http_server_ws_broadcast( gui->http );
707 0 : }
708 :
709 : static void
710 : fd_gui_handle_vote_account_update( fd_gui_t * gui,
711 0 : uchar const * msg ) {
712 0 : if( FD_UNLIKELY( gui->vote_account.vote_account_cnt==FD_GUI_MAX_PEER_CNT ) ) {
713 0 : FD_LOG_DEBUG(("vote account cnt exceeds 40200 %lu, ignoring additional entries", gui->vote_account.vote_account_cnt ));
714 0 : return;
715 0 : }
716 0 : ulong const * header = (ulong const *)fd_type_pun_const( msg );
717 0 : ulong peer_cnt = header[ 0 ];
718 :
719 0 : FD_TEST( peer_cnt<=FD_GUI_MAX_PEER_CNT );
720 :
721 0 : ulong added_cnt = 0UL;
722 0 : ulong added[ FD_GUI_MAX_PEER_CNT ] = {0};
723 :
724 0 : ulong update_cnt = 0UL;
725 0 : ulong updated[ FD_GUI_MAX_PEER_CNT ] = {0};
726 :
727 0 : ulong removed_cnt = 0UL;
728 0 : fd_pubkey_t removed[ FD_GUI_MAX_PEER_CNT ] = {0};
729 :
730 0 : uchar const * data = (uchar const *)(header+1UL);
731 0 : for( ulong i=0UL; i<gui->vote_account.vote_account_cnt; i++ ) {
732 0 : int found = 0;
733 0 : for( ulong j=0UL; j<peer_cnt; j++ ) {
734 0 : if( FD_UNLIKELY( !memcmp( gui->vote_account.vote_accounts[ i ].vote_account, data+j*112UL, 32UL ) ) ) {
735 0 : found = 1;
736 0 : break;
737 0 : }
738 0 : }
739 :
740 0 : if( FD_UNLIKELY( !found ) ) {
741 0 : fd_memcpy( removed[ removed_cnt++ ].uc, gui->vote_account.vote_accounts[ i ].vote_account->uc, 32UL );
742 0 : if( FD_LIKELY( i+1UL!=gui->vote_account.vote_account_cnt ) ) {
743 0 : gui->vote_account.vote_accounts[ i ] = gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt-1UL ];
744 0 : gui->vote_account.vote_account_cnt--;
745 0 : i--;
746 0 : }
747 0 : }
748 0 : }
749 :
750 0 : ulong before_peer_cnt = gui->vote_account.vote_account_cnt;
751 0 : for( ulong i=0UL; i<peer_cnt; i++ ) {
752 0 : int found = 0;
753 0 : ulong found_idx;
754 0 : for( ulong j=0UL; j<gui->vote_account.vote_account_cnt; j++ ) {
755 0 : if( FD_UNLIKELY( !memcmp( gui->vote_account.vote_accounts[ j ].vote_account, data+i*112UL, 32UL ) ) ) {
756 0 : found_idx = j;
757 0 : found = 1;
758 0 : break;
759 0 : }
760 0 : }
761 :
762 0 : if( FD_UNLIKELY( !found ) ) {
763 0 : fd_memcpy( gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].vote_account->uc, data+i*112UL, 32UL );
764 0 : fd_memcpy( gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].pubkey->uc, data+i*112UL+32UL, 32UL );
765 :
766 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].activated_stake = *(ulong const *)(data+i*112UL+64UL);
767 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].last_vote = *(ulong const *)(data+i*112UL+72UL);
768 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].root_slot = *(ulong const *)(data+i*112UL+80UL);
769 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].epoch_credits = *(ulong const *)(data+i*112UL+88UL);
770 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].commission = *(data+i*112UL+96UL);
771 0 : gui->vote_account.vote_accounts[ gui->vote_account.vote_account_cnt ].delinquent = *(data+i*112UL+97UL);
772 :
773 0 : gui->vote_account.vote_account_cnt++;
774 0 : } else {
775 0 : int peer_updated =
776 0 : memcmp( gui->vote_account.vote_accounts[ found_idx ].pubkey->uc, data+i*112UL+32UL, 32UL ) ||
777 0 : gui->vote_account.vote_accounts[ found_idx ].activated_stake != *(ulong const *)(data+i*112UL+64UL) ||
778 : // gui->vote_account.vote_accounts[ found_idx ].last_vote != *(ulong const *)(data+i*112UL+72UL) ||
779 : // gui->vote_account.vote_accounts[ found_idx ].root_slot != *(ulong const *)(data+i*112UL+80UL) ||
780 : // gui->vote_account.vote_accounts[ found_idx ].epoch_credits != *(ulong const *)(data+i*112UL+88UL) ||
781 0 : gui->vote_account.vote_accounts[ found_idx ].commission != *(data+i*112UL+96UL) ||
782 0 : gui->vote_account.vote_accounts[ found_idx ].delinquent != *(data+i*112UL+97UL);
783 :
784 0 : if( FD_UNLIKELY( peer_updated ) ) {
785 0 : updated[ update_cnt++ ] = found_idx;
786 :
787 0 : fd_memcpy( gui->vote_account.vote_accounts[ found_idx ].pubkey->uc, data+i*112UL+32UL, 32UL );
788 0 : gui->vote_account.vote_accounts[ found_idx ].activated_stake = *(ulong const *)(data+i*112UL+64UL);
789 0 : gui->vote_account.vote_accounts[ found_idx ].last_vote = *(ulong const *)(data+i*112UL+72UL);
790 0 : gui->vote_account.vote_accounts[ found_idx ].root_slot = *(ulong const *)(data+i*112UL+80UL);
791 0 : gui->vote_account.vote_accounts[ found_idx ].epoch_credits = *(ulong const *)(data+i*112UL+88UL);
792 0 : gui->vote_account.vote_accounts[ found_idx ].commission = *(data+i*112UL+96UL);
793 0 : gui->vote_account.vote_accounts[ found_idx ].delinquent = *(data+i*112UL+97UL);
794 0 : }
795 0 : }
796 0 : }
797 :
798 0 : added_cnt = gui->vote_account.vote_account_cnt - before_peer_cnt;
799 0 : for( ulong i=before_peer_cnt; i<gui->vote_account.vote_account_cnt; i++ ) added[ i-before_peer_cnt ] = i;
800 :
801 0 : fd_gui_printf_peers_vote_account_update( gui, updated, update_cnt, removed, removed_cnt, added, added_cnt );
802 0 : fd_http_server_ws_broadcast( gui->http );
803 0 : }
804 :
805 : static void
806 : fd_gui_handle_validator_info_update( fd_gui_t * gui,
807 0 : uchar const * msg ) {
808 0 : if( FD_UNLIKELY( gui->validator_info.info_cnt == FD_GUI_MAX_PEER_CNT ) ) {
809 0 : FD_LOG_DEBUG(("validator info cnt exceeds 40200 %lu, ignoring additional entries", gui->validator_info.info_cnt ));
810 0 : return;
811 0 : }
812 0 : uchar const * data = (uchar const *)fd_type_pun_const( msg );
813 :
814 0 : ulong added_cnt = 0UL;
815 0 : ulong added[ 1 ] = {0};
816 :
817 0 : ulong update_cnt = 0UL;
818 0 : ulong updated[ 1 ] = {0};
819 :
820 0 : ulong removed_cnt = 0UL;
821 : /* Unlike gossip or vote account updates, validator info messages come
822 : in as info is discovered, and may contain as little as 1 validator
823 : per message. Therefore, it doesn't make sense to use the remove
824 : mechanism. */
825 :
826 0 : ulong before_peer_cnt = gui->validator_info.info_cnt;
827 0 : int found = 0;
828 0 : ulong found_idx;
829 0 : for( ulong j=0UL; j<gui->validator_info.info_cnt; j++ ) {
830 0 : if( FD_UNLIKELY( !memcmp( gui->validator_info.info[ j ].pubkey, data, 32UL ) ) ) {
831 0 : found_idx = j;
832 0 : found = 1;
833 0 : break;
834 0 : }
835 0 : }
836 :
837 0 : if( FD_UNLIKELY( !found ) ) {
838 0 : fd_memcpy( gui->validator_info.info[ gui->validator_info.info_cnt ].pubkey->uc, data, 32UL );
839 :
840 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].name, (char const *)(data+32UL), 64 );
841 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].name[ 63 ] = '\0';
842 :
843 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].website, (char const *)(data+96UL), 128 );
844 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].website[ 127 ] = '\0';
845 :
846 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].details, (char const *)(data+224UL), 256 );
847 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].details[ 255 ] = '\0';
848 :
849 0 : strncpy( gui->validator_info.info[ gui->validator_info.info_cnt ].icon_uri, (char const *)(data+480UL), 128 );
850 0 : gui->validator_info.info[ gui->validator_info.info_cnt ].icon_uri[ 127 ] = '\0';
851 :
852 0 : gui->validator_info.info_cnt++;
853 0 : } else {
854 0 : int peer_updated =
855 0 : memcmp( gui->validator_info.info[ found_idx ].pubkey->uc, data, 32UL ) ||
856 0 : strncmp( gui->validator_info.info[ found_idx ].name, (char const *)(data+32UL), 64 ) ||
857 0 : strncmp( gui->validator_info.info[ found_idx ].website, (char const *)(data+96UL), 128 ) ||
858 0 : strncmp( gui->validator_info.info[ found_idx ].details, (char const *)(data+224UL), 256 ) ||
859 0 : strncmp( gui->validator_info.info[ found_idx ].icon_uri, (char const *)(data+480UL), 128 );
860 :
861 0 : if( FD_UNLIKELY( peer_updated ) ) {
862 0 : updated[ update_cnt++ ] = found_idx;
863 :
864 0 : fd_memcpy( gui->validator_info.info[ found_idx ].pubkey->uc, data, 32UL );
865 :
866 0 : strncpy( gui->validator_info.info[ found_idx ].name, (char const *)(data+32UL), 64 );
867 0 : gui->validator_info.info[ found_idx ].name[ 63 ] = '\0';
868 :
869 0 : strncpy( gui->validator_info.info[ found_idx ].website, (char const *)(data+96UL), 128 );
870 0 : gui->validator_info.info[ found_idx ].website[ 127 ] = '\0';
871 :
872 0 : strncpy( gui->validator_info.info[ found_idx ].details, (char const *)(data+224UL), 256 );
873 0 : gui->validator_info.info[ found_idx ].details[ 255 ] = '\0';
874 :
875 0 : strncpy( gui->validator_info.info[ found_idx ].icon_uri, (char const *)(data+480UL), 128 );
876 0 : gui->validator_info.info[ found_idx ].icon_uri[ 127 ] = '\0';
877 0 : }
878 0 : }
879 :
880 0 : added_cnt = gui->validator_info.info_cnt - before_peer_cnt;
881 0 : for( ulong i=before_peer_cnt; i<gui->validator_info.info_cnt; i++ ) added[ i-before_peer_cnt ] = i;
882 :
883 0 : fd_gui_printf_peers_validator_info_update( gui, updated, update_cnt, NULL, removed_cnt, added, added_cnt );
884 0 : fd_http_server_ws_broadcast( gui->http );
885 0 : }
886 :
887 : int
888 : fd_gui_request_slot( fd_gui_t * gui,
889 : ulong ws_conn_id,
890 : ulong request_id,
891 0 : cJSON const * params ) {
892 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "slot" );
893 0 : if( FD_UNLIKELY( !cJSON_IsNumber( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
894 :
895 0 : ulong _slot = slot_param->valueulong;
896 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
897 0 : if( FD_UNLIKELY( slot->slot!=_slot || slot->slot==ULONG_MAX ) ) {
898 0 : fd_gui_printf_null_query_response( gui->http, "slot", "query", request_id );
899 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
900 0 : return 0;
901 0 : }
902 :
903 0 : fd_gui_printf_slot_request( gui, _slot, request_id );
904 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
905 0 : return 0;
906 0 : }
907 :
908 : int
909 : fd_gui_request_slot_transactions( fd_gui_t * gui,
910 : ulong ws_conn_id,
911 : ulong request_id,
912 0 : cJSON const * params ) {
913 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "slot" );
914 0 : if( FD_UNLIKELY( !cJSON_IsNumber( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
915 :
916 0 : ulong _slot = slot_param->valueulong;
917 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
918 0 : if( FD_UNLIKELY( slot->slot!=_slot || slot->slot==ULONG_MAX ) ) {
919 0 : fd_gui_printf_null_query_response( gui->http, "slot", "query", request_id );
920 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
921 0 : return 0;
922 0 : }
923 :
924 0 : fd_gui_printf_slot_transactions_request( gui, _slot, request_id );
925 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
926 0 : return 0;
927 0 : }
928 :
929 : int
930 : fd_gui_request_slot_detailed( fd_gui_t * gui,
931 : ulong ws_conn_id,
932 : ulong request_id,
933 0 : cJSON const * params ) {
934 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "slot" );
935 0 : if( FD_UNLIKELY( !cJSON_IsNumber( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
936 :
937 0 : ulong _slot = slot_param->valueulong;
938 0 : fd_gui_slot_t const * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
939 0 : if( FD_UNLIKELY( slot->slot!=_slot || slot->slot==ULONG_MAX ) ) {
940 0 : fd_gui_printf_null_query_response( gui->http, "slot", "query", request_id );
941 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
942 0 : return 0;
943 0 : }
944 :
945 0 : fd_gui_printf_slot_request_detailed( gui, _slot, request_id );
946 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
947 0 : return 0;
948 0 : }
949 :
950 : static inline ulong
951 0 : fd_gui_slot_duration( fd_gui_t const * gui, fd_gui_slot_t const * cur ) {
952 0 : if( FD_UNLIKELY( cur->slot == ULONG_MAX || cur->slot == 0UL ) ) return ULONG_MAX;
953 :
954 0 : fd_gui_slot_t const * prev = gui->slots[ (cur->slot - 1UL) % FD_GUI_SLOTS_CNT ];
955 0 : if( FD_UNLIKELY( prev->slot == ULONG_MAX ||
956 0 : prev->skipped ||
957 0 : prev->completed_time == LONG_MAX ||
958 0 : prev->slot != (cur->slot - 1UL) ||
959 0 : cur->skipped ||
960 0 : cur->completed_time == LONG_MAX ) ) return ULONG_MAX;
961 :
962 0 : return (ulong)(cur->completed_time - prev->completed_time);
963 0 : }
964 :
965 : static inline void
966 : fd_gui_try_insert_ranking( fd_gui_t * gui,
967 : fd_gui_slot_rankings_t * rankings,
968 0 : fd_gui_slot_t const * slot ) {
969 : /* Rankings are inserted into an extra slot at the end of the ranking
970 : array, then the array is sorted. */
971 0 : #define TRY_INSERT_SLOT( ranking_name, ranking_slot, ranking_value ) \
972 0 : do { \
973 0 : rankings->FD_CONCAT2(largest_, ranking_name) [ FD_GUI_SLOT_RANKINGS_SZ ] = (fd_gui_slot_ranking_t){ .slot = (ranking_slot), .value = (ranking_value), .type = FD_GUI_SLOT_RANKING_TYPE_DESC }; \
974 0 : fd_gui_slot_ranking_sort_insert( rankings->FD_CONCAT2(largest_, ranking_name), FD_GUI_SLOT_RANKINGS_SZ+1UL ); \
975 0 : rankings->FD_CONCAT2(smallest_, ranking_name)[ FD_GUI_SLOT_RANKINGS_SZ ] = (fd_gui_slot_ranking_t){ .slot = (ranking_slot), .value = (ranking_value), .type = FD_GUI_SLOT_RANKING_TYPE_ASC }; \
976 0 : fd_gui_slot_ranking_sort_insert( rankings->FD_CONCAT2(smallest_, ranking_name), FD_GUI_SLOT_RANKINGS_SZ+1UL ); \
977 0 : } while (0)
978 :
979 0 : if( slot->skipped ) {
980 0 : TRY_INSERT_SLOT( skipped, slot->slot, slot->slot );
981 0 : return;
982 0 : }
983 :
984 0 : ulong dur = fd_gui_slot_duration( gui, slot );
985 0 : if( FD_LIKELY( dur!=ULONG_MAX ) ) TRY_INSERT_SLOT( duration, slot->slot, dur );
986 0 : TRY_INSERT_SLOT( tips, slot->slot, slot->tips );
987 0 : TRY_INSERT_SLOT( fees, slot->slot, slot->priority_fee );
988 0 : TRY_INSERT_SLOT( rewards, slot->slot, slot->tips + slot->priority_fee );
989 0 : TRY_INSERT_SLOT( compute_units, slot->slot, slot->compute_units );
990 0 : #undef TRY_INSERT_SLOT
991 0 : }
992 :
993 : static void
994 0 : fd_gui_update_slot_rankings( fd_gui_t * gui ) {
995 0 : if( FD_UNLIKELY( gui->summary.startup_progress.startup_ledger_max_slot==ULONG_MAX ) ) return;
996 0 : if( FD_UNLIKELY( gui->summary.slot_rooted==0UL ) ) return;
997 :
998 0 : ulong epoch_start_slot = ULONG_MAX;
999 0 : ulong epoch = ULONG_MAX;
1000 0 : for( ulong i = 0UL; i<2UL; i++ ) {
1001 0 : if( FD_LIKELY( gui->epoch.has_epoch[ i ] ) ) {
1002 : /* the "current" epoch is the smallest */
1003 0 : epoch_start_slot = fd_ulong_min( epoch_start_slot, gui->epoch.epochs[ i ].start_slot );
1004 0 : epoch = fd_ulong_min( epoch, gui->epoch.epochs[ i ].epoch );
1005 0 : }
1006 0 : }
1007 :
1008 0 : if( FD_UNLIKELY( epoch==ULONG_MAX ) ) return;
1009 0 : ulong epoch_idx = epoch % 2UL;
1010 :
1011 : /* No new slots since the last update */
1012 0 : if( FD_UNLIKELY( gui->epoch.epochs[ epoch_idx ].rankings_slot>gui->summary.slot_rooted ) ) return;
1013 :
1014 : /* Slots before startup_ledger_max_slot are unavailable. */
1015 0 : gui->epoch.epochs[ epoch_idx ].rankings_slot = fd_ulong_max( gui->epoch.epochs[ epoch_idx ].rankings_slot, gui->summary.startup_progress.startup_ledger_max_slot+1UL );
1016 :
1017 : /* Update the rankings. Only look through slots we haven't already. */
1018 0 : for( ulong s = gui->summary.slot_rooted; s>=gui->epoch.epochs[ epoch_idx ].rankings_slot; s--) {
1019 0 : fd_gui_slot_t * slot = gui->slots[ s % FD_GUI_SLOTS_CNT ];
1020 0 : if( FD_UNLIKELY( slot->slot!=s || slot->slot==ULONG_MAX ) ) break;
1021 :
1022 0 : fd_gui_try_insert_ranking( gui, gui->epoch.epochs[ epoch_idx ].rankings, slot );
1023 0 : if( FD_UNLIKELY( slot->mine ) ) fd_gui_try_insert_ranking( gui, gui->epoch.epochs[ epoch_idx ].my_rankings, slot );
1024 0 : }
1025 :
1026 0 : gui->epoch.epochs[ epoch_idx ].rankings_slot = gui->summary.slot_rooted + 1UL;
1027 0 : }
1028 :
1029 : int
1030 : fd_gui_request_slot_rankings( fd_gui_t * gui,
1031 : ulong ws_conn_id,
1032 : ulong request_id,
1033 0 : cJSON const * params ) {
1034 0 : const cJSON * slot_param = cJSON_GetObjectItemCaseSensitive( params, "mine" );
1035 0 : if( FD_UNLIKELY( !cJSON_IsBool( slot_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1036 :
1037 0 : int mine = !!(slot_param->type & cJSON_True);
1038 0 : fd_gui_update_slot_rankings( gui );
1039 0 : fd_gui_printf_slot_rankings_request( gui, request_id, mine );
1040 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
1041 0 : return 0;
1042 0 : }
1043 :
1044 : int
1045 : fd_gui_ws_message( fd_gui_t * gui,
1046 : ulong ws_conn_id,
1047 : uchar const * data,
1048 0 : ulong data_len ) {
1049 : /* TODO: cJSON allocates, might fail SIGSYS due to brk(2)...
1050 : switch off this (or use wksp allocator) */
1051 0 : const char * parse_end;
1052 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char *)data, data_len, &parse_end, 0 );
1053 0 : if( FD_UNLIKELY( !json ) ) {
1054 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1055 0 : }
1056 :
1057 0 : const cJSON * node = cJSON_GetObjectItemCaseSensitive( json, "id" );
1058 0 : if( FD_UNLIKELY( !cJSON_IsNumber( node ) ) ) {
1059 0 : cJSON_Delete( json );
1060 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1061 0 : }
1062 0 : ulong id = node->valueulong;
1063 :
1064 0 : const cJSON * topic = cJSON_GetObjectItemCaseSensitive( json, "topic" );
1065 0 : if( FD_UNLIKELY( !cJSON_IsString( topic ) || topic->valuestring==NULL ) ) {
1066 0 : cJSON_Delete( json );
1067 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1068 0 : }
1069 :
1070 0 : const cJSON * key = cJSON_GetObjectItemCaseSensitive( json, "key" );
1071 0 : if( FD_UNLIKELY( !cJSON_IsString( key ) || key->valuestring==NULL ) ) {
1072 0 : cJSON_Delete( json );
1073 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1074 0 : }
1075 :
1076 0 : if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query" ) ) ) {
1077 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1078 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1079 0 : cJSON_Delete( json );
1080 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1081 0 : }
1082 :
1083 0 : int result = fd_gui_request_slot( gui, ws_conn_id, id, params );
1084 0 : cJSON_Delete( json );
1085 0 : return result;
1086 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query_detailed" ) ) ) {
1087 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1088 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1089 0 : cJSON_Delete( json );
1090 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1091 0 : }
1092 :
1093 0 : int result = fd_gui_request_slot_detailed( gui, ws_conn_id, id, params );
1094 0 : cJSON_Delete( json );
1095 0 : return result;
1096 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query_transactions" ) ) ) {
1097 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1098 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1099 0 : cJSON_Delete( json );
1100 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1101 0 : }
1102 :
1103 0 : int result = fd_gui_request_slot_transactions( gui, ws_conn_id, id, params );
1104 0 : cJSON_Delete( json );
1105 0 : return result;
1106 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "slot" ) && !strcmp( key->valuestring, "query_rankings" ) ) ) {
1107 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1108 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1109 0 : cJSON_Delete( json );
1110 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1111 0 : }
1112 :
1113 0 : int result = fd_gui_request_slot_rankings( gui, ws_conn_id, id, params );
1114 0 : cJSON_Delete( json );
1115 0 : return result;
1116 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "summary" ) && !strcmp( key->valuestring, "ping" ) ) ) {
1117 0 : fd_gui_printf_summary_ping( gui, id );
1118 0 : FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) );
1119 :
1120 0 : cJSON_Delete( json );
1121 0 : return 0;
1122 0 : }
1123 :
1124 0 : cJSON_Delete( json );
1125 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD;
1126 0 : }
1127 :
1128 : static void
1129 : fd_gui_clear_slot( fd_gui_t * gui,
1130 : ulong _slot,
1131 0 : ulong _parent_slot ) {
1132 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1133 :
1134 0 : int mine = 0;
1135 0 : ulong epoch_idx = 0UL;
1136 0 : for( ulong i=0UL; i<2UL; i++) {
1137 0 : if( FD_UNLIKELY( !gui->epoch.has_epoch[ i ] ) ) continue;
1138 0 : if( FD_LIKELY( _slot>=gui->epoch.epochs[ i ].start_slot && _slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1139 0 : fd_pubkey_t const * slot_leader = fd_epoch_leaders_get( gui->epoch.epochs[ i ].lsched, _slot );
1140 0 : mine = !memcmp( slot_leader->uc, gui->summary.identity_key->uc, 32UL );
1141 0 : epoch_idx = i;
1142 0 : break;
1143 0 : }
1144 0 : }
1145 :
1146 0 : slot->slot = _slot;
1147 0 : slot->parent_slot = _parent_slot;
1148 0 : slot->max_compute_units = UINT_MAX;
1149 0 : slot->mine = mine;
1150 0 : slot->skipped = 0;
1151 0 : slot->must_republish = 1;
1152 0 : slot->level = FD_GUI_SLOT_LEVEL_INCOMPLETE;
1153 0 : slot->total_txn_cnt = UINT_MAX;
1154 0 : slot->vote_txn_cnt = UINT_MAX;
1155 0 : slot->failed_txn_cnt = UINT_MAX;
1156 0 : slot->nonvote_failed_txn_cnt = UINT_MAX;
1157 0 : slot->compute_units = UINT_MAX;
1158 0 : slot->transaction_fee = ULONG_MAX;
1159 0 : slot->priority_fee = ULONG_MAX;
1160 0 : slot->tips = ULONG_MAX;
1161 0 : slot->leader_state = FD_GUI_SLOT_LEADER_UNSTARTED;
1162 0 : slot->completed_time = LONG_MAX;
1163 :
1164 0 : slot->txs.leader_start_time = LONG_MAX;
1165 0 : slot->txs.leader_end_time = LONG_MAX;
1166 0 : slot->txs.microblocks_upper_bound = USHORT_MAX;
1167 0 : slot->txs.begin_microblocks = 0U;
1168 0 : slot->txs.end_microblocks = 0U;
1169 0 : slot->txs.reference_ticks = LONG_MAX;
1170 0 : slot->txs.reference_nanos = LONG_MAX;
1171 0 : slot->txs.start_offset = ULONG_MAX;
1172 0 : slot->txs.end_offset = ULONG_MAX;
1173 :
1174 0 : if( FD_LIKELY( slot->mine ) ) {
1175 : /* All slots start off not skipped, until we see it get off the reset
1176 : chain. */
1177 0 : gui->epoch.epochs[ epoch_idx ].my_total_slots++;
1178 0 : }
1179 :
1180 0 : if( FD_UNLIKELY( !_slot ) ) {
1181 : /* Slot 0 is always rooted */
1182 0 : slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
1183 0 : }
1184 0 : }
1185 :
1186 : static void
1187 : fd_gui_handle_leader_schedule( fd_gui_t * gui,
1188 0 : ulong const * msg ) {
1189 0 : ulong epoch = msg[ 0 ];
1190 0 : ulong staked_cnt = msg[ 1 ];
1191 0 : ulong start_slot = msg[ 2 ];
1192 0 : ulong slot_cnt = msg[ 3 ];
1193 0 : ulong excluded_stake = msg[ 4 ];
1194 0 : ulong vote_keyed_lsched = msg[ 5 ];
1195 :
1196 0 : FD_TEST( staked_cnt<=MAX_STAKED_LEADERS );
1197 0 : FD_TEST( slot_cnt<=MAX_SLOTS_PER_EPOCH );
1198 :
1199 0 : ulong idx = epoch % 2UL;
1200 0 : gui->epoch.has_epoch[ idx ] = 1;
1201 :
1202 0 : gui->epoch.epochs[ idx ].epoch = epoch;
1203 0 : gui->epoch.epochs[ idx ].start_slot = start_slot;
1204 0 : gui->epoch.epochs[ idx ].end_slot = start_slot + slot_cnt - 1; // end_slot is inclusive.
1205 0 : gui->epoch.epochs[ idx ].excluded_stake = excluded_stake;
1206 0 : gui->epoch.epochs[ idx ].my_total_slots = 0UL;
1207 0 : gui->epoch.epochs[ idx ].my_skipped_slots = 0UL;
1208 :
1209 0 : memset( gui->epoch.epochs[ idx ].rankings, (int)(UINT_MAX), sizeof(gui->epoch.epochs[ idx ].rankings[ 0 ]) );
1210 0 : memset( gui->epoch.epochs[ idx ].my_rankings, (int)(UINT_MAX), sizeof(gui->epoch.epochs[ idx ].my_rankings[ 0 ]) );
1211 0 : gui->epoch.epochs[ idx ].rankings_slot = start_slot;
1212 :
1213 0 : fd_vote_stake_weight_t const * stake_weights = fd_type_pun_const( msg+6UL );
1214 0 : memcpy( gui->epoch.epochs[ idx ].stakes, stake_weights, staked_cnt*sizeof(fd_vote_stake_weight_t) );
1215 :
1216 0 : fd_epoch_leaders_delete( fd_epoch_leaders_leave( gui->epoch.epochs[ idx ].lsched ) );
1217 0 : gui->epoch.epochs[idx].lsched = fd_epoch_leaders_join( fd_epoch_leaders_new( gui->epoch.epochs[ idx ]._lsched,
1218 0 : epoch,
1219 0 : gui->epoch.epochs[ idx ].start_slot,
1220 0 : slot_cnt,
1221 0 : staked_cnt,
1222 0 : gui->epoch.epochs[ idx ].stakes,
1223 0 : excluded_stake,
1224 0 : vote_keyed_lsched ) );
1225 :
1226 0 : if( FD_UNLIKELY( start_slot==0UL ) ) {
1227 0 : gui->epoch.epochs[ 0 ].start_time = fd_log_wallclock();
1228 0 : } else {
1229 0 : gui->epoch.epochs[ idx ].start_time = LONG_MAX;
1230 :
1231 0 : for( ulong i=0UL; i<fd_ulong_min( start_slot-1UL, FD_GUI_SLOTS_CNT ); i++ ) {
1232 0 : fd_gui_slot_t * slot = gui->slots[ (start_slot-i) % FD_GUI_SLOTS_CNT ];
1233 0 : if( FD_UNLIKELY( slot->slot!=(start_slot-i) ) ) break;
1234 0 : else if( FD_UNLIKELY( slot->skipped ) ) continue;
1235 :
1236 0 : gui->epoch.epochs[ idx ].start_time = slot->completed_time;
1237 0 : break;
1238 0 : }
1239 0 : }
1240 :
1241 0 : fd_gui_printf_epoch( gui, idx );
1242 0 : fd_http_server_ws_broadcast( gui->http );
1243 0 : }
1244 :
1245 : static void
1246 : fd_gui_handle_slot_start( fd_gui_t * gui,
1247 0 : ulong * msg ) {
1248 0 : ulong _slot = msg[ 0 ];
1249 0 : ulong _parent_slot = msg[ 1 ];
1250 : // FD_LOG_WARNING(( "Got start slot %lu parent_slot %lu", _slot, _parent_slot ));
1251 0 : FD_TEST( gui->debug_in_leader_slot==ULONG_MAX );
1252 0 : gui->debug_in_leader_slot = _slot;
1253 :
1254 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1255 :
1256 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, _parent_slot );
1257 0 : slot->leader_state = FD_GUI_SLOT_LEADER_STARTED;
1258 :
1259 0 : fd_gui_tile_timers_snap( gui );
1260 0 : gui->summary.tile_timers_snap_idx_slot_start = (gui->summary.tile_timers_snap_idx+(FD_GUI_TILE_TIMER_SNAP_CNT-1UL))%FD_GUI_TILE_TIMER_SNAP_CNT;
1261 :
1262 0 : fd_gui_txn_waterfall_t waterfall[ 1 ];
1263 0 : fd_gui_txn_waterfall_snap( gui, waterfall );
1264 0 : fd_gui_tile_stats_snap( gui, waterfall, slot->tile_stats_begin );
1265 0 : }
1266 :
1267 : static void
1268 : fd_gui_handle_slot_end( fd_gui_t * gui,
1269 0 : ulong * msg ) {
1270 0 : ulong _slot = msg[ 0 ];
1271 0 : ulong _cus_used = msg[ 1 ];
1272 0 : if( FD_UNLIKELY( gui->debug_in_leader_slot!=_slot ) ) {
1273 0 : FD_LOG_ERR(( "gui->debug_in_leader_slot %lu _slot %lu", gui->debug_in_leader_slot, _slot ));
1274 0 : }
1275 0 : gui->debug_in_leader_slot = ULONG_MAX;
1276 :
1277 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1278 0 : FD_TEST( slot->slot==_slot );
1279 :
1280 0 : slot->leader_state = FD_GUI_SLOT_LEADER_ENDED;
1281 0 : slot->compute_units = (uint)_cus_used;
1282 :
1283 0 : fd_gui_tile_timers_snap( gui );
1284 : /* Record slot number so we can detect overwrite. */
1285 0 : gui->summary.tile_timers_leader_history_slot[ gui->summary.tile_timers_history_idx ] = _slot;
1286 : /* Point into per-leader-slot storage. */
1287 0 : slot->tile_timers_history_idx = gui->summary.tile_timers_history_idx;
1288 : /* Downsample tile timers into per-leader-slot storage. */
1289 0 : ulong end = gui->summary.tile_timers_snap_idx;
1290 0 : end = fd_ulong_if( end<gui->summary.tile_timers_snap_idx_slot_start, end+FD_GUI_TILE_TIMER_SNAP_CNT, end );
1291 0 : gui->summary.tile_timers_leader_history_slot_sample_cnt[ gui->summary.tile_timers_history_idx ] = end-gui->summary.tile_timers_snap_idx_slot_start;
1292 0 : ulong stride = fd_ulong_max( 1UL, (end-gui->summary.tile_timers_snap_idx_slot_start) / FD_GUI_TILE_TIMER_LEADER_DOWNSAMPLE_CNT );
1293 0 : for( ulong sample_snap_idx=gui->summary.tile_timers_snap_idx_slot_start, i=0UL; sample_snap_idx<end; sample_snap_idx+=stride, i++ ) {
1294 0 : memcpy( gui->summary.tile_timers_leader_history[ gui->summary.tile_timers_history_idx ][ i ], gui->summary.tile_timers_snap[ sample_snap_idx%FD_GUI_TILE_TIMER_SNAP_CNT ], sizeof(gui->summary.tile_timers_leader_history[ gui->summary.tile_timers_history_idx ][ i ]) );
1295 0 : }
1296 0 : gui->summary.tile_timers_history_idx = (gui->summary.tile_timers_history_idx+1UL)%FD_GUI_TILE_TIMER_LEADER_CNT;
1297 :
1298 : /* When a slot ends, snap the state of the waterfall and save it into
1299 : that slot, and also reset the reference counters to the end of the
1300 : slot. */
1301 :
1302 0 : fd_gui_txn_waterfall_snap( gui, slot->waterfall_end );
1303 0 : memcpy( slot->waterfall_begin, gui->summary.txn_waterfall_reference, sizeof(slot->waterfall_begin) );
1304 0 : memcpy( gui->summary.txn_waterfall_reference, slot->waterfall_end, sizeof(gui->summary.txn_waterfall_reference) );
1305 :
1306 0 : fd_gui_tile_stats_snap( gui, slot->waterfall_end, slot->tile_stats_end );
1307 0 : }
1308 :
1309 : static void
1310 : fd_gui_handle_reset_slot( fd_gui_t * gui,
1311 0 : ulong * msg ) {
1312 0 : ulong last_landed_vote = msg[ 0 ];
1313 :
1314 0 : ulong parent_cnt = msg[ 1 ];
1315 0 : FD_TEST( parent_cnt<4096UL );
1316 :
1317 0 : ulong _slot = msg[ 2 ];
1318 :
1319 0 : for( ulong i=0UL; i<parent_cnt; i++ ) {
1320 0 : ulong parent_slot = msg[2UL+i];
1321 0 : fd_gui_slot_t * slot = gui->slots[ parent_slot % FD_GUI_SLOTS_CNT ];
1322 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1323 0 : ulong parent_parent_slot = ULONG_MAX;
1324 0 : if( FD_UNLIKELY( i!=parent_cnt-1UL) ) parent_parent_slot = msg[ 3UL+i ];
1325 0 : fd_gui_clear_slot( gui, parent_slot, parent_parent_slot );
1326 0 : }
1327 0 : }
1328 :
1329 0 : if( FD_UNLIKELY( gui->summary.vote_distance!=_slot-last_landed_vote ) ) {
1330 0 : gui->summary.vote_distance = _slot-last_landed_vote;
1331 0 : fd_gui_printf_vote_distance( gui );
1332 0 : fd_http_server_ws_broadcast( gui->http );
1333 0 : }
1334 :
1335 0 : if( FD_LIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_NON_VOTING ) ) {
1336 0 : if( FD_UNLIKELY( last_landed_vote==ULONG_MAX || (last_landed_vote+150UL)<_slot ) ) {
1337 0 : if( FD_UNLIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_DELINQUENT ) ) {
1338 0 : gui->summary.vote_state = FD_GUI_VOTE_STATE_DELINQUENT;
1339 0 : fd_gui_printf_vote_state( gui );
1340 0 : fd_http_server_ws_broadcast( gui->http );
1341 0 : }
1342 0 : } else {
1343 0 : if( FD_UNLIKELY( gui->summary.vote_state!=FD_GUI_VOTE_STATE_VOTING ) ) {
1344 0 : gui->summary.vote_state = FD_GUI_VOTE_STATE_VOTING;
1345 0 : fd_gui_printf_vote_state( gui );
1346 0 : fd_http_server_ws_broadcast( gui->http );
1347 0 : }
1348 0 : }
1349 0 : }
1350 :
1351 0 : ulong parent_slot_idx = 0UL;
1352 :
1353 0 : int republish_skip_rate[ 2 ] = {0};
1354 :
1355 0 : for( ulong i=0UL; i<fd_ulong_min( _slot+1, FD_GUI_SLOTS_CNT ); i++ ) {
1356 0 : ulong parent_slot = _slot - i;
1357 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1358 :
1359 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1360 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX || slot->slot!=parent_slot ) ) fd_gui_clear_slot( gui, parent_slot, ULONG_MAX );
1361 :
1362 : /* The chain of parents may stretch into already rooted slots if
1363 : they haven't been squashed yet, if we reach one of them we can
1364 : just exit, all the information prior to the root is already
1365 : correct. */
1366 :
1367 0 : if( FD_LIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1368 :
1369 0 : int should_republish = slot->must_republish;
1370 0 : slot->must_republish = 0;
1371 :
1372 0 : if( FD_UNLIKELY( parent_slot!=msg[2UL+parent_slot_idx] ) ) {
1373 : /* We are between two parents in the rooted chain, which means
1374 : we were skipped. */
1375 0 : if( FD_UNLIKELY( !slot->skipped ) ) {
1376 0 : slot->skipped = 1;
1377 0 : should_republish = 1;
1378 0 : if( FD_LIKELY( slot->mine ) ) {
1379 0 : for( ulong i=0UL; i<2UL; i++ ) {
1380 0 : if( FD_LIKELY( parent_slot>=gui->epoch.epochs[ i ].start_slot && parent_slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1381 0 : gui->epoch.epochs[ i ].my_skipped_slots++;
1382 0 : republish_skip_rate[ i ] = 1;
1383 0 : break;
1384 0 : }
1385 0 : }
1386 0 : }
1387 0 : }
1388 0 : } else {
1389 : /* Reached the next parent... */
1390 0 : if( FD_UNLIKELY( slot->skipped ) ) {
1391 0 : slot->skipped = 0;
1392 0 : should_republish = 1;
1393 0 : if( FD_LIKELY( slot->mine ) ) {
1394 0 : for( ulong i=0UL; i<2UL; i++ ) {
1395 0 : if( FD_LIKELY( parent_slot>=gui->epoch.epochs[ i ].start_slot && parent_slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1396 0 : gui->epoch.epochs[ i ].my_skipped_slots--;
1397 0 : republish_skip_rate[ i ] = 1;
1398 0 : break;
1399 0 : }
1400 0 : }
1401 0 : }
1402 0 : }
1403 0 : parent_slot_idx++;
1404 0 : }
1405 :
1406 0 : if( FD_LIKELY( should_republish ) ) {
1407 0 : fd_gui_printf_slot( gui, parent_slot );
1408 0 : fd_http_server_ws_broadcast( gui->http );
1409 0 : }
1410 :
1411 : /* We reached the last parent in the chain, everything above this
1412 : must have already been rooted, so we can exit. */
1413 :
1414 0 : if( FD_UNLIKELY( parent_slot_idx>=parent_cnt ) ) break;
1415 0 : }
1416 :
1417 0 : ulong duration_sum = 0UL;
1418 0 : ulong slot_cnt = 0UL;
1419 :
1420 0 : for( ulong i=0UL; i<fd_ulong_min( _slot+1, 750UL ); i++ ) {
1421 0 : ulong parent_slot = _slot - i;
1422 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1423 :
1424 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1425 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1426 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1427 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect _slot-i %lu got slot->slot %lu", _slot, i, _slot-i, slot->slot ));
1428 0 : }
1429 :
1430 0 : ulong slot_duration = fd_gui_slot_duration( gui, slot );
1431 0 : if( FD_LIKELY( slot_duration!=ULONG_MAX ) ) {
1432 0 : duration_sum += slot_duration;
1433 0 : slot_cnt++;
1434 0 : }
1435 0 : }
1436 :
1437 0 : if( FD_LIKELY( slot_cnt>0 )) {
1438 0 : gui->summary.estimated_slot_duration_nanos = (ulong)(duration_sum / slot_cnt);
1439 0 : fd_gui_printf_estimated_slot_duration_nanos( gui );
1440 0 : fd_http_server_ws_broadcast( gui->http );
1441 0 : }
1442 :
1443 0 : if( FD_LIKELY( _slot!=gui->summary.slot_completed ) ) {
1444 0 : gui->summary.slot_completed = _slot;
1445 0 : fd_gui_printf_completed_slot( gui );
1446 0 : fd_http_server_ws_broadcast( gui->http );
1447 0 : }
1448 :
1449 0 : for( ulong i=0UL; i<2UL; i++ ) {
1450 0 : if( FD_LIKELY( republish_skip_rate[ i ] ) ) {
1451 0 : fd_gui_printf_skip_rate( gui, i );
1452 0 : fd_http_server_ws_broadcast( gui->http );
1453 0 : }
1454 0 : }
1455 0 : }
1456 :
1457 : static void
1458 : fd_gui_handle_completed_slot( fd_gui_t * gui,
1459 0 : ulong * msg ) {
1460 0 : ulong _slot = msg[ 0 ];
1461 0 : uint total_txn_count = (uint)msg[ 1 ];
1462 0 : uint nonvote_txn_count = (uint)msg[ 2 ];
1463 0 : uint failed_txn_count = (uint)msg[ 3 ];
1464 0 : uint nonvote_failed_txn_count = (uint)msg[ 4 ];
1465 0 : uint compute_units = (uint)msg[ 5 ];
1466 0 : ulong transaction_fee = msg[ 6 ];
1467 0 : ulong priority_fee = msg[ 7 ];
1468 0 : ulong tips = msg[ 8 ];
1469 0 : ulong _parent_slot = msg[ 9 ];
1470 0 : ulong max_compute_units = msg[ 10 ];
1471 :
1472 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1473 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, _parent_slot );
1474 :
1475 0 : slot->completed_time = fd_log_wallclock();
1476 0 : slot->parent_slot = _parent_slot;
1477 0 : slot->max_compute_units = (uint)max_compute_units;
1478 0 : if( FD_LIKELY( slot->level<FD_GUI_SLOT_LEVEL_COMPLETED ) ) {
1479 : /* Typically a slot goes from INCOMPLETE to COMPLETED but it can
1480 : happen that it starts higher. One such case is when we
1481 : optimistically confirm a higher slot that skips this one, but
1482 : then later we replay this one anyway to track the bank fork. */
1483 :
1484 0 : if( FD_LIKELY( _slot<gui->summary.slot_optimistically_confirmed ) ) {
1485 : /* Cluster might have already optimistically confirmed by the time
1486 : we finish replaying it. */
1487 0 : slot->level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED;
1488 0 : } else {
1489 0 : slot->level = FD_GUI_SLOT_LEVEL_COMPLETED;
1490 0 : }
1491 0 : }
1492 0 : slot->total_txn_cnt = total_txn_count;
1493 0 : slot->vote_txn_cnt = total_txn_count - nonvote_txn_count;
1494 0 : slot->failed_txn_cnt = failed_txn_count;
1495 0 : slot->nonvote_failed_txn_cnt = nonvote_failed_txn_count;
1496 0 : slot->transaction_fee = transaction_fee;
1497 0 : slot->priority_fee = priority_fee;
1498 0 : slot->tips = tips;
1499 0 : if( FD_LIKELY( slot->leader_state==FD_GUI_SLOT_LEADER_UNSTARTED ) ) {
1500 : /* If we were already leader for this slot, then the poh component
1501 : calculated the CUs used and sent them there, rather than the
1502 : replay component which is sending this completed slot. */
1503 0 : slot->compute_units = compute_units;
1504 0 : }
1505 :
1506 0 : if( FD_UNLIKELY( gui->epoch.has_epoch[ 0 ] && _slot==gui->epoch.epochs[ 0 ].end_slot ) ) {
1507 0 : gui->epoch.epochs[ 0 ].end_time = slot->completed_time;
1508 0 : } else if( FD_UNLIKELY( gui->epoch.has_epoch[ 1 ] && _slot==gui->epoch.epochs[ 1 ].end_slot ) ) {
1509 0 : gui->epoch.epochs[ 1 ].end_time = slot->completed_time;
1510 0 : }
1511 :
1512 : /* Broadcast new skip rate if one of our slots got completed. */
1513 0 : if( FD_LIKELY( slot->mine ) ) {
1514 0 : for( ulong i=0UL; i<2UL; i++ ) {
1515 0 : if( FD_LIKELY( _slot>=gui->epoch.epochs[ i ].start_slot && _slot<=gui->epoch.epochs[ i ].end_slot ) ) {
1516 0 : fd_gui_printf_skip_rate( gui, i );
1517 0 : fd_http_server_ws_broadcast( gui->http );
1518 0 : break;
1519 0 : }
1520 0 : }
1521 0 : }
1522 0 : }
1523 :
1524 : static void
1525 : fd_gui_handle_rooted_slot( fd_gui_t * gui,
1526 0 : ulong * msg ) {
1527 0 : ulong _slot = msg[ 0 ];
1528 :
1529 : // FD_LOG_WARNING(( "Got rooted slot %lu", _slot ));
1530 :
1531 : /* Slot 0 is always rooted. No need to iterate all the way back to
1532 : i==_slot */
1533 0 : for( ulong i=0UL; i<fd_ulong_min( _slot, FD_GUI_SLOTS_CNT ); i++ ) {
1534 0 : ulong parent_slot = _slot - i;
1535 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1536 :
1537 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1538 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1539 :
1540 0 : if( FD_UNLIKELY( slot->slot!=parent_slot ) ) {
1541 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot ));
1542 0 : }
1543 0 : if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1544 :
1545 0 : slot->level = FD_GUI_SLOT_LEVEL_ROOTED;
1546 0 : fd_gui_printf_slot( gui, parent_slot );
1547 0 : fd_http_server_ws_broadcast( gui->http );
1548 0 : }
1549 :
1550 0 : gui->summary.slot_rooted = _slot;
1551 0 : fd_gui_printf_root_slot( gui );
1552 0 : fd_http_server_ws_broadcast( gui->http );
1553 0 : }
1554 :
1555 : static void
1556 : fd_gui_handle_optimistically_confirmed_slot( fd_gui_t * gui,
1557 0 : ulong * msg ) {
1558 0 : ulong _slot = msg[ 0 ];
1559 :
1560 : /* Slot 0 is always rooted. No need to iterate all the way back to
1561 : i==_slot */
1562 0 : for( ulong i=0UL; i<fd_ulong_min( _slot, FD_GUI_SLOTS_CNT ); i++ ) {
1563 0 : ulong parent_slot = _slot - i;
1564 0 : ulong parent_idx = parent_slot % FD_GUI_SLOTS_CNT;
1565 :
1566 0 : fd_gui_slot_t * slot = gui->slots[ parent_idx ];
1567 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX) ) break;
1568 :
1569 0 : if( FD_UNLIKELY( slot->slot>parent_slot ) ) {
1570 0 : FD_LOG_ERR(( "_slot %lu i %lu we expect parent_slot %lu got slot->slot %lu", _slot, i, parent_slot, slot->slot ));
1571 0 : } else if( FD_UNLIKELY( slot->slot<parent_slot ) ) {
1572 : /* Slot not even replayed yet ... will come out as optimistically confirmed */
1573 0 : continue;
1574 0 : }
1575 0 : if( FD_UNLIKELY( slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break;
1576 :
1577 0 : if( FD_LIKELY( slot->level<FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED ) ) {
1578 0 : slot->level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED;
1579 0 : fd_gui_printf_slot( gui, parent_slot );
1580 0 : fd_http_server_ws_broadcast( gui->http );
1581 0 : }
1582 0 : }
1583 :
1584 0 : if( FD_UNLIKELY( _slot<gui->summary.slot_optimistically_confirmed ) ) {
1585 : /* Optimistically confirmed slot went backwards ... mark some slots as no
1586 : longer optimistically confirmed. */
1587 0 : for( ulong i=gui->summary.slot_optimistically_confirmed; i>=_slot; i-- ) {
1588 0 : fd_gui_slot_t * slot = gui->slots[ i % FD_GUI_SLOTS_CNT ];
1589 0 : if( FD_UNLIKELY( slot->slot==ULONG_MAX ) ) break;
1590 0 : if( FD_LIKELY( slot->slot==i ) ) {
1591 : /* It's possible for the optimistically confirmed slot to skip
1592 : backwards between two slots that we haven't yet replayed. In
1593 : that case we don't need to change anything, since they will
1594 : get marked properly when they get completed. */
1595 0 : slot->level = FD_GUI_SLOT_LEVEL_COMPLETED;
1596 0 : fd_gui_printf_slot( gui, i );
1597 0 : fd_http_server_ws_broadcast( gui->http );
1598 0 : }
1599 0 : }
1600 0 : }
1601 :
1602 0 : gui->summary.slot_optimistically_confirmed = _slot;
1603 0 : fd_gui_printf_optimistically_confirmed_slot( gui );
1604 0 : fd_http_server_ws_broadcast( gui->http );
1605 0 : }
1606 :
1607 : static void
1608 : fd_gui_handle_balance_update( fd_gui_t * gui,
1609 0 : ulong const * msg ) {
1610 0 : switch( msg[ 0 ] ) {
1611 0 : case 0UL:
1612 0 : gui->summary.identity_account_balance = msg[ 1 ];
1613 0 : fd_gui_printf_identity_balance( gui );
1614 0 : fd_http_server_ws_broadcast( gui->http );
1615 0 : break;
1616 0 : case 1UL:
1617 0 : gui->summary.vote_account_balance = msg[ 1 ];
1618 0 : fd_gui_printf_vote_balance( gui );
1619 0 : fd_http_server_ws_broadcast( gui->http );
1620 0 : break;
1621 0 : default:
1622 0 : FD_LOG_ERR(( "balance: unknown account type: %lu", msg[ 0 ] ));
1623 0 : }
1624 0 : }
1625 :
1626 : static void
1627 : fd_gui_handle_start_progress( fd_gui_t * gui,
1628 0 : uchar const * msg ) {
1629 0 : uchar type = msg[ 0 ];
1630 :
1631 0 : switch (type) {
1632 0 : case 0:
1633 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_INITIALIZING;
1634 0 : FD_LOG_INFO(( "progress: initializing" ));
1635 0 : break;
1636 0 : case 1: {
1637 0 : char const * snapshot_type;
1638 0 : if( FD_UNLIKELY( gui->summary.startup_progress.startup_got_full_snapshot ) ) {
1639 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_INCREMENTAL_SNAPSHOT;
1640 0 : snapshot_type = "incremental";
1641 0 : } else {
1642 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_SEARCHING_FOR_FULL_SNAPSHOT;
1643 0 : snapshot_type = "full";
1644 0 : }
1645 0 : FD_LOG_INFO(( "progress: searching for %s snapshot", snapshot_type ));
1646 0 : break;
1647 0 : }
1648 0 : case 2: {
1649 0 : uchar is_full_snapshot = msg[ 1 ];
1650 0 : if( FD_LIKELY( is_full_snapshot ) ) {
1651 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_FULL_SNAPSHOT;
1652 0 : gui->summary.startup_progress.startup_full_snapshot_slot = *((ulong *)(msg + 2));
1653 0 : gui->summary.startup_progress.startup_full_snapshot_peer_ip_addr = *((uint *)(msg + 10));
1654 0 : gui->summary.startup_progress.startup_full_snapshot_peer_port = *((ushort *)(msg + 14));
1655 0 : gui->summary.startup_progress.startup_full_snapshot_total_bytes = *((ulong *)(msg + 16));
1656 0 : gui->summary.startup_progress.startup_full_snapshot_current_bytes = *((ulong *)(msg + 24));
1657 0 : gui->summary.startup_progress.startup_full_snapshot_elapsed_secs = *((double *)(msg + 32));
1658 0 : gui->summary.startup_progress.startup_full_snapshot_remaining_secs = *((double *)(msg + 40));
1659 0 : gui->summary.startup_progress.startup_full_snapshot_throughput = *((double *)(msg + 48));
1660 0 : FD_LOG_INFO(( "progress: downloading full snapshot: slot=%lu", gui->summary.startup_progress.startup_full_snapshot_slot ));
1661 0 : } else {
1662 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_DOWNLOADING_INCREMENTAL_SNAPSHOT;
1663 0 : gui->summary.startup_progress.startup_incremental_snapshot_slot = *((ulong *)(msg + 2));
1664 0 : gui->summary.startup_progress.startup_incremental_snapshot_peer_ip_addr = *((uint *)(msg + 10));
1665 0 : gui->summary.startup_progress.startup_incremental_snapshot_peer_port = *((ushort *)(msg + 14));
1666 0 : gui->summary.startup_progress.startup_incremental_snapshot_total_bytes = *((ulong *)(msg + 16));
1667 0 : gui->summary.startup_progress.startup_incremental_snapshot_current_bytes = *((ulong *)(msg + 24));
1668 0 : gui->summary.startup_progress.startup_incremental_snapshot_elapsed_secs = *((double *)(msg + 32));
1669 0 : gui->summary.startup_progress.startup_incremental_snapshot_remaining_secs = *((double *)(msg + 40));
1670 0 : gui->summary.startup_progress.startup_incremental_snapshot_throughput = *((double *)(msg + 48));
1671 0 : FD_LOG_INFO(( "progress: downloading incremental snapshot: slot=%lu", gui->summary.startup_progress.startup_incremental_snapshot_slot ));
1672 0 : }
1673 0 : break;
1674 0 : }
1675 0 : case 3: {
1676 0 : gui->summary.startup_progress.startup_got_full_snapshot = 1;
1677 0 : break;
1678 0 : }
1679 0 : case 4:
1680 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_CLEANING_BLOCK_STORE;
1681 0 : FD_LOG_INFO(( "progress: cleaning block store" ));
1682 0 : break;
1683 0 : case 5:
1684 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_CLEANING_ACCOUNTS;
1685 0 : FD_LOG_INFO(( "progress: cleaning accounts" ));
1686 0 : break;
1687 0 : case 6:
1688 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_LOADING_LEDGER;
1689 0 : FD_LOG_INFO(( "progress: loading ledger" ));
1690 0 : break;
1691 0 : case 7: {
1692 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_PROCESSING_LEDGER;
1693 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_PROCESSING_LEDGER;
1694 0 : gui->summary.startup_progress.startup_ledger_slot = fd_ulong_load_8( msg + 1 );
1695 0 : gui->summary.startup_progress.startup_ledger_max_slot = fd_ulong_load_8( msg + 9 );
1696 0 : FD_LOG_INFO(( "progress: processing ledger: slot=%lu, max_slot=%lu", gui->summary.startup_progress.startup_ledger_slot, gui->summary.startup_progress.startup_ledger_max_slot ));
1697 0 : break;
1698 0 : }
1699 0 : case 8:
1700 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_STARTING_SERVICES;
1701 0 : FD_LOG_INFO(( "progress: starting services" ));
1702 0 : break;
1703 0 : case 9:
1704 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_HALTED;
1705 0 : FD_LOG_INFO(( "progress: halted" ));
1706 0 : break;
1707 0 : case 10: {
1708 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_WAITING_FOR_SUPERMAJORITY;
1709 0 : gui->summary.startup_progress.startup_waiting_for_supermajority_slot = fd_ulong_load_8( msg + 1 );
1710 0 : gui->summary.startup_progress.startup_waiting_for_supermajority_stake_pct = fd_ulong_load_8( msg + 9 );
1711 0 : FD_LOG_INFO(( "progress: waiting for supermajority: slot=%lu, gossip_stake_percent=%lu", gui->summary.startup_progress.startup_waiting_for_supermajority_slot, gui->summary.startup_progress.startup_waiting_for_supermajority_stake_pct ));
1712 0 : break;
1713 0 : }
1714 0 : case 11:
1715 0 : gui->summary.startup_progress.phase = FD_GUI_START_PROGRESS_TYPE_RUNNING;
1716 0 : FD_LOG_INFO(( "progress: running" ));
1717 0 : break;
1718 0 : default:
1719 0 : FD_LOG_ERR(( "progress: unknown type: %u", type ));
1720 0 : }
1721 :
1722 0 : fd_gui_printf_startup_progress( gui );
1723 0 : fd_http_server_ws_broadcast( gui->http );
1724 0 : }
1725 :
1726 : static void
1727 : fd_gui_handle_genesis_hash( fd_gui_t * gui,
1728 0 : uchar const * msg ) {
1729 0 : FD_BASE58_ENCODE_32_BYTES(msg, hash_cstr);
1730 0 : ulong cluster = fd_genesis_cluster_identify(hash_cstr);
1731 0 : char const * cluster_name = fd_genesis_cluster_name(cluster);
1732 :
1733 0 : if( FD_LIKELY( strcmp( gui->summary.cluster, cluster_name ) ) ) {
1734 0 : gui->summary.cluster = fd_genesis_cluster_name(cluster);
1735 0 : fd_gui_printf_cluster( gui );
1736 0 : fd_http_server_ws_broadcast( gui->http );
1737 0 : }
1738 0 : }
1739 :
1740 : static void
1741 : fd_gui_handle_block_engine_update( fd_gui_t * gui,
1742 0 : uchar const * msg ) {
1743 0 : fd_plugin_msg_block_engine_update_t const * update = (fd_plugin_msg_block_engine_update_t const *)msg;
1744 :
1745 0 : gui->block_engine.has_block_engine = 1;
1746 0 : memcpy( gui->block_engine.name, update->name, sizeof(gui->block_engine.name )-1 );
1747 0 : memcpy( gui->block_engine.url, update->url, sizeof(gui->block_engine.url )-1 );
1748 0 : memcpy( gui->block_engine.ip_cstr, update->ip_cstr, sizeof(gui->block_engine.ip_cstr)-1 );
1749 0 : gui->block_engine.status = update->status;
1750 :
1751 0 : fd_gui_printf_block_engine( gui );
1752 0 : fd_http_server_ws_broadcast( gui->http );
1753 0 : }
1754 :
1755 : void
1756 : fd_gui_plugin_message( fd_gui_t * gui,
1757 : ulong plugin_msg,
1758 0 : uchar const * msg ) {
1759 :
1760 0 : switch( plugin_msg ) {
1761 0 : case FD_PLUGIN_MSG_SLOT_ROOTED:
1762 0 : fd_gui_handle_rooted_slot( gui, (ulong *)msg );
1763 0 : break;
1764 0 : case FD_PLUGIN_MSG_SLOT_OPTIMISTICALLY_CONFIRMED:
1765 0 : fd_gui_handle_optimistically_confirmed_slot( gui, (ulong *)msg );
1766 0 : break;
1767 0 : case FD_PLUGIN_MSG_SLOT_COMPLETED:
1768 0 : fd_gui_handle_completed_slot( gui, (ulong *)msg );
1769 0 : break;
1770 0 : case FD_PLUGIN_MSG_SLOT_ESTIMATED:
1771 0 : gui->summary.slot_estimated = *(ulong const *)msg;
1772 0 : fd_gui_printf_estimated_slot( gui );
1773 0 : fd_http_server_ws_broadcast( gui->http );
1774 0 : break;
1775 0 : case FD_PLUGIN_MSG_LEADER_SCHEDULE: {
1776 0 : fd_gui_handle_leader_schedule( gui, (ulong const *)msg );
1777 0 : break;
1778 0 : }
1779 0 : case FD_PLUGIN_MSG_SLOT_START: {
1780 0 : fd_gui_handle_slot_start( gui, (ulong *)msg );
1781 0 : break;
1782 0 : }
1783 0 : case FD_PLUGIN_MSG_SLOT_END: {
1784 0 : fd_gui_handle_slot_end( gui, (ulong *)msg );
1785 0 : break;
1786 0 : }
1787 0 : case FD_PLUGIN_MSG_GOSSIP_UPDATE: {
1788 0 : fd_gui_handle_gossip_update( gui, msg );
1789 0 : break;
1790 0 : }
1791 0 : case FD_PLUGIN_MSG_VOTE_ACCOUNT_UPDATE: {
1792 0 : fd_gui_handle_vote_account_update( gui, msg );
1793 0 : break;
1794 0 : }
1795 0 : case FD_PLUGIN_MSG_VALIDATOR_INFO: {
1796 0 : fd_gui_handle_validator_info_update( gui, msg );
1797 0 : break;
1798 0 : }
1799 0 : case FD_PLUGIN_MSG_SLOT_RESET: {
1800 0 : fd_gui_handle_reset_slot( gui, (ulong *)msg );
1801 0 : break;
1802 0 : }
1803 0 : case FD_PLUGIN_MSG_BALANCE: {
1804 0 : fd_gui_handle_balance_update( gui, (ulong *)msg );
1805 0 : break;
1806 0 : }
1807 0 : case FD_PLUGIN_MSG_START_PROGRESS: {
1808 0 : fd_gui_handle_start_progress( gui, msg );
1809 0 : break;
1810 0 : }
1811 0 : case FD_PLUGIN_MSG_GENESIS_HASH_KNOWN: {
1812 0 : fd_gui_handle_genesis_hash( gui, msg );
1813 0 : break;
1814 0 : }
1815 0 : case FD_PLUGIN_MSG_BLOCK_ENGINE_UPDATE: {
1816 0 : fd_gui_handle_block_engine_update( gui, msg );
1817 0 : break;
1818 0 : }
1819 0 : default:
1820 0 : FD_LOG_ERR(( "Unhandled plugin msg: 0x%lx", plugin_msg ));
1821 0 : break;
1822 0 : }
1823 0 : }
1824 :
1825 : static void
1826 : fd_gui_init_slot_txns( fd_gui_t * gui,
1827 : long tickcount,
1828 0 : ulong _slot ) {
1829 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1830 0 : if( FD_UNLIKELY( slot->slot!=_slot ) ) fd_gui_clear_slot( gui, _slot, ULONG_MAX );
1831 :
1832 : /* initialize reference timestamp */
1833 0 : if ( FD_UNLIKELY( LONG_MAX==slot->txs.reference_ticks ) ) {
1834 0 : slot->txs.reference_ticks = tickcount;
1835 0 : slot->txs.reference_nanos = fd_log_wallclock() - (long)((double)(fd_tickcount() - slot->txs.reference_ticks) / fd_tempo_tick_per_ns( NULL ));
1836 0 : }
1837 0 : }
1838 :
1839 : void
1840 : fd_gui_became_leader( fd_gui_t * gui,
1841 : long tickcount,
1842 : ulong _slot,
1843 : long start_time_nanos,
1844 : long end_time_nanos,
1845 : ulong max_compute_units,
1846 0 : ulong max_microblocks ) {
1847 0 : fd_gui_init_slot_txns( gui, tickcount, _slot );
1848 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1849 0 : slot->max_compute_units = (uint)max_compute_units;
1850 :
1851 0 : slot->txs.leader_start_time = start_time_nanos;
1852 0 : slot->txs.leader_end_time = end_time_nanos;
1853 0 : if( FD_LIKELY( slot->txs.microblocks_upper_bound==USHORT_MAX ) ) slot->txs.microblocks_upper_bound = (ushort)max_microblocks;
1854 0 : }
1855 :
1856 : void
1857 : fd_gui_unbecame_leader( fd_gui_t * gui,
1858 : long tickcount,
1859 : ulong _slot,
1860 0 : ulong microblocks_in_slot ) {
1861 0 : fd_gui_init_slot_txns( gui, tickcount, _slot );
1862 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1863 :
1864 0 : slot->txs.microblocks_upper_bound = (ushort)microblocks_in_slot;
1865 0 : }
1866 :
1867 : void
1868 : fd_gui_microblock_execution_begin( fd_gui_t * gui,
1869 : long tickcount,
1870 : ulong _slot,
1871 : fd_txn_p_t * txns,
1872 : ulong txn_cnt,
1873 : uint microblock_idx,
1874 0 : ulong pack_txn_idx ) {
1875 0 : fd_gui_init_slot_txns( gui, tickcount, _slot );
1876 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1877 :
1878 0 : if( FD_UNLIKELY( slot->txs.start_offset==ULONG_MAX ) ) slot->txs.start_offset = pack_txn_idx;
1879 0 : else slot->txs.start_offset = fd_ulong_min( slot->txs.start_offset, pack_txn_idx );
1880 :
1881 0 : gui->pack_txn_idx = fd_ulong_max( gui->pack_txn_idx, pack_txn_idx+txn_cnt-1UL );
1882 :
1883 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1884 0 : fd_txn_p_t * txn_payload = &txns[ i ];
1885 0 : fd_txn_t * txn = TXN( txn_payload );
1886 :
1887 0 : ulong sig_rewards = FD_PACK_FEE_PER_SIGNATURE * txn->signature_cnt;
1888 0 : ulong priority_rewards = ULONG_MAX;
1889 0 : ulong requested_execution_cus = ULONG_MAX;
1890 0 : ulong precompile_sigs = ULONG_MAX;
1891 0 : ulong requested_loaded_accounts_data_cost = ULONG_MAX;
1892 0 : uint _flags;
1893 0 : ulong cost_estimate = fd_pack_compute_cost( txn, txn_payload->payload, &_flags, &requested_execution_cus, &priority_rewards, &precompile_sigs, &requested_loaded_accounts_data_cost );
1894 0 : sig_rewards += FD_PACK_FEE_PER_SIGNATURE * precompile_sigs;
1895 0 : sig_rewards = sig_rewards * FD_PACK_TXN_FEE_BURN_PCT / 100UL;
1896 :
1897 0 : fd_gui_txn_t * txn_entry = gui->txs[ (pack_txn_idx + i)%FD_GUI_TXN_HISTORY_SZ ];
1898 0 : fd_memcpy(txn_entry->signature, txn_payload->payload + txn->signature_off, FD_SHA512_HASH_SZ);
1899 0 : txn_entry->timestamp_arrival_nanos = txn_payload->scheduler_arrival_time_nanos;
1900 0 : txn_entry->compute_units_requested = cost_estimate & 0x1FFFFFU;
1901 0 : txn_entry->priority_fee = priority_rewards;
1902 0 : txn_entry->transaction_fee = sig_rewards;
1903 0 : txn_entry->timestamp_delta_start_nanos = (int)((double)(tickcount - slot->txs.reference_ticks) / fd_tempo_tick_per_ns( NULL ));
1904 0 : txn_entry->source_ipv4 = txn_payload->source_ipv4;
1905 0 : txn_entry->source_tpu = txn_payload->source_tpu;
1906 0 : txn_entry->microblock_idx = microblock_idx;
1907 0 : txn_entry->flags |= (uchar)FD_GUI_TXN_FLAGS_STARTED;
1908 0 : txn_entry->flags &= (uchar)(~(uchar)(FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE | FD_GUI_TXN_FLAGS_FROM_BUNDLE));
1909 0 : txn_entry->flags |= (uchar)fd_uint_if(txn_payload->flags & FD_TXN_P_FLAGS_IS_SIMPLE_VOTE, FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE, 0U);
1910 0 : txn_entry->flags |= (uchar)fd_uint_if((txn_payload->flags & FD_TXN_P_FLAGS_BUNDLE) || (txn_payload->flags & FD_TXN_P_FLAGS_INITIALIZER_BUNDLE), FD_GUI_TXN_FLAGS_FROM_BUNDLE, 0U);
1911 0 : }
1912 :
1913 : /* At the moment, bank publishes at most 1 transaction per microblock,
1914 : even if it received microblocks with multiple transactions
1915 : (i.e. a bundle). This means that we need to calculate microblock
1916 : count here based on the transaction count. */
1917 0 : slot->txs.begin_microblocks = (ushort)(slot->txs.begin_microblocks + txn_cnt);
1918 0 : }
1919 :
1920 : void
1921 : fd_gui_microblock_execution_end( fd_gui_t * gui,
1922 : long tickcount,
1923 : ulong bank_idx,
1924 : ulong _slot,
1925 : ulong txn_cnt,
1926 : fd_txn_p_t * txns,
1927 : ulong pack_txn_idx,
1928 : uchar txn_start_pct,
1929 : uchar txn_load_end_pct,
1930 : uchar txn_end_pct,
1931 : uchar txn_preload_end_pct,
1932 0 : ulong tips ) {
1933 0 : if( FD_UNLIKELY( 1UL!=txn_cnt ) ) FD_LOG_ERR(( "gui expects 1 txn per microblock from bank, found %lu", txn_cnt ));
1934 :
1935 0 : fd_gui_init_slot_txns( gui, tickcount, _slot );
1936 0 : fd_gui_slot_t * slot = gui->slots[ _slot % FD_GUI_SLOTS_CNT ];
1937 :
1938 0 : if( FD_UNLIKELY( slot->txs.end_offset==ULONG_MAX ) ) slot->txs.end_offset = pack_txn_idx + txn_cnt;
1939 0 : else slot->txs.end_offset = fd_ulong_max( slot->txs.end_offset, pack_txn_idx+txn_cnt );
1940 :
1941 0 : gui->pack_txn_idx = fd_ulong_max( gui->pack_txn_idx, pack_txn_idx+txn_cnt-1UL );
1942 :
1943 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1944 0 : fd_txn_p_t * txn_p = &txns[ i ];
1945 :
1946 0 : fd_gui_txn_t * txn_entry = gui->txs[ (pack_txn_idx + i)%FD_GUI_TXN_HISTORY_SZ ];
1947 0 : txn_entry->bank_idx = bank_idx & 0x3FU;
1948 0 : txn_entry->compute_units_consumed = txn_p->bank_cu.actual_consumed_cus & 0x1FFFFFU;
1949 0 : txn_entry->error_code = (txn_p->flags >> 24) & 0x3FU;
1950 0 : txn_entry->timestamp_delta_end_nanos = (int)((double)(tickcount - slot->txs.reference_ticks) / fd_tempo_tick_per_ns( NULL ));
1951 0 : txn_entry->txn_start_pct = txn_start_pct;
1952 0 : txn_entry->txn_load_end_pct = txn_load_end_pct;
1953 0 : txn_entry->txn_end_pct = txn_end_pct;
1954 0 : txn_entry->txn_preload_end_pct = txn_preload_end_pct;
1955 0 : txn_entry->tips = tips;
1956 0 : txn_entry->flags |= (uchar)FD_GUI_TXN_FLAGS_ENDED;
1957 0 : txn_entry->flags &= (uchar)(~(uchar)FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK);
1958 0 : txn_entry->flags |= (uchar)fd_uint_if(txn_p->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS, FD_GUI_TXN_FLAGS_LANDED_IN_BLOCK, 0U);
1959 0 : }
1960 :
1961 0 : slot->txs.end_microblocks = slot->txs.end_microblocks + (uint)txn_cnt;
1962 0 : }
|