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