Line data Source code
1 : #include "fd_gui_peers.h"
2 : #include "fd_gui_printf.h"
3 : #include "fd_gui_config_parse.h"
4 : #include "fd_gui_metrics.h"
5 : #include "../../disco/metrics/fd_metrics_base.h"
6 :
7 : FD_IMPORT_BINARY( dbip_f, "src/disco/gui/dbip.bin.zst" );
8 :
9 : #define LOGGING 0
10 :
11 0 : #define FD_GUI_WFS_ACTIVITY_TIMEOUT_NANOS (15L*1000L*1000L*1000L)
12 :
13 : FD_FN_CONST ulong
14 0 : fd_gui_peers_align( void ) {
15 0 : ulong a = 128UL;
16 0 : a = fd_ulong_max( a, alignof(fd_gui_peers_ctx_t) );
17 0 : a = fd_ulong_max( a, fd_gui_peers_live_table_align() );
18 0 : a = fd_ulong_max( a, fd_gui_peers_bandwidth_tracking_align() );
19 0 : a = fd_ulong_max( a, fd_gui_peers_node_info_pool_align() );
20 0 : a = fd_ulong_max( a, fd_gui_peers_node_info_map_align() );
21 0 : a = fd_ulong_max( a, fd_gui_peers_node_pubkey_map_align() );
22 0 : a = fd_ulong_max( a, fd_gui_peers_node_sock_map_align() );
23 0 : a = fd_ulong_max( a, alignof(fd_gui_peers_ws_conn_t) );
24 0 : a = fd_ulong_max( a, alignof(fd_gui_geoip_node_t) );
25 0 : FD_TEST( fd_ulong_pow2_up( a )==a );
26 0 : return a;
27 0 : }
28 :
29 : FD_FN_CONST ulong
30 0 : fd_gui_peers_footprint( ulong max_ws_conn_cnt ) {
31 0 : ulong info_chain_cnt = fd_gui_peers_node_info_map_chain_cnt_est ( FD_CONTACT_INFO_TABLE_SIZE );
32 0 : ulong pubkey_chain_cnt = fd_gui_peers_node_pubkey_map_chain_cnt_est( FD_CONTACT_INFO_TABLE_SIZE );
33 0 : ulong sock_chain_cnt = fd_gui_peers_node_sock_map_chain_cnt_est ( FD_CONTACT_INFO_TABLE_SIZE );
34 :
35 0 : ulong l = FD_LAYOUT_INIT;
36 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_gui_peers_ctx_t), sizeof(fd_gui_peers_ctx_t) );
37 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_live_table_align(), fd_gui_peers_live_table_footprint ( FD_CONTACT_INFO_TABLE_SIZE ) );
38 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_bandwidth_tracking_align(), fd_gui_peers_bandwidth_tracking_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
39 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_info_pool_align(), fd_gui_peers_node_info_pool_footprint ( FD_CONTACT_INFO_TABLE_SIZE ) );
40 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_info_map_align(), fd_gui_peers_node_info_map_footprint ( info_chain_cnt ) );
41 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_pubkey_map_align(), fd_gui_peers_node_pubkey_map_footprint ( pubkey_chain_cnt ) );
42 0 : l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_sock_map_align(), fd_gui_peers_node_sock_map_footprint ( sock_chain_cnt ) );
43 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_gui_peers_ws_conn_t), max_ws_conn_cnt*sizeof(fd_gui_peers_ws_conn_t) );
44 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_gui_geoip_node_t), sizeof(fd_gui_geoip_node_t)*FD_GUI_GEOIP_DBIP_MAX_NODES );
45 :
46 0 : #if FD_HAS_ZSTD
47 0 : l = FD_LAYOUT_APPEND( l, 16UL, ZSTD_estimateDStreamSize( 1 << FD_GUI_GEOIP_ZSTD_WINDOW_LOG ) );
48 0 : #endif
49 :
50 0 : return FD_LAYOUT_FINI( l, fd_gui_peers_align() );
51 0 : }
52 :
53 : #if FD_HAS_ZSTD
54 :
55 : static void
56 : build_geoip_trie( fd_gui_peers_ctx_t * peers,
57 : fd_gui_geoip_node_t * nodes,
58 : uchar * db_f,
59 : ulong db_f_sz,
60 : fd_gui_ip_db_t * ip_db,
61 0 : ulong max_node_cnt ) {
62 0 : ip_db->nodes = nodes;
63 0 : uchar db_buf[ 16384 ];
64 0 : ulong processed_decompressed_bytes = 0UL;
65 0 : ulong buffered_decompressed_bytes = 0UL;
66 0 : ulong processed_compressed_bytes = 0UL;
67 :
68 : /* streaming parser state */
69 0 : int done = 0;
70 0 : ulong country_code_cnt = ULONG_MAX;
71 0 : ulong country_code_idx = 0UL;
72 0 : ulong city_name_cnt = ULONG_MAX;
73 0 : ulong city_name_idx = 0UL;
74 0 : ulong node_cnt = ULONG_MAX;
75 0 : ulong node_idx = 1UL; /* including root node */
76 :
77 0 : fd_gui_geoip_node_t * root = &nodes[ 0 ];
78 0 : root->left = NULL;
79 0 : root->right = NULL;
80 0 : root->has_prefix = 0;
81 :
82 0 : for( ;; ) {
83 : /* move leftover data to the front of the buffer */
84 0 : if( FD_LIKELY( processed_decompressed_bytes ) ) {
85 0 : memmove( db_buf, db_buf+processed_decompressed_bytes, buffered_decompressed_bytes-processed_decompressed_bytes );
86 0 : buffered_decompressed_bytes -= processed_decompressed_bytes;
87 0 : processed_decompressed_bytes = 0UL;
88 0 : }
89 :
90 0 : if( FD_LIKELY( !done && buffered_decompressed_bytes<sizeof(db_buf) ) ) {
91 0 : ulong compressed_sz = 0UL;
92 0 : ulong decompressed_sz = 0UL;
93 0 : ulong err = ZSTD_decompressStream_simpleArgs( peers->zstd_dctx, db_buf + buffered_decompressed_bytes, sizeof(db_buf)-buffered_decompressed_bytes, &decompressed_sz, db_f + processed_compressed_bytes, db_f_sz-processed_compressed_bytes, &compressed_sz );
94 0 : if( FD_UNLIKELY( ZSTD_isError( err ) ) ) FD_LOG_ERR(( "ZSTD_decompressStream_simpleArgs failed (%s)", ZSTD_getErrorName( err ) ) );
95 0 : done = err==0UL;
96 0 : buffered_decompressed_bytes += decompressed_sz;
97 0 : processed_compressed_bytes += compressed_sz;
98 0 : }
99 :
100 0 : if( FD_UNLIKELY( country_code_cnt==ULONG_MAX ) ) {
101 0 : if( FD_UNLIKELY( buffered_decompressed_bytes<sizeof(ulong) ) ) continue;
102 0 : country_code_cnt = FD_LOAD( ulong, db_buf );
103 0 : FD_TEST( country_code_cnt && country_code_cnt<=FD_GUI_GEOIP_MAX_COUNTRY_CNT ); /* 255 reserved for unknown */
104 0 : processed_decompressed_bytes += sizeof(ulong);
105 0 : } else if( FD_UNLIKELY( country_code_cnt!=ULONG_MAX && country_code_idx<country_code_cnt ) ) {
106 0 : if( FD_UNLIKELY( buffered_decompressed_bytes<2UL ) ) continue;
107 0 : for( ; country_code_idx<country_code_cnt; country_code_idx++ ) {
108 0 : if( FD_UNLIKELY( buffered_decompressed_bytes<2UL ) ) break;
109 0 : fd_memcpy( ip_db->country_code[ country_code_idx ], db_buf+processed_decompressed_bytes, 2UL );
110 0 : ip_db->country_code[ country_code_idx ][ 2 ] = '\0';
111 0 : processed_decompressed_bytes += 2UL;
112 0 : }
113 0 : } else if( FD_UNLIKELY( city_name_cnt==ULONG_MAX ) ) {
114 0 : if( FD_UNLIKELY( buffered_decompressed_bytes<sizeof(ulong) ) ) continue;
115 0 : city_name_cnt = FD_LOAD( ulong, db_buf );
116 0 : FD_TEST( city_name_cnt<=FD_GUI_GEOIP_MAX_CITY_CNT );
117 0 : processed_decompressed_bytes += sizeof(ulong);
118 0 : } else if( FD_UNLIKELY( city_name_cnt!=ULONG_MAX && city_name_idx<city_name_cnt ) ) {
119 0 : for( ; city_name_idx<city_name_cnt && memchr( db_buf+processed_decompressed_bytes, '\0', fd_ulong_min( FD_GUI_GEOIP_MAX_CITY_NAME_SZ, sizeof(db_buf)-processed_decompressed_bytes ) ); city_name_idx++ ) {
120 0 : ulong city_name_len;
121 0 : FD_TEST( fd_cstr_printf_check( ip_db->city_name[ city_name_idx ], sizeof(ip_db->city_name[ city_name_idx ]), &city_name_len, "%s", db_buf+processed_decompressed_bytes ) );
122 0 : processed_decompressed_bytes += city_name_len+1UL;
123 0 : }
124 0 : } else if( FD_UNLIKELY( node_cnt==ULONG_MAX ) ) {
125 0 : if( FD_UNLIKELY( buffered_decompressed_bytes<sizeof(ulong) ) ) continue;
126 0 : node_cnt = FD_LOAD( ulong, db_buf );
127 0 : FD_TEST( node_cnt && 2UL*node_cnt<=max_node_cnt );
128 0 : processed_decompressed_bytes += sizeof(ulong);
129 0 : } else {
130 0 : const ulong node_sz = 10UL;
131 0 : while( buffered_decompressed_bytes-processed_decompressed_bytes>=node_sz ) {
132 0 : uint ip_addr = fd_uint_bswap( FD_LOAD( uint, db_buf+processed_decompressed_bytes ) );
133 0 : uchar prefix_len = FD_LOAD( uchar, db_buf+processed_decompressed_bytes+4UL );
134 0 : FD_TEST( prefix_len<=32UL );
135 0 : uchar country_idx = FD_LOAD( uchar, db_buf+processed_decompressed_bytes+5UL );
136 0 : FD_TEST( country_idx<country_code_cnt );
137 0 : uint city_idx = FD_LOAD( uint, db_buf+processed_decompressed_bytes+6UL );
138 0 : FD_TEST( city_idx==UINT_MAX || city_idx<city_name_cnt ); /* optional field */
139 :
140 0 : fd_gui_geoip_node_t * node = root;
141 0 : for( uchar bit_pos=0; bit_pos<prefix_len; bit_pos++ ) {
142 0 : uchar bit = (ip_addr >> (31 - bit_pos)) & 1;
143 :
144 0 : fd_gui_geoip_node_t * child;
145 0 : if( FD_LIKELY( !bit ) ) {
146 0 : child = node->left;
147 0 : if( FD_LIKELY( !child ) ) {
148 0 : FD_TEST( node_idx<max_node_cnt );
149 0 : child = &nodes[ node_idx++ ];
150 0 : child->left = NULL;
151 0 : child->right = NULL;
152 0 : child->has_prefix = 0;
153 0 : node->left = child;
154 0 : }
155 0 : } else {
156 0 : child = node->right;
157 0 : if( FD_LIKELY( !child ) ) {
158 0 : FD_TEST( node_idx<max_node_cnt );
159 0 : child = &nodes[ node_idx++ ];
160 0 : child->left = NULL;
161 0 : child->right = NULL;
162 0 : child->has_prefix = 0;
163 0 : node->right = child;
164 0 : }
165 0 : }
166 0 : node = child;
167 0 : }
168 :
169 0 : node->has_prefix = 1;
170 0 : node->country_code_idx = country_idx;
171 0 : node->city_name_idx = city_idx;
172 :
173 0 : processed_decompressed_bytes += node_sz;
174 0 : }
175 :
176 : /* file was fully decompressed */
177 0 : if( FD_UNLIKELY( done ) ) {
178 0 : for( ulong i=1UL; i<country_code_cnt; i++ ) {
179 0 : if( FD_UNLIKELY( strcmp( ip_db->country_code[ i-1UL ], ip_db->country_code[ i ] ) > 0 ) ) {
180 0 : FD_LOG_ERR(("country codes not sorted a=%s > b=%s country_code_cnt=%lu i=%lu", ip_db->country_code[ i-1UL ], ip_db->country_code[ i ], country_code_cnt, i ) );
181 0 : }
182 0 : }
183 :
184 0 : for( ulong i=1UL; i<city_name_cnt; i++ ) {
185 0 : if( FD_UNLIKELY( strcmp( ip_db->city_name[ i-1UL ], ip_db->city_name[ i ] ) > 0 ) ) {
186 0 : FD_LOG_ERR(("city names not sorted a=%s > b=%s city_name_cnt=%lu i=%lu ", ip_db->city_name[ i-1UL ], ip_db->city_name[ i ], city_name_cnt, i ) );
187 0 : }
188 0 : }
189 :
190 0 : FD_TEST( buffered_decompressed_bytes==processed_decompressed_bytes );
191 0 : return;
192 0 : }
193 0 : }
194 0 : }
195 0 : }
196 :
197 : #endif
198 :
199 : void *
200 : fd_gui_peers_new( void * shmem,
201 : fd_http_server_t * http,
202 : fd_topo_t const * topo,
203 : ulong max_ws_conn_cnt,
204 : char const * wfs_expected_bank_hash_cstr,
205 0 : long now ) {
206 0 : if( FD_UNLIKELY( !shmem ) ) {
207 0 : FD_LOG_WARNING(( "NULL shmem" ));
208 0 : return NULL;
209 0 : }
210 :
211 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_peers_align() ) ) ) {
212 0 : FD_LOG_WARNING(( "misaligned shmem" ));
213 0 : return NULL;
214 0 : }
215 :
216 0 : ulong info_chain_cnt = fd_gui_peers_node_info_map_chain_cnt_est ( FD_CONTACT_INFO_TABLE_SIZE );
217 0 : ulong pubkey_chain_cnt = fd_gui_peers_node_pubkey_map_chain_cnt_est( FD_CONTACT_INFO_TABLE_SIZE );
218 0 : ulong sock_chain_cnt = fd_gui_peers_node_sock_map_chain_cnt_est ( FD_CONTACT_INFO_TABLE_SIZE );
219 :
220 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
221 0 : fd_gui_peers_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_gui_peers_ctx_t), sizeof(fd_gui_peers_ctx_t) );
222 0 : void * _live_table = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_live_table_align(), fd_gui_peers_live_table_footprint ( FD_CONTACT_INFO_TABLE_SIZE ) );
223 0 : void * _bw_tracking = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_bandwidth_tracking_align(), fd_gui_peers_bandwidth_tracking_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
224 0 : void * _info_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_node_info_pool_align(), fd_gui_peers_node_info_pool_footprint ( FD_CONTACT_INFO_TABLE_SIZE ) );
225 0 : void * _info_map = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_node_info_map_align(), fd_gui_peers_node_info_map_footprint ( info_chain_cnt ) );
226 0 : void * _pubkey_map = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_node_pubkey_map_align(), fd_gui_peers_node_pubkey_map_footprint ( pubkey_chain_cnt ) );
227 0 : void * _sock_map = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_node_sock_map_align(), fd_gui_peers_node_sock_map_footprint ( sock_chain_cnt ) );
228 0 : ctx->client_viewports = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_gui_peers_ws_conn_t), max_ws_conn_cnt*sizeof(fd_gui_peers_ws_conn_t) );
229 0 : #if FD_HAS_ZSTD
230 0 : void * _dbip_nodes = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_gui_geoip_node_t), sizeof(fd_gui_geoip_node_t)*FD_GUI_GEOIP_DBIP_MAX_NODES );
231 :
232 0 : uchar * _zstd_ctx = FD_SCRATCH_ALLOC_APPEND( l, 16UL, ZSTD_estimateDStreamSize( 1 << FD_GUI_GEOIP_ZSTD_WINDOW_LOG ) );
233 0 : ctx->zstd_dctx = ZSTD_initStaticDStream( _zstd_ctx, ZSTD_estimateDStreamSize( 1 << FD_GUI_GEOIP_ZSTD_WINDOW_LOG ) );
234 0 : FD_TEST( ctx->zstd_dctx );
235 0 : #endif
236 :
237 0 : for( ulong i = 0UL; i<max_ws_conn_cnt; i++ ) ctx->client_viewports[ i ].connected = 0;
238 :
239 0 : ctx->http = http;
240 0 : ctx->topo = topo;
241 :
242 0 : ctx->wfs_enabled = !!strcmp( wfs_expected_bank_hash_cstr, "" );
243 :
244 0 : ctx->max_ws_conn_cnt = max_ws_conn_cnt;
245 0 : ctx->open_ws_conn_cnt = 0UL;
246 0 : ctx->active_ws_conn_id = ULONG_MAX;
247 :
248 0 : ctx->slot_voted = ULONG_MAX;
249 :
250 0 : for( ulong i=0UL; i<2UL; i++ ) {
251 0 : ctx->epochs[ i ].epoch = ULONG_MAX;
252 0 : ctx->epochs[ i ].stakes_cnt = 0UL;
253 0 : }
254 :
255 0 : ctx->next_client_nanos = now;
256 0 : ctx->next_metric_rate_update_nanos = now;
257 0 : ctx->next_gossip_stats_update_nanos = now;
258 0 : memset( &ctx->gossip_stats, 0, sizeof(ctx->gossip_stats) );
259 :
260 0 : for( ulong i = 0; i<FD_CONTACT_INFO_TABLE_SIZE; i++) ctx->contact_info_table[ i ].row.valid = 0;
261 :
262 0 : ctx->live_table = fd_gui_peers_live_table_join( fd_gui_peers_live_table_new( _live_table, FD_CONTACT_INFO_TABLE_SIZE ) );
263 0 : fd_gui_peers_live_table_seed( ctx->contact_info_table, FD_CONTACT_INFO_TABLE_SIZE, 42UL );
264 :
265 0 : ctx->bw_tracking = fd_gui_peers_bandwidth_tracking_join( fd_gui_peers_bandwidth_tracking_new( _bw_tracking, FD_CONTACT_INFO_TABLE_SIZE ) );
266 0 : fd_gui_peers_bandwidth_tracking_seed( ctx->contact_info_table, FD_CONTACT_INFO_TABLE_SIZE, 42UL );
267 :
268 0 : ctx->node_info_pool = fd_gui_peers_node_info_pool_join ( fd_gui_peers_node_info_pool_new ( _info_pool, FD_CONTACT_INFO_TABLE_SIZE ) );
269 0 : ctx->node_info_map = fd_gui_peers_node_info_map_join ( fd_gui_peers_node_info_map_new ( _info_map, info_chain_cnt, 42UL ) );
270 0 : ctx->node_pubkey_map = fd_gui_peers_node_pubkey_map_join( fd_gui_peers_node_pubkey_map_new( _pubkey_map, pubkey_chain_cnt, 42UL ) );
271 0 : ctx->node_sock_map = fd_gui_peers_node_sock_map_join ( fd_gui_peers_node_sock_map_new ( _sock_map, sock_chain_cnt, 42UL ) );
272 :
273 0 : #if FD_HAS_ZSTD
274 0 : build_geoip_trie( ctx, _dbip_nodes, (uchar *)dbip_f, dbip_f_sz, &ctx->dbip, FD_GUI_GEOIP_DBIP_MAX_NODES );
275 0 : #endif
276 :
277 0 : ctx->wfs_peers_cnt = 0UL;
278 0 : ctx->wfs_peers_valid = 0;
279 0 : ctx->wfs_stakes_sent = 0;
280 0 : wfs_fresh_dlist_join( wfs_fresh_dlist_new( ctx->wfs_fresh_dlist ) );
281 :
282 0 : return shmem;
283 0 : }
284 :
285 : fd_gui_peers_ctx_t *
286 0 : fd_gui_peers_join( void * shmem ) {
287 0 : if( FD_UNLIKELY( !shmem ) ) {
288 0 : FD_LOG_WARNING(( "NULL shmem" ));
289 0 : return NULL;
290 0 : }
291 :
292 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_peers_align() ) ) ) {
293 0 : FD_LOG_WARNING(( "misaligned shmem" ));
294 0 : return NULL;
295 0 : }
296 :
297 0 : fd_gui_peers_ctx_t * ctx = (fd_gui_peers_ctx_t *)shmem;
298 :
299 0 : return ctx;
300 0 : }
301 :
302 : static void
303 : fd_gui_peers_gossip_stats_snap( fd_gui_peers_ctx_t * peers,
304 : fd_gui_peers_gossip_stats_t * gossip_stats,
305 0 : long now ) {
306 0 : gossip_stats->sample_time = now;
307 0 : ulong gossvf_tile_cnt = fd_topo_tile_name_cnt( peers->topo, "gossvf" );
308 0 : ulong gossip_tile_cnt = 1UL;
309 :
310 0 : gossip_stats->network_health_pull_response_msg_rx_success =
311 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PULL_RESPONSE ) );
312 0 : gossip_stats->network_health_pull_response_msg_rx_failure =
313 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_RESPONSE_LOOPBACK ) )
314 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_RESPONSE_NO_VALID_CRDS ) );
315 0 : gossip_stats->network_health_push_msg_rx_success =
316 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PUSH ) );
317 0 : gossip_stats->network_health_push_msg_rx_failure =
318 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PUSH_LOOPBACK ) )
319 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PUSH_NO_VALID_CRDS ) );
320 0 : gossip_stats->network_health_push_crds_rx_success =
321 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_UPSERTED_PUSH ) );
322 0 : gossip_stats->network_health_push_crds_rx_failure =
323 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PUSH_STALE ) )
324 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PUSH_DUPLICATE ) )
325 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PUSH_SIGNATURE ) )
326 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PUSH_ORIGIN_NO_CONTACT_INFO ) )
327 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PUSH_ORIGIN_SHRED_VERSION ) )
328 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PUSH_INACTIVE ) )
329 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PUSH_WALLCLOCK ) );
330 0 : gossip_stats->network_health_pull_response_crds_rx_success =
331 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_UPSERTED_PULL_RESPONSE ) );
332 0 : gossip_stats->network_health_pull_response_crds_rx_failure =
333 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PULL_RESPONSE_STALE ) )
334 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PULL_RESPONSE_DUPLICATE ) )
335 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_WALLCLOCK ) )
336 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_DUPLICATE ) )
337 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_SIGNATURE ) )
338 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_ORIGIN_NO_CONTACT_INFO ) )
339 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_ORIGIN_SHRED_VERSION ) )
340 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_INACTIVE ) );
341 0 : gossip_stats->network_health_push_crds_rx_duplicate =
342 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PUSH_DUPLICATE ) );
343 0 : gossip_stats->network_health_pull_response_crds_rx_duplicate =
344 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_DROPPED_PULL_RESPONSE_DUPLICATE ) )
345 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_DROPPED_PULL_RESPONSE_DUPLICATE ) );
346 :
347 0 : gossip_stats->network_health_total_stake = 0UL; /* todo ... fetch from RPC */
348 0 : gossip_stats->network_health_total_peers = 0UL; /* todo ... fetch from RPC */
349 :
350 0 : gossip_stats->network_health_connected_stake = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_STAKE ) );
351 0 : gossip_stats->network_health_connected_staked_peers = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_STAKED ) );
352 0 : gossip_stats->network_health_connected_unstaked_peers = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_UNSTAKED ) );
353 :
354 0 : gossip_stats->network_ingress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
355 0 : gossip_stats->network_ingress_total_bytes_per_sec = 0UL;
356 :
357 0 : for( fd_gui_peers_bandwidth_tracking_fwd_iter_t iter = fd_gui_peers_bandwidth_tracking_fwd_iter_init( peers->bw_tracking, &FD_GUI_PEERS_BW_TRACKING_INGRESS_SORT_KEY, peers->contact_info_table ), j = 0UL;
358 0 : !fd_gui_peers_bandwidth_tracking_fwd_iter_done( iter );
359 0 : iter = fd_gui_peers_bandwidth_tracking_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
360 0 : fd_gui_peers_node_t * cur = fd_gui_peers_bandwidth_tracking_fwd_iter_ele( iter, peers->contact_info_table );
361 :
362 0 : if( FD_UNLIKELY( j<gossip_stats->network_ingress_peer_sz ) ) {
363 0 : fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &cur->row.pubkey, NULL, peers->node_info_pool );
364 0 : if( FD_LIKELY( node_info ) ) FD_TEST( fd_cstr_printf_check( gossip_stats->network_ingress_peer_names[ j ], FD_GUI_CONFIG_PARSE_VALIDATOR_INFO_NAME_SZ+1UL, NULL, "%s", node_info->name ) );
365 0 : else gossip_stats->network_ingress_peer_names[ j ][ 0 ] = '\0';
366 0 : gossip_stats->network_ingress_peer_bytes_per_sec[ j ] = cur->row.gossvf_rx_sum.rate_ema;
367 0 : fd_memcpy( &gossip_stats->network_ingress_peer_identities[ j ], cur->row.pubkey.uc, 32UL );
368 0 : }
369 :
370 0 : gossip_stats->network_ingress_total_bytes_per_sec += cur->row.gossvf_rx_sum.rate_ema;
371 0 : }
372 :
373 0 : gossip_stats->network_ingress_total_bytes = fd_gui_metrics_gossip_total_ingress_bytes( peers->topo, gossvf_tile_cnt );
374 :
375 0 : gossip_stats->network_egress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
376 :
377 0 : FD_TEST( gossip_stats->network_egress_peer_sz==gossip_stats->network_ingress_peer_sz );
378 :
379 0 : gossip_stats->network_egress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
380 0 : gossip_stats->network_egress_total_bytes_per_sec = 0UL;
381 :
382 0 : for( fd_gui_peers_bandwidth_tracking_fwd_iter_t iter = fd_gui_peers_bandwidth_tracking_fwd_iter_init( peers->bw_tracking, &FD_GUI_PEERS_BW_TRACKING_EGRESS_SORT_KEY, peers->contact_info_table ), j = 0UL;
383 0 : !fd_gui_peers_bandwidth_tracking_fwd_iter_done( iter );
384 0 : iter = fd_gui_peers_bandwidth_tracking_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
385 0 : fd_gui_peers_node_t * cur = fd_gui_peers_bandwidth_tracking_fwd_iter_ele( iter, peers->contact_info_table );
386 :
387 0 : if( FD_UNLIKELY( j<gossip_stats->network_egress_peer_sz ) ) {
388 0 : fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &cur->row.pubkey, NULL, peers->node_info_pool );
389 0 : if( FD_LIKELY( node_info ) ) FD_TEST( fd_cstr_printf_check( gossip_stats->network_egress_peer_names[ j ], FD_GUI_CONFIG_PARSE_VALIDATOR_INFO_NAME_SZ+1UL, NULL, "%s", node_info->name ) );
390 0 : else gossip_stats->network_egress_peer_names[ j ][ 0 ] = '\0';
391 0 : gossip_stats->network_egress_peer_bytes_per_sec[ j ] = cur->row.gossip_tx_sum.rate_ema;
392 0 : fd_memcpy( &gossip_stats->network_egress_peer_identities[ j ], cur->row.pubkey.uc, 32UL );
393 0 : }
394 :
395 0 : gossip_stats->network_egress_total_bytes_per_sec += cur->row.gossip_tx_sum.rate_ema;
396 0 : }
397 :
398 0 : gossip_stats->network_egress_total_bytes = fd_gui_metrics_gossip_total_egress_bytes( peers->topo, gossip_tile_cnt );
399 :
400 0 : gossip_stats->storage_capacity = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_CAPACITY ) );
401 0 : gossip_stats->storage_expired_cnt = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_EXPIRED ) );
402 0 : gossip_stats->storage_evicted_cnt = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_EVICTED ) );
403 :
404 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_CONTACT_INFO_V1 ) );
405 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_VOTE ) );
406 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_LOWEST_SLOT ) );
407 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_SNAPSHOT_HASHES ) );
408 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_ACCOUNTS_HASHES ) );
409 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_EPOCH_SLOTS ) );
410 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_VERSION_V1 ) );
411 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_VERSION_V2 ) );
412 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_NODE_INSTANCE ) );
413 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_DUPLICATE_SHRED ) );
414 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_INCREMENTAL_SNAPSHOT_HASHES ) );
415 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_CONTACT_INFO_V2 ) );
416 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_RESTART_LAST_VOTED_FORK_SLOTS ) );
417 0 : gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_OCCUPIED_RESTART_HEAVIEST_FORK ) );
418 :
419 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX ] =
420 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_CONTACT_INFO_V1 ) )
421 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_CONTACT_INFO_V1 ) );
422 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX ] =
423 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_VOTE ) )
424 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_VOTE ) );
425 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX ] =
426 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_LOWEST_SLOT ) )
427 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_LOWEST_SLOT ) );
428 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX ] =
429 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_SNAPSHOT_HASHES ) )
430 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_SNAPSHOT_HASHES ) );
431 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX ] =
432 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_ACCOUNTS_HASHES ) )
433 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_ACCOUNTS_HASHES ) );
434 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX ] =
435 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_EPOCH_SLOTS ) )
436 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_EPOCH_SLOTS ) );
437 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX ] =
438 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_VERSION_V1 ) )
439 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_VERSION_V1 ) );
440 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX ] =
441 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_VERSION_V2 ) )
442 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_VERSION_V2 ) );
443 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX ] =
444 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_NODE_INSTANCE ) )
445 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_NODE_INSTANCE ) );
446 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX ] =
447 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_DUPLICATE_SHRED ) )
448 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_DUPLICATE_SHRED ) );
449 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX ] =
450 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_INCREMENTAL_SNAPSHOT_HASHES ) )
451 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_INCREMENTAL_SNAPSHOT_HASHES ) );
452 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX ] =
453 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_CONTACT_INFO_V2 ) )
454 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_CONTACT_INFO_V2 ) );
455 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] =
456 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_RESTART_LAST_VOTED_FORK_SLOTS ) )
457 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_RESTART_LAST_VOTED_FORK_SLOTS ) );
458 0 : gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX ] =
459 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_RESTART_HEAVIEST_FORK ) )
460 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_RESTART_HEAVIEST_FORK ) );
461 :
462 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX ] =
463 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_CONTACT_INFO_V1 ) )
464 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_CONTACT_INFO_V1 ) );
465 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX ] =
466 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_VOTE ) )
467 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_VOTE ) );
468 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX ] =
469 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_LOWEST_SLOT ) )
470 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_LOWEST_SLOT ) );
471 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX ] =
472 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_SNAPSHOT_HASHES ) )
473 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_SNAPSHOT_HASHES ) );
474 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX ] =
475 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_ACCOUNTS_HASHES ) )
476 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_ACCOUNTS_HASHES ) );
477 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX ] =
478 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_EPOCH_SLOTS ) )
479 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_EPOCH_SLOTS ) );
480 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX ] =
481 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_VERSION_V1 ) )
482 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_VERSION_V1 ) );
483 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX ] =
484 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_VERSION_V2 ) )
485 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_VERSION_V2 ) );
486 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX ] =
487 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_NODE_INSTANCE ) )
488 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_NODE_INSTANCE ) );
489 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX ] =
490 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_DUPLICATE_SHRED ) )
491 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_DUPLICATE_SHRED ) );
492 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX ] =
493 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_INCREMENTAL_SNAPSHOT_HASHES ) )
494 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_INCREMENTAL_SNAPSHOT_HASHES ) );
495 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX ] =
496 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_CONTACT_INFO_V2 ) )
497 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_CONTACT_INFO_V2 ) );
498 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] =
499 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_RESTART_LAST_VOTED_FORK_SLOTS ) )
500 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_RESTART_LAST_VOTED_FORK_SLOTS ) );
501 0 : gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX ] =
502 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PUSH_TX_BYTES_RESTART_HEAVIEST_FORK ) )
503 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_PULL_RESPONSE_TX_BYTES_RESTART_HEAVIEST_FORK ) );
504 :
505 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PULL_REQUEST ) );
506 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PULL_RESPONSE ) );
507 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PUSH ) );
508 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PING ) );
509 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PONG ) );
510 0 : gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PRUNE ) );
511 :
512 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX ] =
513 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PULL_REQUEST ) )
514 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_NOT_CONTACT_INFO ) )
515 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_LOOPBACK ) )
516 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_INACTIVE ) )
517 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_WALLCLOCK ) )
518 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_SIGNATURE ) )
519 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_SHRED_VERSION ) )
520 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_REQUEST_MASK_BITS ) );
521 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] =
522 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PULL_RESPONSE ) )
523 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_RESPONSE_LOOPBACK ) )
524 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PULL_RESPONSE_NO_VALID_CRDS ) );
525 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ] =
526 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PUSH ) )
527 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PUSH_LOOPBACK ) )
528 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PUSH_NO_VALID_CRDS ) );
529 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX ] =
530 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PING ) )
531 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PING_SIGNATURE ) );
532 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX ] =
533 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PONG ) )
534 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PONG_SIGNATURE ) );
535 0 : gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX ] =
536 0 : fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_SUCCESS_PRUNE ) )
537 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PRUNE_DESTINATION ) )
538 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PRUNE_WALLCLOCK ) )
539 0 : + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_DROPPED_PRUNE_SIGNATURE ) );
540 :
541 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PULL_REQUEST ) );
542 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PULL_RESPONSE ) );
543 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PUSH ) );
544 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PING ) );
545 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PONG ) );
546 0 : gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PRUNE ) );
547 :
548 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PULL_REQUEST ) );
549 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PULL_RESPONSE ) );
550 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PUSH ) );
551 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PING ) );
552 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PONG ) );
553 0 : gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_PRUNE ) );
554 0 : }
555 :
556 : static int
557 : fd_gui_peers_contact_info_eq( fd_gossip_contact_info_t const * ci1,
558 0 : fd_gossip_contact_info_t const * ci2 ) {
559 0 : int ci_eq =
560 0 : ci1->shred_version == ci2->shred_version
561 0 : && ci1->outset == ci2->outset
562 : // && ci1->wallclock_nanos == ci2->wallclock_nanos
563 0 : && ci1->version.client == ci2->version.client
564 0 : && ci1->version.major == ci2->version.major
565 0 : && ci1->version.minor == ci2->version.minor
566 0 : && ci1->version.patch == ci2->version.patch
567 0 : && ci1->version.commit == ci2->version.commit
568 0 : && ci1->version.feature_set == ci2->version.feature_set;
569 :
570 0 : if( FD_LIKELY( !ci_eq ) ) return 0;
571 0 : for( ulong j=0UL; j<(FD_GOSSIP_CONTACT_INFO_SOCKET_CNT); j++ ) {
572 0 : if( FD_UNLIKELY( ci1->sockets[ j ].is_ipv6 != ci2->sockets[ j ].is_ipv6 ) ) return 0;
573 :
574 0 : if( FD_UNLIKELY( ci1->sockets[ j ].is_ipv6 ) ) {
575 0 : if( FD_UNLIKELY( memcmp( ci1->sockets[ j ].ip6, ci2->sockets[ j ].ip6, 16UL ) ) ) return 0;
576 0 : } else {
577 0 : if( FD_UNLIKELY( ci1->sockets[ j ].ip4 != ci2->sockets[ j ].ip4 ) ) return 0;
578 0 : }
579 0 : if( FD_UNLIKELY( ci1->sockets[ j ].port != ci2->sockets[ j ].port ) ) return 0;
580 0 : }
581 0 : return 1;
582 0 : }
583 :
584 : void
585 : fd_gui_peers_handle_gossip_message( fd_gui_peers_ctx_t * peers,
586 : uchar const * payload,
587 : ulong payload_sz,
588 : fd_gossip_socket_t const * peer_sock,
589 0 : int is_rx ) {
590 0 : fd_gui_peers_node_t * peer = fd_gui_peers_node_sock_map_ele_query( peers->node_sock_map, peer_sock, NULL, peers->contact_info_table );
591 :
592 : /* We set MAP_MULTI=1 since there are not guarantees that duplicates
593 : sockets wont exist. In cases where we see multiple sockets the
594 : update timestamp in fd_gui_peers_node_t is the tiebreaker */
595 0 : for( fd_gui_peers_node_t * p = peer; p!=NULL; p=(fd_gui_peers_node_t *)fd_gui_peers_node_sock_map_ele_next_const( p, NULL, peers->contact_info_table ) ) {
596 0 : if( peer->row.update_time_nanos>p->row.update_time_nanos ) peer = p;
597 0 : }
598 :
599 0 : if( FD_UNLIKELY( !peer ) ) return; /* NOP, peer not known yet */
600 :
601 0 : fd_gossip_message_t message[ 1 ];
602 0 : int success = fd_gossip_message_deserialize( message, payload, payload_sz );
603 0 : if( FD_UNLIKELY( !success ) ) return; /* NOP, msg unparsable */
604 :
605 0 : FD_TEST( message->tag < FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT );
606 0 : fd_ptr_if( is_rx, &peer->row.gossvf_rx[ message->tag ], &peer->row.gossip_tx[ message->tag ] )->cur += payload_sz;
607 0 : fd_ptr_if( is_rx, (fd_gui_peers_metric_rate_t *)&peer->row.gossvf_rx_sum, (fd_gui_peers_metric_rate_t *)&peer->row.gossip_tx_sum )->cur += payload_sz;
608 : #if LOGGING
609 : if( is_rx ) FD_LOG_WARNING(("payload rx=%lu", payload_sz ));
610 : else FD_LOG_WARNING(("payload tx=%lu", payload_sz ));
611 : #endif
612 0 : }
613 :
614 : #if FD_HAS_ZSTD
615 :
616 : static fd_gui_geoip_node_t const *
617 : geoip_lookup( fd_gui_ip_db_t const * ip_db,
618 0 : uint ip_addr ) {
619 0 : fd_gui_geoip_node_t const * ret = NULL;
620 :
621 0 : uint ip_addr_host = fd_uint_bswap( ip_addr );
622 :
623 0 : fd_gui_geoip_node_t const * node = &ip_db->nodes[0];
624 :
625 0 : for( uchar bit_pos=0; bit_pos<32; bit_pos++ ) {
626 0 : if( FD_UNLIKELY( node->has_prefix ) ) {
627 0 : ret = node;
628 0 : }
629 :
630 0 : uchar bit = (ip_addr_host >> (31 - bit_pos)) & 1;
631 0 : fd_gui_geoip_node_t const * child = bit ? node->right : node->left;
632 0 : if( FD_UNLIKELY( !child ) ) break;
633 :
634 0 : node = child;
635 0 : }
636 :
637 0 : if( FD_UNLIKELY( node->has_prefix ) ) {
638 0 : ret = node;
639 0 : }
640 :
641 0 : return ret;
642 0 : }
643 :
644 : #endif
645 :
646 : #define SORT_NAME wfs_peer_sort
647 : #define SORT_KEY_T fd_gui_wfs_peer_t
648 0 : #define SORT_BEFORE(a,b) (memcmp( (a).identity_key.uc, (b).identity_key.uc, 32UL )<0)
649 : #include "../../util/tmpl/fd_sort.c"
650 :
651 : #define SORT_NAME fd_gui_peers_voter_sort_iden_desc
652 0 : #define SORT_KEY_T fd_gui_peers_voter_t
653 0 : #define SORT_BEFORE(a,b) (memcmp( (a).weight.id_key.uc, (b).weight.id_key.uc, 32UL )>0)
654 : #include "../../util/tmpl/fd_sort.c"
655 :
656 : #define SORT_NAME fd_gui_peers_voter_idx_sort_vote_desc
657 0 : #define SORT_KEY_T fd_gui_peers_voter_idx_t
658 0 : #define SORT_BEFORE(a,b) (memcmp( (a).key.uc, (b).key.uc, 32UL )>0)
659 : #include "../../util/tmpl/fd_sort.c"
660 :
661 : static void
662 : wfs_handle_contact_info_update( fd_gui_peers_ctx_t * peers,
663 : fd_pubkey_t const * identity,
664 0 : long now ) {
665 0 : if( FD_LIKELY( !peers->wfs_peers_valid ) ) return;
666 :
667 0 : ulong idx = wfs_peer_sort_split( peers->wfs_peers, peers->wfs_peers_cnt, (fd_gui_wfs_peer_t){ .identity_key = *identity } );
668 0 : if( FD_UNLIKELY( idx>=peers->wfs_peers_cnt || memcmp( identity->uc, peers->wfs_peers[ idx ].identity_key.uc, sizeof(fd_pubkey_t) ) ) ) return;
669 :
670 0 : fd_gui_wfs_peer_t * wp = &peers->wfs_peers[ idx ];
671 0 : wp->update_time_nanos = now;
672 :
673 0 : if( !wp->is_online ) {
674 0 : wp->is_online = 1;
675 0 : wfs_fresh_dlist_idx_push_tail( peers->wfs_fresh_dlist, idx, peers->wfs_peers );
676 :
677 0 : fd_gui_peers_printf_wfs_add( peers, &idx, 1UL );
678 0 : fd_http_server_ws_broadcast( peers->http );
679 0 : } else {
680 0 : wfs_fresh_dlist_idx_remove( peers->wfs_fresh_dlist, idx, peers->wfs_peers );
681 0 : wfs_fresh_dlist_idx_push_tail( peers->wfs_fresh_dlist, idx, peers->wfs_peers );
682 0 : }
683 0 : }
684 :
685 : static void
686 : wfs_handle_contact_info_remove( fd_gui_peers_ctx_t * peers,
687 0 : fd_pubkey_t const * identity ) {
688 0 : if( FD_LIKELY( !peers->wfs_peers_valid ) ) return;
689 :
690 0 : ulong idx = wfs_peer_sort_split( peers->wfs_peers, peers->wfs_peers_cnt, (fd_gui_wfs_peer_t){ .identity_key = *identity } );
691 0 : if( FD_UNLIKELY( idx>=peers->wfs_peers_cnt || memcmp( identity->uc, peers->wfs_peers[ idx ].identity_key.uc, 32UL ) ) ) return;
692 :
693 0 : fd_gui_wfs_peer_t * wp = &peers->wfs_peers[ idx ];
694 0 : if( wp->is_online ) {
695 0 : wfs_fresh_dlist_idx_remove( peers->wfs_fresh_dlist, idx, peers->wfs_peers );
696 0 : wp->is_online = 0;
697 :
698 0 : fd_gui_peers_printf_wfs_remove( peers, &idx, 1UL );
699 0 : fd_http_server_ws_broadcast( peers->http );
700 0 : }
701 0 : }
702 :
703 : static inline fd_gui_peers_voter_t const *
704 : fd_gui_peers_voter_best_for_identity( fd_gui_peers_voter_t const * voters,
705 : ulong voter_cnt,
706 0 : ulong * p_i ) {
707 0 : ulong i = *p_i;
708 0 : ulong j = i + 1UL;
709 0 : fd_gui_peers_voter_t const * best = &voters[ i ];
710 0 : while( j<voter_cnt && !memcmp( voters[ j ].weight.id_key.uc, best->weight.id_key.uc, sizeof(fd_pubkey_t) ) ) {
711 0 : fd_gui_peers_voter_t const * candidate = &voters[ j ];
712 0 : ulong slot_best = fd_ulong_if( best->vote_slot==ULONG_MAX, 0UL, best->vote_slot );
713 0 : ulong slot_cand = fd_ulong_if( candidate->vote_slot==ULONG_MAX, 0UL, candidate->vote_slot );
714 0 : if( (slot_cand>slot_best) || ((slot_cand==slot_best) && (candidate->weight.stake>best->weight.stake)) ) {
715 0 : best = candidate;
716 0 : }
717 0 : j++;
718 0 : }
719 0 : *p_i = j;
720 0 : return best;
721 0 : }
722 :
723 : void
724 : fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t * peers,
725 : fd_gossip_update_message_t const * update,
726 0 : long now ) {
727 0 : switch( update->tag ) {
728 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
729 0 : if( FD_UNLIKELY( update->contact_info->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected contact_info_idx %lu >= %lu", update->contact_info->idx, FD_CONTACT_INFO_TABLE_SIZE ));
730 0 : fd_gui_peers_node_t * peer = &peers->contact_info_table[ update->contact_info->idx ];
731 0 : if( FD_LIKELY( peer->row.valid ) ) {
732 : #if LOGGING
733 : char _pk[ FD_BASE58_ENCODED_32_SZ ];
734 : fd_base58_encode_32( update->origin, NULL, _pk );
735 : FD_LOG_WARNING(("UPDATE %lu pk=%s", update->contact_info->idx, _pk ));
736 : #endif
737 : #ifdef FD_GUI_USE_HANDHOLDING
738 : /* invariant checks */
739 : if( FD_UNLIKELY( memcmp( peer->row.pubkey.uc, update->origin, 32UL ) ) ) {
740 : char ci_pk[ FD_BASE58_ENCODED_32_SZ ];
741 : char og_pk[ FD_BASE58_ENCODED_32_SZ ];
742 : fd_base58_encode_32( peer->row.pubkey.uc, NULL, ci_pk );
743 : fd_base58_encode_32( update->origin, NULL, og_pk );
744 :
745 : /* A new pubkey is not allowed to overwrite an existing valid index */
746 : FD_LOG_ERR(( "invariant violation: peer->row.pubkey.uc=%s != update->origin=%s ", ci_pk, og_pk ));
747 : }
748 : FD_TEST( peer==fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, (fd_pubkey_t const * )update->origin, NULL, peers->contact_info_table ) );
749 : fd_gui_peers_node_t * peer_sock = fd_gui_peers_node_sock_map_ele_query( peers->node_sock_map, &peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ], NULL, peers->contact_info_table );
750 : int found = 0;
751 : for( fd_gui_peers_node_t * p = peer_sock; !!p; p=(fd_gui_peers_node_t *)fd_gui_peers_node_sock_map_ele_next_const( p, NULL, peers->contact_info_table ) ) {
752 : if( peer==p ) {
753 : found = 1;
754 : break;
755 : }
756 : }
757 : FD_TEST( found );
758 : #endif
759 : /* update does nothing */
760 0 : if( FD_UNLIKELY( fd_gui_peers_contact_info_eq( &peer->row.contact_info, update->contact_info->value ) ) ) {
761 0 : peer->row.wallclock_nanos = FD_MILLI_TO_NANOSEC( update->wallclock );
762 0 : peer->row.update_time_nanos = now;
763 0 : wfs_handle_contact_info_update( peers, (fd_pubkey_t const *)update->origin, now );
764 0 : break;
765 0 : }
766 :
767 0 : fd_gui_peers_node_sock_map_idx_remove_fast( peers->node_sock_map, update->contact_info->idx, peers->contact_info_table );
768 0 : fd_gui_peers_live_table_idx_remove ( peers->live_table, update->contact_info->idx, peers->contact_info_table );
769 :
770 0 : peer->row.pubkey = *(fd_pubkey_t *)update->origin;
771 0 : peer->row.contact_info = *update->contact_info->value;
772 0 : peer->row.wallclock_nanos = FD_MILLI_TO_NANOSEC( update->wallclock );
773 0 : peer->row.update_time_nanos = now;
774 : /* fetch and set country code */
775 0 : #if FD_HAS_ZSTD
776 0 : uint ip4 = peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].is_ipv6 ? 0 : peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].ip4;
777 0 : fd_gui_geoip_node_t const * dbip_ip = geoip_lookup( &peers->dbip, ip4 );
778 :
779 0 : peer->row.country_code_idx = dbip_ip ? dbip_ip->country_code_idx : UCHAR_MAX;
780 0 : peer->row.city_name_idx = dbip_ip ? dbip_ip->city_name_idx : UINT_MAX;
781 : #else
782 : peer->row.country_code_idx = UCHAR_MAX;
783 : peer->row.city_name_idx = UINT_MAX;
784 : #endif
785 :
786 0 : fd_gui_peers_live_table_idx_insert ( peers->live_table, update->contact_info->idx, peers->contact_info_table );
787 0 : fd_gui_peers_node_sock_map_idx_insert ( peers->node_sock_map, update->contact_info->idx, peers->contact_info_table );
788 :
789 : /* broadcast update to WebSocket clients */
790 0 : fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_UPDATE }, (ulong[]){ update->contact_info->idx }, 1UL );
791 0 : fd_http_server_ws_broadcast( peers->http );
792 :
793 0 : wfs_handle_contact_info_update( peers, (fd_pubkey_t const *)update->origin, now );
794 0 : } else {
795 : #if LOGGING
796 : char _pk[ FD_BASE58_ENCODED_32_SZ ];
797 : fd_base58_encode_32( update->origin, NULL, _pk );
798 : FD_LOG_WARNING(( "ADD %lu pk=%s", update->contact_info->idx, _pk ));
799 : #endif
800 0 : FD_TEST( !fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, fd_type_pun_const( update->origin ), NULL, peers->contact_info_table ) );
801 0 : peer->row.pubkey = *(fd_pubkey_t *)update->origin;
802 0 : memset( &peer->row.gossvf_rx, 0, sizeof(peer->row.gossvf_rx) );
803 0 : memset( &peer->row.gossip_tx, 0, sizeof(peer->row.gossip_tx) );
804 0 : memset( &peer->row.gossvf_rx_sum, 0, sizeof(peer->row.gossvf_rx_sum) );
805 0 : memset( &peer->row.gossip_tx_sum, 0, sizeof(peer->row.gossip_tx_sum) );
806 0 : peer->row.has_vote_info = 0;
807 0 : peer->row.delinquent = 0;
808 0 : peer->row.stake = ULONG_MAX;
809 :
810 : /* Backfill stake from already-received epoch data. This
811 : handles the case where epoch info arrived before this peer
812 : was known via gossip. If both epoch slots are populated,
813 : use the larger (current) epoch. */
814 0 : fd_vote_stake_weight_t best_weight = {0};
815 0 : int found = 0;
816 0 : int have_0 = peers->epochs[ 0 ].epoch!=ULONG_MAX;
817 0 : int have_1 = peers->epochs[ 1 ].epoch!=ULONG_MAX;
818 0 : ulong ep = fd_ulong_if( have_0 & have_1, fd_ulong_if( peers->epochs[ 0 ].epoch>peers->epochs[ 1 ].epoch, 0UL, 1UL ), fd_ulong_if( have_0, 0UL, fd_ulong_if( have_1, 1UL, ULONG_MAX ) ) );
819 0 : if( FD_LIKELY( ep!=ULONG_MAX ) ) {
820 0 : fd_gui_peers_voter_t const * stakes = peers->epochs[ ep ].stakes;
821 0 : ulong cnt = peers->epochs[ ep ].stakes_cnt;
822 :
823 0 : fd_gui_peers_voter_t query = { .weight = { .id_key = peer->row.pubkey } };
824 0 : ulong idx = fd_gui_peers_voter_sort_iden_desc_split( stakes, cnt, query );
825 0 : if( FD_LIKELY( idx<cnt && !memcmp( stakes[ idx ].weight.id_key.uc, peer->row.pubkey.uc, sizeof(fd_pubkey_t) ) ) ) {
826 0 : fd_gui_peers_voter_t const * best = fd_gui_peers_voter_best_for_identity( stakes, cnt, &idx );
827 0 : best_weight = best->weight;
828 0 : found = 1;
829 0 : }
830 0 : }
831 0 : if( FD_UNLIKELY( found ) ) {
832 0 : peer->row.has_vote_info = 1;
833 0 : peer->row.vote_account = best_weight.vote_key;
834 0 : peer->row.stake = best_weight.stake;
835 0 : }
836 :
837 0 : fd_gui_config_parse_info_t * info = fd_gui_peers_node_info_map_ele_query( peers->node_info_map, fd_type_pun_const(update->origin ), NULL, peers->node_info_pool );
838 0 : if( FD_LIKELY( info ) ) fd_memcpy( peer->row.name, info->name, sizeof(info->name) );
839 0 : else peer->row.name[ 0 ] = '\0';
840 :
841 0 : peer->row.wallclock_nanos = FD_MILLI_TO_NANOSEC( update->wallclock );
842 :
843 0 : peer->row.update_time_nanos = now;
844 0 : peer->row.contact_info = *update->contact_info->value;
845 :
846 : /* fetch and set country code */
847 0 : #if FD_HAS_ZSTD
848 0 : uint ip4 = peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].is_ipv6 ? 0 : peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].ip4;
849 0 : fd_gui_geoip_node_t const * dbip_ip = geoip_lookup( &peers->dbip, ip4 );
850 :
851 0 : peer->row.country_code_idx = dbip_ip ? dbip_ip->country_code_idx : UCHAR_MAX;
852 0 : peer->row.city_name_idx = dbip_ip ? dbip_ip->city_name_idx : UINT_MAX;
853 : #else
854 : peer->row.country_code_idx = UCHAR_MAX;
855 : peer->row.city_name_idx = UINT_MAX;
856 : #endif
857 :
858 0 : peer->row.valid = 1;
859 :
860 : /* update pubkey_map, sock_map */
861 0 : fd_gui_peers_node_sock_map_idx_insert ( peers->node_sock_map, update->contact_info->idx, peers->contact_info_table );
862 0 : fd_gui_peers_node_pubkey_map_idx_insert( peers->node_pubkey_map, update->contact_info->idx, peers->contact_info_table );
863 :
864 : /* update live tables */
865 0 : fd_gui_peers_live_table_idx_insert ( peers->live_table, update->contact_info->idx, peers->contact_info_table );
866 0 : fd_gui_peers_bandwidth_tracking_idx_insert( peers->bw_tracking, update->contact_info->idx, peers->contact_info_table );
867 :
868 0 : fd_gui_printf_peers_view_resize( peers, fd_gui_peers_live_table_ele_cnt( peers->live_table ) );
869 0 : fd_http_server_ws_broadcast( peers->http );
870 :
871 : /* broadcast update to WebSocket clients */
872 0 : fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_ADD }, (ulong[]){ update->contact_info->idx }, 1UL );
873 0 : fd_http_server_ws_broadcast( peers->http );
874 :
875 0 : wfs_handle_contact_info_update( peers, (fd_pubkey_t const *)update->origin, now );
876 0 : }
877 0 : break;
878 0 : }
879 0 : case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
880 0 : if( FD_UNLIKELY( update->contact_info_remove->idx>=FD_CONTACT_INFO_TABLE_SIZE ) ) FD_LOG_ERR(( "unexpected remove_contact_info_idx %lu >= %lu", update->contact_info_remove->idx, FD_CONTACT_INFO_TABLE_SIZE ));
881 : #if LOGGING
882 : char _pk[ FD_BASE58_ENCODED_32_SZ ];
883 : fd_base58_encode_32( update->origin, NULL, _pk );
884 : FD_LOG_WARNING(( "REMOVE %lu pk=%s",update->contact_info_remove->idx, _pk ));
885 : #endif
886 :
887 0 : fd_gui_peers_node_t * peer = &peers->contact_info_table[ update->contact_info_remove->idx ];
888 :
889 : #ifdef FD_GUI_USE_HANDHOLDING
890 : /* invariant checks */
891 : FD_TEST( peer->row.valid ); /* Should have already been in the table */
892 : FD_TEST( peer==fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, (fd_pubkey_t const * )update->origin, NULL, peers->contact_info_table ) );
893 : fd_gui_peers_node_t * peer_sock = fd_gui_peers_node_sock_map_ele_query( peers->node_sock_map, &peer->row.contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ], NULL, peers->contact_info_table );
894 : int found = 0;
895 : for( fd_gui_peers_node_t const * p = peer_sock; !!p; p=(fd_gui_peers_node_t const *)fd_gui_peers_node_sock_map_ele_next_const( p, NULL, peers->contact_info_table ) ) {
896 : if( peer==p ) {
897 : found = 1;
898 : break;
899 : }
900 : }
901 : FD_TEST( found );
902 : #endif
903 0 : wfs_handle_contact_info_remove( peers, (fd_pubkey_t const *)update->origin );
904 :
905 0 : fd_gui_peers_live_table_idx_remove ( peers->live_table, update->contact_info_remove->idx, peers->contact_info_table );
906 0 : fd_gui_peers_bandwidth_tracking_idx_remove ( peers->bw_tracking, update->contact_info_remove->idx, peers->contact_info_table );
907 0 : fd_gui_peers_node_sock_map_idx_remove_fast ( peers->node_sock_map, update->contact_info_remove->idx, peers->contact_info_table );
908 0 : fd_gui_peers_node_pubkey_map_idx_remove_fast( peers->node_pubkey_map, update->contact_info_remove->idx, peers->contact_info_table );
909 0 : peer->row.valid = 0;
910 :
911 0 : fd_gui_printf_peers_view_resize( peers, fd_gui_peers_live_table_ele_cnt( peers->live_table ) );
912 0 : fd_http_server_ws_broadcast( peers->http );
913 :
914 : /* broadcast update to WebSocket clients */
915 0 : fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_DELETE }, (ulong[]){ update->contact_info_remove->idx }, 1UL );
916 0 : fd_http_server_ws_broadcast( peers->http );
917 0 : break;
918 0 : }
919 0 : default: break;
920 0 : }
921 0 : }
922 :
923 : void
924 : fd_gui_peers_handle_vote( fd_gui_peers_ctx_t * peers,
925 : fd_pubkey_t const * vote_account,
926 : ulong vote_slot,
927 0 : int is_us ) {
928 0 : if( FD_UNLIKELY( is_us && peers->slot_voted!=vote_slot ) ) {
929 0 : peers->slot_voted = fd_ulong_if( vote_slot==0UL, ULONG_MAX, vote_slot );
930 0 : fd_gui_peers_printf_vote_slot( peers );
931 0 : fd_http_server_ws_broadcast( peers->http );
932 0 : }
933 :
934 0 : for( ulong i=0UL; i<2UL; i++ ) {
935 0 : fd_gui_peers_voter_idx_t * vidx = peers->epochs[ i ].vote_idx;
936 0 : ulong cnt = peers->epochs[ i ].stakes_cnt;
937 0 : ulong pos = fd_gui_peers_voter_idx_sort_vote_desc_split( vidx, cnt, (fd_gui_peers_voter_idx_t){ .key = *vote_account } );
938 0 : if( FD_UNLIKELY( pos>=cnt || memcmp( vidx[ pos ].key.uc, vote_account->uc, sizeof(fd_pubkey_t) ) ) ) continue;
939 :
940 0 : fd_gui_peers_voter_t * voter = &peers->epochs[ i ].stakes[ vidx[ pos ].idx ];
941 0 : voter->vote_slot = fd_ulong_if( voter->vote_slot==ULONG_MAX, vote_slot, fd_ulong_max( voter->vote_slot, vote_slot ) );
942 0 : }
943 0 : }
944 :
945 : void
946 : fd_gui_peers_handle_epoch_info( fd_gui_peers_ctx_t * peers,
947 : fd_epoch_info_msg_t const * epoch_info,
948 0 : long now FD_PARAM_UNUSED ) {
949 0 : ulong epoch_idx = epoch_info->epoch % 2UL;
950 0 : if( FD_UNLIKELY( peers->epochs[ epoch_idx ].epoch!=ULONG_MAX && peers->epochs[ epoch_idx ].epoch>=epoch_info->epoch ) ) return;
951 :
952 0 : if( FD_UNLIKELY( epoch_info->staked_vote_cnt>MAX_COMPRESSED_STAKE_WEIGHTS ) )
953 0 : FD_LOG_ERR(( "epoch stakes exceed MAX_COMPRESSED_STAKE_WEIGHTS=%lu", MAX_COMPRESSED_STAKE_WEIGHTS ));
954 0 : if( FD_UNLIKELY( epoch_info->staked_id_cnt>MAX_SHRED_DESTS ) )
955 0 : FD_LOG_ERR(( "epoch id weights exceed MAX_SHRED_DESTS=%lu", MAX_SHRED_DESTS ));
956 :
957 0 : fd_vote_stake_weight_t const * weights = fd_epoch_info_msg_stake_weights( epoch_info );
958 :
959 0 : ulong stakes_cnt = 0UL;
960 0 : for( ulong i=0UL; i<epoch_info->staked_vote_cnt; i++ ) {
961 0 : if( FD_UNLIKELY( fd_pubkey_check_zero( &weights[ i ].id_key ) ) ) continue;
962 0 : peers->epochs[ epoch_idx ].stakes[ stakes_cnt ] = (fd_gui_peers_voter_t){
963 0 : .weight = weights[ i ],
964 0 : .vote_slot = ULONG_MAX,
965 0 : };
966 0 : stakes_cnt++;
967 0 : }
968 0 : peers->epochs[ epoch_idx ].epoch = epoch_info->epoch;
969 0 : peers->epochs[ epoch_idx ].stakes_cnt = stakes_cnt;
970 :
971 : /* sort for deduplication */
972 0 : fd_gui_peers_voter_sort_iden_desc_inplace( peers->epochs[ epoch_idx ].stakes, peers->epochs[ epoch_idx ].stakes_cnt );
973 :
974 0 : ulong updated_cnt = 0UL;
975 0 : ulong i=0UL;
976 0 : while( i<peers->epochs[ epoch_idx ].stakes_cnt ) {
977 0 : fd_gui_peers_voter_t const * best = fd_gui_peers_voter_best_for_identity( peers->epochs[ epoch_idx ].stakes, peers->epochs[ epoch_idx ].stakes_cnt, &i );
978 :
979 0 : ulong peer_idx = fd_gui_peers_node_pubkey_map_idx_query(
980 0 : peers->node_pubkey_map, &best->weight.id_key, ULONG_MAX,
981 0 : peers->contact_info_table );
982 0 : if( FD_UNLIKELY( peer_idx==ULONG_MAX ) ) continue;
983 :
984 0 : fd_gui_peers_node_t * peer = &peers->contact_info_table[ peer_idx ];
985 :
986 0 : int vote_eq = peer->row.has_vote_info
987 0 : && !memcmp( peer->row.vote_account.uc, best->weight.vote_key.uc, sizeof(fd_pubkey_t) )
988 0 : && peer->row.stake==best->weight.stake;
989 0 : if( FD_LIKELY( vote_eq ) ) continue;
990 :
991 0 : fd_gui_peers_live_table_idx_remove( peers->live_table, peer_idx, peers->contact_info_table );
992 :
993 0 : peer->row.has_vote_info = 1;
994 0 : peer->row.vote_account = best->weight.vote_key;
995 0 : peer->row.stake = best->weight.stake;
996 :
997 0 : fd_gui_peers_live_table_idx_insert( peers->live_table, peer_idx, peers->contact_info_table );
998 :
999 0 : peers->scratch.actions[ updated_cnt ] = FD_GUI_PEERS_NODE_UPDATE;
1000 0 : peers->scratch.idxs [ updated_cnt ] = peer_idx;
1001 0 : updated_cnt++;
1002 0 : }
1003 :
1004 0 : if( FD_UNLIKELY( updated_cnt ) ) {
1005 0 : fd_gui_peers_printf_nodes( peers, peers->scratch.actions, peers->scratch.idxs, updated_cnt );
1006 0 : fd_http_server_ws_broadcast( peers->http );
1007 0 : }
1008 :
1009 : /* Build vote account index for fd_gui_peers_handle_vote */
1010 0 : for( ulong j=0UL; j<stakes_cnt; j++ ) {
1011 0 : peers->epochs[ epoch_idx ].vote_idx[ j ] = (fd_gui_peers_voter_idx_t){
1012 0 : .key = peers->epochs[ epoch_idx ].stakes[ j ].weight.vote_key,
1013 0 : .idx = j,
1014 0 : };
1015 0 : }
1016 0 : fd_gui_peers_voter_idx_sort_vote_desc_inplace( peers->epochs[ epoch_idx ].vote_idx, stakes_cnt );
1017 0 : }
1018 :
1019 : #define SORT_NAME fd_gui_peers_voter_sort_slot_stake_desc
1020 0 : #define SORT_KEY_T fd_gui_peers_voter_t
1021 0 : #define SORT_BEFORE(a,b) ((a).vote_slot>(b).vote_slot ? 1 : (a).vote_slot<(b).vote_slot ? 0 : (a).weight.stake>(b).weight.stake)
1022 : #include "../../util/tmpl/fd_sort.c"
1023 :
1024 : void
1025 : fd_gui_peers_update_delinquency( fd_gui_peers_ctx_t * peers,
1026 0 : long now FD_PARAM_UNUSED ) {
1027 0 : ulong epoch_t_1 = ULONG_MAX;
1028 0 : for( ulong i=0UL; i<2UL; i++ ) {
1029 0 : ulong epoch = peers->epochs[ i ].epoch;
1030 0 : if( FD_UNLIKELY( epoch==ULONG_MAX ) ) continue;
1031 0 : if( FD_LIKELY( epoch_t_1==ULONG_MAX || epoch>epoch_t_1 ) ) epoch_t_1 = epoch;
1032 0 : }
1033 0 : ulong epoch_idx = epoch_t_1 % 2UL;
1034 :
1035 0 : fd_gui_peers_voter_t * voters = peers->epochs[ epoch_idx ].stakes;
1036 0 : ulong voters_cnt = peers->epochs[ epoch_idx ].stakes_cnt;
1037 :
1038 : /* Copy to scratch and sort for p67 computation. We use a scratch
1039 : copy to avoid clobbering the identity sort order of stakes. */
1040 0 : fd_gui_peers_voter_t * scratch = peers->scratch.voters_scratch;
1041 0 : fd_memcpy( scratch, voters, voters_cnt * sizeof(fd_gui_peers_voter_t) );
1042 0 : fd_gui_peers_voter_sort_slot_stake_desc_inplace( scratch, voters_cnt );
1043 :
1044 0 : ulong total_stake = 0UL;
1045 0 : for( ulong i=0UL; i<voters_cnt; i++ ) total_stake += scratch[ i ].weight.stake;
1046 :
1047 0 : ulong cumulative_stake = 0UL;
1048 0 : ulong last_vote_slot_p33 = ULONG_MAX;
1049 0 : for( ulong i=0UL; i<voters_cnt; i++ ) {
1050 0 : if( FD_UNLIKELY( scratch[ i ].vote_slot==ULONG_MAX ) ) continue;
1051 0 : cumulative_stake += scratch[ i ].weight.stake;
1052 0 : if( FD_LIKELY( 3UL*cumulative_stake > 1UL*total_stake ) ) {
1053 0 : last_vote_slot_p33 = scratch[ i ].vote_slot;
1054 0 : break;
1055 0 : }
1056 0 : }
1057 0 : if( FD_UNLIKELY( last_vote_slot_p33==ULONG_MAX ) ) {
1058 0 : return; /* not enough observed votes */
1059 0 : }
1060 :
1061 0 : ulong updated_cnt = 0UL;
1062 0 : for( ulong i=0UL; i<voters_cnt; i++ ) {
1063 0 : ulong peer_idx = fd_gui_peers_node_pubkey_map_idx_query(
1064 0 : peers->node_pubkey_map, &voters[ i ].weight.id_key, ULONG_MAX,
1065 0 : peers->contact_info_table );
1066 0 : if( FD_UNLIKELY( peer_idx==ULONG_MAX ) ) continue;
1067 :
1068 0 : fd_gui_peers_node_t * peer = &peers->contact_info_table[ peer_idx ];
1069 :
1070 : /* Only update peers whose vote_account was already set by
1071 : handle_epoch_info and matches this voter */
1072 0 : if( FD_UNLIKELY( !peer->row.has_vote_info ) ) continue;
1073 0 : if( FD_UNLIKELY( memcmp( peer->row.vote_account.uc, voters[ i ].weight.vote_key.uc, sizeof(fd_pubkey_t) ) ) ) continue;
1074 :
1075 0 : int is_delinquent = fd_int_if( voters[ i ].vote_slot==ULONG_MAX, 1, ((long)last_vote_slot_p33 - (long)voters[ i ].vote_slot) > 150L );
1076 0 : if( FD_LIKELY( peer->row.delinquent==is_delinquent ) ) continue;
1077 :
1078 0 : peer->row.delinquent = is_delinquent;
1079 :
1080 0 : peers->scratch.actions[ updated_cnt ] = FD_GUI_PEERS_NODE_UPDATE;
1081 0 : peers->scratch.idxs [ updated_cnt ] = peer_idx;
1082 0 : updated_cnt++;
1083 0 : }
1084 :
1085 0 : if( FD_UNLIKELY( updated_cnt ) ) {
1086 0 : fd_gui_peers_printf_nodes( peers, peers->scratch.actions, peers->scratch.idxs, updated_cnt );
1087 0 : fd_http_server_ws_broadcast( peers->http );
1088 0 : }
1089 0 : }
1090 :
1091 : void
1092 : fd_gui_peers_handle_config_account( fd_gui_peers_ctx_t * peers,
1093 : uchar const * data,
1094 0 : ulong sz ) {
1095 : /* optimistically acquire node_info */
1096 0 : if( FD_UNLIKELY( !fd_gui_peers_node_info_pool_free( peers->node_info_pool ) ) ) {
1097 0 : FD_LOG_WARNING(( "On-chain ConfigProgram accounts count exceeded %lu", FD_CONTACT_INFO_TABLE_SIZE ));
1098 0 : return;
1099 0 : }
1100 0 : fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_pool_ele_acquire( peers->node_info_pool );
1101 :
1102 0 : cJSON * json;
1103 0 : if( FD_UNLIKELY( !fd_gui_config_parse_validator_info_check( data, sz, &json, &node_info->pubkey ) ) ) {
1104 0 : fd_gui_peers_node_info_pool_ele_release( peers->node_info_pool, node_info );
1105 0 : return;
1106 0 : }
1107 :
1108 0 : if( FD_UNLIKELY( fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &node_info->pubkey, NULL, peers->node_info_pool ) ) ) {
1109 0 : fd_gui_peers_node_info_pool_ele_release( peers->node_info_pool, node_info );
1110 0 : cJSON_Delete( json );
1111 0 : return; /* no duplicate entries */
1112 0 : }
1113 :
1114 0 : fd_gui_config_parse_validator_info( json, node_info ); /* calls cJSON_delete( json ) */
1115 :
1116 : /* Some nodes just clear all the fields instead of deleting their
1117 : on-chain account, we can ignore those entries */
1118 0 : if( FD_UNLIKELY( node_info->name[ 0 ]=='\0' && node_info->details[ 0 ]=='\0' && node_info->website[ 0 ]=='\0' && node_info->icon_uri[ 0 ]=='\0' && node_info->keybase_username[ 0 ]=='\0' ) ) {
1119 0 : fd_gui_peers_node_info_pool_ele_release( peers->node_info_pool, node_info );
1120 0 : return;
1121 0 : }
1122 :
1123 0 : fd_gui_peers_node_info_map_ele_insert( peers->node_info_map, node_info, peers->node_info_pool );
1124 :
1125 0 : fd_gui_peers_node_t * peer = fd_gui_peers_node_pubkey_map_ele_query( peers->node_pubkey_map, &node_info->pubkey, NULL, peers->contact_info_table );
1126 0 : if( FD_UNLIKELY( peer ) ) {
1127 0 : fd_gui_peers_live_table_ele_remove( peers->live_table, peer, peers->contact_info_table );
1128 0 : fd_cstr_ncpy( peer->row.name, node_info->name, sizeof(peer->row.name) );
1129 0 : fd_gui_peers_live_table_ele_insert( peers->live_table, peer, peers->contact_info_table );
1130 0 : }
1131 0 : }
1132 :
1133 : void
1134 : fd_gui_peers_stage_snapshot_manifest( fd_gui_peers_ctx_t * peers,
1135 : fd_snapshot_manifest_t const * manifest,
1136 0 : long now ) {
1137 :
1138 0 : if( FD_LIKELY( !peers->wfs_enabled ) ) return;
1139 :
1140 0 : fd_vote_stake_weight_t * vote_scratch = peers->scratch.manifest_vote_weights;
1141 0 : ulong vote_scratch_cnt = 0UL;
1142 0 : ulong vote_accounts_sz = manifest->vote_accounts_len;
1143 0 : if( FD_UNLIKELY( vote_accounts_sz>FD_VOTE_ACCOUNTS_MAX ) ) {
1144 0 : FD_LOG_WARNING(( "vote accounts %lu exceeds maximum %lu", vote_accounts_sz, FD_VOTE_ACCOUNTS_MAX ));
1145 0 : vote_accounts_sz = FD_VOTE_ACCOUNTS_MAX;
1146 0 : }
1147 0 : for( ulong i=0UL; i<vote_accounts_sz; i++ ) {
1148 0 : if( FD_UNLIKELY( manifest->vote_accounts[ i ].stake==0UL ) ) continue;
1149 0 : fd_memcpy( vote_scratch[ vote_scratch_cnt ].id_key.uc, manifest->vote_accounts[ i ].node_account_pubkey, sizeof(fd_pubkey_t) );
1150 0 : fd_memcpy( vote_scratch[ vote_scratch_cnt ].vote_key.uc, manifest->vote_accounts[ i ].vote_account_pubkey, sizeof(fd_pubkey_t) );
1151 0 : vote_scratch[ vote_scratch_cnt ].stake = manifest->vote_accounts[ i ].stake;
1152 0 : vote_scratch_cnt++;
1153 0 : }
1154 :
1155 : /* Mirrors gossip WFS logic */
1156 0 : fd_stake_weight_t * id_weights = peers->scratch.manifest_id_weights;
1157 0 : ulong id_cnt = compute_id_weights_from_vote_weights( id_weights, vote_scratch, vote_scratch_cnt );
1158 :
1159 : /* Restore invariant: sorted by identity key */
1160 0 : fd_stake_weight_key_sort_inplace( id_weights, id_cnt );
1161 :
1162 0 : for( ulong i=0UL; i<id_cnt; i++ ) {
1163 0 : peers->wfs_peers[ i ].identity_key = id_weights[ i ].key;
1164 0 : peers->wfs_peers[ i ].stake = id_weights[ i ].stake;
1165 0 : peers->wfs_peers[ i ].fresh_prev = ULONG_MAX;
1166 0 : peers->wfs_peers[ i ].fresh_next = ULONG_MAX;
1167 :
1168 0 : ulong peer_idx = fd_gui_peers_node_pubkey_map_idx_query( peers->node_pubkey_map, &id_weights[ i ].key, ULONG_MAX,peers->contact_info_table );
1169 0 : if( peer_idx!=ULONG_MAX && peers->contact_info_table[ peer_idx ].row.update_time_nanos > now - FD_GUI_WFS_ACTIVITY_TIMEOUT_NANOS ) {
1170 0 : peers->wfs_peers[ i ].is_online = 1;
1171 0 : peers->wfs_peers[ i ].update_time_nanos = peers->contact_info_table[ peer_idx ].row.update_time_nanos;
1172 0 : } else {
1173 0 : peers->wfs_peers[ i ].is_online = 0;
1174 0 : peers->wfs_peers[ i ].update_time_nanos = 0L;
1175 0 : }
1176 0 : }
1177 0 : peers->wfs_peers_cnt = id_cnt;
1178 0 : }
1179 :
1180 : void
1181 0 : fd_gui_peers_commit_snapshot_manifest( fd_gui_peers_ctx_t * peers ) {
1182 0 : if( FD_UNLIKELY( !peers->wfs_enabled ) ) return;
1183 :
1184 0 : wfs_fresh_dlist_join( wfs_fresh_dlist_new( peers->wfs_fresh_dlist ) );
1185 :
1186 : /* Emit the wait_for_supermajority.stakes message with stakes and
1187 : infos. By this point all config accounts have been processed so
1188 : node_info_map is populated. */
1189 0 : fd_gui_peers_printf_wfs_stakes( peers );
1190 0 : fd_http_server_ws_broadcast( peers->http );
1191 0 : peers->wfs_stakes_sent = 1;
1192 :
1193 0 : ulong added_cnt = 0UL;
1194 0 : for( ulong i=0UL; i<peers->wfs_peers_cnt; i++ ) {
1195 : /* Peers are technically added here not ordered by timestamp, but it's
1196 : not an issue since a) all timestamps should be similar b) the dlist
1197 : will eventually be correct as subsequent updates come in. */
1198 0 : if( FD_UNLIKELY( peers->wfs_peers[ i ].is_online ) ) {
1199 0 : peers->scratch.wfs_peers[ added_cnt++ ] = i;
1200 0 : wfs_fresh_dlist_idx_push_tail( peers->wfs_fresh_dlist, i, peers->wfs_peers );
1201 0 : }
1202 0 : }
1203 0 : if( FD_LIKELY( added_cnt ) ) {
1204 0 : fd_gui_peers_printf_wfs_add( peers, peers->scratch.wfs_peers, added_cnt );
1205 0 : fd_http_server_ws_broadcast( peers->http );
1206 0 : }
1207 0 : peers->wfs_peers_valid = 1;
1208 0 : }
1209 :
1210 : static void
1211 0 : fd_gui_peers_viewport_snap( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
1212 0 : FD_TEST( peers->client_viewports[ ws_conn_id ].connected );
1213 0 : peers->scratch.viewport_cnt = 0UL;
1214 0 : if( FD_UNLIKELY( peers->client_viewports[ ws_conn_id ].row_cnt==0UL ) ) return; /* empty viewport */
1215 0 : if( FD_UNLIKELY( peers->client_viewports[ ws_conn_id ].row_cnt>FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ ) ) FD_LOG_ERR(("row_cnt=%lu", peers->client_viewports[ ws_conn_id ].row_cnt ));
1216 :
1217 0 : for( fd_gui_peers_live_table_fwd_iter_t iter = fd_gui_peers_live_table_fwd_iter_init( peers->live_table, &peers->client_viewports[ ws_conn_id ].sort_key, peers->contact_info_table ), j = 0;
1218 0 : !fd_gui_peers_live_table_fwd_iter_done( iter ) && j<peers->client_viewports[ ws_conn_id ].start_row+peers->client_viewports[ ws_conn_id ].row_cnt;
1219 0 : iter = fd_gui_peers_live_table_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
1220 0 : if( FD_LIKELY( j<peers->client_viewports[ ws_conn_id ].start_row ) ) continue;
1221 0 : fd_gui_peers_node_t const * cur = fd_gui_peers_live_table_fwd_iter_ele_const( iter, peers->contact_info_table );
1222 :
1223 0 : ulong viewport_idx = j-peers->client_viewports[ ws_conn_id ].start_row;
1224 0 : FD_TEST( viewport_idx<FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ );
1225 0 : peers->scratch.viewport[ viewport_idx ] = cur->row;
1226 0 : peers->scratch.viewport_cnt = viewport_idx+1UL;
1227 0 : }
1228 :
1229 : /* Capture the old baseline as the diff reference, then commit the new
1230 : rows as the new baseline. Both happen here, before any formatting,
1231 : so fd_gui_printf_peers_viewport_update reads only from scratch. */
1232 0 : fd_memcpy( peers->scratch.viewport_ref, peers->client_viewports[ ws_conn_id ].viewport, peers->scratch.viewport_cnt*sizeof(fd_gui_peers_row_t) );
1233 0 : fd_memcpy( peers->client_viewports[ ws_conn_id ].viewport, peers->scratch.viewport, peers->scratch.viewport_cnt*sizeof(fd_gui_peers_row_t) );
1234 0 : }
1235 :
1236 : static int
1237 : fd_gui_peers_request_scroll( fd_gui_peers_ctx_t * peers,
1238 : ulong ws_conn_id,
1239 : ulong request_id,
1240 0 : cJSON const * params ) {
1241 0 : if( FD_UNLIKELY( !peers->client_viewports[ ws_conn_id ].connected ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1242 :
1243 0 : const cJSON * start_row_param = cJSON_GetObjectItemCaseSensitive( params, "start_row" );
1244 0 : if( FD_UNLIKELY( !cJSON_IsNumber( start_row_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1245 0 : ulong _start_row = start_row_param->valueulong;
1246 :
1247 0 : const cJSON * row_cnt_param = cJSON_GetObjectItemCaseSensitive( params, "row_cnt" );
1248 0 : if( FD_UNLIKELY( !cJSON_IsNumber( row_cnt_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1249 0 : ulong _row_cnt = row_cnt_param->valueulong;
1250 :
1251 0 : if( FD_UNLIKELY( _row_cnt > FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ || _start_row > fd_gui_peers_live_table_ele_cnt( peers->live_table )-_row_cnt ) ) {
1252 0 : fd_gui_printf_null_query_response( peers->http, "gossip", "query_scroll", request_id );
1253 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1254 0 : return 0;
1255 0 : }
1256 :
1257 0 : if( FD_UNLIKELY( (peers->client_viewports[ ws_conn_id ].start_row==_start_row || _row_cnt==0UL) && peers->client_viewports[ ws_conn_id ].row_cnt==_row_cnt ) ) {
1258 0 : return 0; /* NOP, scroll window hasn't changed */
1259 0 : }
1260 :
1261 : /* update the client's viewport */
1262 0 : peers->client_viewports[ ws_conn_id ].start_row = _start_row;
1263 0 : peers->client_viewports[ ws_conn_id ].row_cnt = _row_cnt;
1264 :
1265 0 : fd_gui_peers_viewport_snap( peers, ws_conn_id );
1266 0 : fd_gui_printf_peers_viewport_request( peers, "query_scroll", ws_conn_id, request_id );
1267 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1268 0 : return 0;
1269 0 : }
1270 :
1271 : static int
1272 : fd_gui_peers_request_sort( fd_gui_peers_ctx_t * peers,
1273 : ulong ws_conn_id,
1274 : ulong request_id,
1275 0 : cJSON const * params ) {
1276 0 : if( FD_UNLIKELY( !peers->client_viewports[ ws_conn_id ].connected ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1277 :
1278 0 : const cJSON * _col = cJSON_GetObjectItemCaseSensitive( params, "col" );
1279 0 : if( FD_UNLIKELY( !cJSON_IsArray( _col ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1280 :
1281 0 : fd_gui_peers_live_table_sort_key_t sort_key = {0};
1282 :
1283 0 : do {
1284 0 : cJSON * c;
1285 0 : ulong i;
1286 0 : for( c = _col->child, i=0UL; c; c = c->next, i++ ) {
1287 0 : if( FD_UNLIKELY( !cJSON_IsString( c ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1288 0 : if( FD_UNLIKELY( i >= fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1289 0 : sort_key.col[ i ] = fd_gui_peers_live_table_col_name_to_idx( peers->live_table, c->valuestring );
1290 0 : if( FD_UNLIKELY( sort_key.col[ i ]==ULONG_MAX ) ) {
1291 0 : FD_LOG_WARNING(( "unexpected column name %s", c->valuestring ));
1292 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1293 0 : }
1294 0 : }
1295 0 : if( FD_UNLIKELY( i!=fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1296 0 : } while( 0 );
1297 :
1298 0 : const cJSON * _dir = cJSON_GetObjectItemCaseSensitive( params, "dir" );
1299 0 : if( FD_UNLIKELY( !cJSON_IsArray( _dir ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1300 :
1301 0 : do {
1302 0 : cJSON * c;
1303 0 : ulong i;
1304 0 : for( c = _dir->child, i=0UL; c; c = c->next, i++ ) {
1305 0 : if( FD_UNLIKELY( i >= fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1306 0 : if( FD_UNLIKELY( !cJSON_IsNumber( c ) || c->valuedouble!=(double)c->valueint || c->valueint<-1 || c->valueint>1 ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1307 0 : sort_key.dir[ i ] = c->valueint;
1308 0 : }
1309 0 : if( FD_UNLIKELY( i!=fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1310 0 : } while( 0 );
1311 :
1312 0 : if( FD_UNLIKELY( !fd_gui_peers_live_table_verify_sort_key( &sort_key ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1313 :
1314 0 : fd_gui_peers_live_table_sort_key_remove( peers->live_table, &peers->client_viewports[ ws_conn_id ].sort_key );
1315 0 : peers->client_viewports[ ws_conn_id ].sort_key = sort_key;
1316 :
1317 0 : fd_gui_peers_viewport_snap( peers, ws_conn_id );
1318 0 : fd_gui_printf_peers_viewport_request( peers, "query_sort", ws_conn_id, request_id );
1319 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1320 0 : return 0;
1321 0 : }
1322 :
1323 : int
1324 : fd_gui_peers_ws_message( fd_gui_peers_ctx_t * peers,
1325 : ulong ws_conn_id,
1326 : uchar const * data,
1327 0 : ulong data_len ) {
1328 : /* TODO: cJSON allocates, might fail SIGSYS due to brk(2)...
1329 : switch off this (or use wksp allocator) */
1330 0 : const char * parse_end;
1331 0 : cJSON * json = cJSON_ParseWithLengthOpts( (char *)data, data_len, &parse_end, 0 );
1332 0 : if( FD_UNLIKELY( !json ) ) {
1333 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1334 0 : }
1335 :
1336 0 : const cJSON * node = cJSON_GetObjectItemCaseSensitive( json, "id" );
1337 0 : if( FD_UNLIKELY( !cJSON_IsNumber( node ) ) ) {
1338 0 : cJSON_Delete( json );
1339 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1340 0 : }
1341 0 : ulong id = node->valueulong;
1342 :
1343 0 : const cJSON * topic = cJSON_GetObjectItemCaseSensitive( json, "topic" );
1344 0 : if( FD_UNLIKELY( !cJSON_IsString( topic ) || topic->valuestring==NULL ) ) {
1345 0 : cJSON_Delete( json );
1346 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1347 0 : }
1348 :
1349 0 : const cJSON * key = cJSON_GetObjectItemCaseSensitive( json, "key" );
1350 0 : if( FD_UNLIKELY( !cJSON_IsString( key ) || key->valuestring==NULL ) ) {
1351 0 : cJSON_Delete( json );
1352 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1353 0 : }
1354 :
1355 0 : if( FD_LIKELY( !strcmp( topic->valuestring, "gossip" ) && !strcmp( key->valuestring, "query_sort" ) ) ) {
1356 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1357 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1358 0 : cJSON_Delete( json );
1359 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1360 0 : }
1361 :
1362 0 : int result = fd_gui_peers_request_sort( peers, ws_conn_id, id, params );
1363 0 : cJSON_Delete( json );
1364 0 : return result;
1365 0 : } else if( FD_LIKELY( !strcmp( topic->valuestring, "gossip" ) && !strcmp( key->valuestring, "query_scroll" ) ) ) {
1366 0 : const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
1367 0 : if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
1368 0 : cJSON_Delete( json );
1369 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
1370 0 : }
1371 :
1372 0 : int result = fd_gui_peers_request_scroll( peers, ws_conn_id, id, params );
1373 0 : cJSON_Delete( json );
1374 0 : return result;
1375 0 : }
1376 :
1377 0 : cJSON_Delete( json );
1378 0 : return FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD;
1379 0 : }
1380 :
1381 : static void
1382 : fd_gui_peers_viewport_log( fd_gui_peers_ctx_t * peers,
1383 0 : ulong ws_conn_id) {
1384 0 : FD_TEST( peers->scratch.viewport_cnt<=FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ );
1385 :
1386 0 : char out[ 1<<14 ];
1387 0 : char * p = fd_cstr_init( out );
1388 :
1389 0 : p = fd_cstr_append_printf( p,
1390 0 : "\n[Viewport] table_size=%lu max_viewport_size=%lu\n"
1391 0 : "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+\n"
1392 0 : "| Row # | RX Push (bps) | RX Pull (bps) | TX Push (bps) | TX Pull (bps) | Pubkey | IP Address |\n"
1393 0 : "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+\n",
1394 0 : fd_gui_peers_live_table_ele_cnt( peers->live_table ), peers->scratch.viewport_cnt );
1395 :
1396 0 : ulong start_row = peers->client_viewports[ ws_conn_id ].start_row;
1397 0 : for( ulong i=0UL; i<peers->scratch.viewport_cnt; i++ ) {
1398 0 : ulong j = start_row + i;
1399 0 : fd_gui_peers_row_t const * cur = &peers->scratch.viewport[ i ];
1400 :
1401 0 : char pubkey_base58[ FD_BASE58_ENCODED_32_SZ ];
1402 0 : fd_base58_encode_32( cur->pubkey.uc, NULL, pubkey_base58 );
1403 :
1404 0 : char peer_addr[ 16 ]; /* 255.255.255.255 + '\0' */
1405 0 : uint ip4 = cur->contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].is_ipv6 ? 0 : cur->contact_info.sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].ip4;
1406 0 : FD_TEST(fd_cstr_printf_check( peer_addr, sizeof(peer_addr), NULL, FD_IP4_ADDR_FMT,
1407 0 : FD_IP4_ADDR_FMT_ARGS( ip4 ) ) );
1408 :
1409 0 : long cur_egress_push_bps = cur->gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate_ema;
1410 0 : long cur_ingress_push_bps = cur->gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate_ema;
1411 0 : long cur_egress_pull_response_bps = cur->gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate_ema;
1412 0 : long cur_ingress_pull_response_bps = cur->gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate_ema;
1413 :
1414 0 : p = fd_cstr_append_printf( p,
1415 0 : "| %5lu | %14ld | %14ld | %14ld | %14ld | %-50s | %-15s |\n",
1416 0 : j,
1417 0 : cur_ingress_push_bps,
1418 0 : cur_ingress_pull_response_bps,
1419 0 : cur_egress_push_bps,
1420 0 : cur_egress_pull_response_bps,
1421 0 : pubkey_base58,
1422 0 : peer_addr );
1423 0 : }
1424 0 : p = fd_cstr_append_printf(p, "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+" );
1425 0 : fd_cstr_fini( p );
1426 0 : FD_LOG_NOTICE(( "%s", out ));
1427 0 : }
1428 :
1429 : static void
1430 0 : fd_gui_peers_ws_conn_rr_grow( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
1431 0 : if( FD_UNLIKELY( !peers->open_ws_conn_cnt ) ) peers->active_ws_conn_id = ws_conn_id;
1432 0 : peers->open_ws_conn_cnt++;
1433 0 : }
1434 :
1435 : static void
1436 0 : fd_gui_peers_ws_conn_rr_shrink( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
1437 0 : peers->open_ws_conn_cnt--;
1438 :
1439 0 : if( FD_UNLIKELY( peers->open_ws_conn_cnt && peers->active_ws_conn_id==ws_conn_id ) ) {
1440 0 : for( ulong i=1UL; i<peers->max_ws_conn_cnt+1UL; i++ ) {
1441 0 : ulong next_ws_conn_id = (ws_conn_id + i) % peers->max_ws_conn_cnt;
1442 0 : if( FD_UNLIKELY( peers->client_viewports[ next_ws_conn_id ].connected ) ) {
1443 0 : peers->active_ws_conn_id = next_ws_conn_id;
1444 0 : break;
1445 0 : }
1446 0 : }
1447 0 : }
1448 0 : }
1449 :
1450 : static int
1451 0 : fd_gui_peers_ws_conn_rr_advance( fd_gui_peers_ctx_t * peers, long now ) {
1452 0 : if( FD_LIKELY( !peers->open_ws_conn_cnt || now <= peers->next_client_nanos ) ) return 0;
1453 :
1454 0 : for( ulong i=1UL; i<peers->max_ws_conn_cnt+1UL; i++ ) {
1455 0 : ulong next_ws_conn_id = (peers->active_ws_conn_id + i) % peers->max_ws_conn_cnt;
1456 0 : if( FD_UNLIKELY( peers->client_viewports[ next_ws_conn_id ].connected ) ) {
1457 0 : peers->active_ws_conn_id = next_ws_conn_id;
1458 0 : break;
1459 0 : }
1460 0 : }
1461 0 : return 1;
1462 0 : }
1463 :
1464 : int
1465 0 : fd_gui_peers_poll( fd_gui_peers_ctx_t * peers, long now ) {
1466 0 : int did_work = 0;
1467 :
1468 0 : ulong evicted_cnt = 0UL;
1469 0 : while( FD_UNLIKELY( peers->wfs_peers_valid && !wfs_fresh_dlist_is_empty( peers->wfs_fresh_dlist, peers->wfs_peers ) ) ) {
1470 0 : ulong head_idx = wfs_fresh_dlist_idx_peek_head( peers->wfs_fresh_dlist, peers->wfs_peers );
1471 0 : fd_gui_wfs_peer_t * oldest = &peers->wfs_peers[ head_idx ];
1472 0 : if( oldest->update_time_nanos > now - FD_GUI_WFS_ACTIVITY_TIMEOUT_NANOS ) break;
1473 :
1474 0 : wfs_fresh_dlist_idx_pop_head( peers->wfs_fresh_dlist, peers->wfs_peers );
1475 0 : oldest->is_online = 0;
1476 :
1477 0 : peers->scratch.wfs_peers[ evicted_cnt++ ] = head_idx;
1478 0 : if( FD_UNLIKELY( evicted_cnt>=256UL ) ) break;
1479 0 : }
1480 0 : if( FD_UNLIKELY( evicted_cnt ) ) {
1481 0 : fd_gui_peers_printf_wfs_remove( peers, peers->scratch.wfs_peers, evicted_cnt );
1482 0 : fd_http_server_ws_broadcast( peers->http );
1483 0 : return 1; /* preserve STEM_BURST */
1484 0 : }
1485 :
1486 : /* update client viewports in a round-robin */
1487 0 : if( FD_UNLIKELY( fd_gui_peers_ws_conn_rr_advance( peers, now ) ) ) {
1488 0 : ulong ws_conn_id = peers->active_ws_conn_id;
1489 0 : FD_TEST( peers->client_viewports[ ws_conn_id ].connected );
1490 0 : if( FD_LIKELY( peers->client_viewports[ ws_conn_id ].row_cnt ) ) {
1491 : /* broadcast the diff as cell updates. fd_http_server_ws_send
1492 : tolerates ws_conn_id having already been closed during
1493 : formatting above. */
1494 0 : fd_gui_peers_viewport_snap( peers, ws_conn_id );
1495 0 : fd_gui_printf_peers_viewport_update( peers, ws_conn_id );
1496 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1497 :
1498 : #if LOGGING
1499 : fd_gui_peers_viewport_log( peers, ws_conn_id );
1500 : #endif
1501 0 : (void)fd_gui_peers_viewport_log;
1502 0 : }
1503 :
1504 : /* fd_http_server_ws_send above can close the websocket connection,
1505 : which decrements open_ws_conn_cnt. If the last client was just
1506 : closed, avoid division by zero and fall back to the base interval. */
1507 0 : long divisor = fd_long_max( (long)peers->open_ws_conn_cnt, 1L );
1508 0 : peers->next_client_nanos = now + ((FD_GUI_PEERS_WS_VIEWPORT_UPDATE_INTERVAL_MILLIS * 1000000L) / divisor);
1509 0 : did_work = 1;
1510 0 : }
1511 :
1512 0 : if( FD_UNLIKELY( now >= peers->next_metric_rate_update_nanos ) ) {
1513 0 : for( fd_gui_peers_node_pubkey_map_iter_t iter = fd_gui_peers_node_pubkey_map_iter_init( peers->node_pubkey_map, peers->contact_info_table );
1514 0 : !fd_gui_peers_node_pubkey_map_iter_done( iter, peers->node_pubkey_map, peers->contact_info_table );
1515 0 : iter = fd_gui_peers_node_pubkey_map_iter_next( iter, peers->node_pubkey_map, peers->contact_info_table ) ) {
1516 0 : fd_gui_peers_node_t * peer = fd_gui_peers_node_pubkey_map_iter_ele( iter, peers->node_pubkey_map, peers->contact_info_table );
1517 :
1518 0 : double window = (double)(now - (peers->next_metric_rate_update_nanos - (FD_GUI_PEERS_METRIC_RATE_UPDATE_INTERVAL_MILLIS * 1000000L)));
1519 :
1520 : /* optimization: no need to remove / re-insert if the rates haven't changed */
1521 0 : int change = 0;
1522 0 : for( ulong i=0UL; !change && i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
1523 0 : fd_gui_peers_metric_rate_t * metric = &peer->row.gossvf_rx[ i ];
1524 0 : long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
1525 0 : long new_rate_ema = (long)fd_gui_ema( metric->update_timestamp_ns, now, (double)new_rate, (double)metric->rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1526 0 : if( FD_LIKELY( new_rate_ema==0L && metric->rate_ema==0L ) ) continue; /* don't update zero-bandwith peers */
1527 0 : change = 1;
1528 0 : }
1529 :
1530 0 : for( ulong i=0UL; !change && i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
1531 0 : fd_gui_peers_metric_rate_t * metric = &peer->row.gossip_tx[ i ];
1532 0 : long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
1533 0 : long new_rate_ema = (long)fd_gui_ema( metric->update_timestamp_ns, now, (double)new_rate, (double)metric->rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1534 0 : if( FD_LIKELY( new_rate_ema==0L && metric->rate_ema==0L ) ) continue; /* don't update zero-bandwith peers */
1535 0 : change = 1;
1536 0 : }
1537 :
1538 0 : if( !change ) continue;
1539 :
1540 : /* live_table */
1541 0 : fd_gui_peers_live_table_ele_remove( peers->live_table, peer, peers->contact_info_table );
1542 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
1543 0 : fd_gui_peers_metric_rate_t * metric = &peer->row.gossvf_rx[ i ];
1544 0 : long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
1545 0 : long new_rate_ema = (long)fd_gui_ema( metric->update_timestamp_ns, now, (double)new_rate, (double)metric->rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1546 0 : metric->rate_ema = fd_long_if( new_rate_ema<100L, 0L, new_rate_ema ); /* snap near-zero ema to zero. 100 bytes/s threshold */
1547 0 : metric->ref = metric->cur;
1548 0 : metric->update_timestamp_ns = now;
1549 0 : }
1550 :
1551 0 : for( ulong i=0UL; i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
1552 0 : fd_gui_peers_metric_rate_t * metric = &peer->row.gossip_tx[ i ];
1553 0 : long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
1554 0 : long new_rate_ema = (long)fd_gui_ema( metric->update_timestamp_ns, now, (double)new_rate, (double)metric->rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1555 0 : metric->rate_ema = fd_long_if( new_rate_ema<100L, 0L, new_rate_ema ); /* snap near-zero ema to zero. 100 bytes/s threshold */
1556 0 : metric->ref = metric->cur;
1557 0 : metric->update_timestamp_ns = now;
1558 0 : }
1559 0 : fd_gui_peers_live_table_ele_insert( peers->live_table, peer, peers->contact_info_table );
1560 :
1561 : /* bandwidth_tracking */
1562 0 : fd_gui_peers_bandwidth_tracking_ele_remove( peers->bw_tracking, peer, peers->contact_info_table );
1563 0 : peer->row.gossvf_rx_sum.rate_ema = (long)fd_gui_ema( peer->row.gossvf_rx_sum.update_timestamp_ns, now, (double)((long)peer->row.gossvf_rx_sum.cur - (long)peer->row.gossvf_rx_sum.ref) * 1e9 / window, (double)peer->row.gossvf_rx_sum.rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1564 0 : peer->row.gossvf_rx_sum.ref = peer->row.gossvf_rx_sum.cur;
1565 0 : peer->row.gossvf_rx_sum.update_timestamp_ns = now;
1566 :
1567 0 : peer->row.gossip_tx_sum.rate_ema = (long)fd_gui_ema( peer->row.gossip_tx_sum.update_timestamp_ns, now, (double)((long)peer->row.gossip_tx_sum.cur - (long)peer->row.gossip_tx_sum.ref) * 1e9 / window, (double)peer->row.gossip_tx_sum.rate_ema, FD_GUI_PEERS_EMA_HALF_LIFE_NS );
1568 0 : peer->row.gossip_tx_sum.ref = peer->row.gossip_tx_sum.cur;
1569 0 : peer->row.gossip_tx_sum.update_timestamp_ns = now;
1570 0 : fd_gui_peers_bandwidth_tracking_ele_insert( peers->bw_tracking, peer, peers->contact_info_table );
1571 0 : }
1572 :
1573 0 : peers->next_metric_rate_update_nanos = now + (FD_GUI_PEERS_METRIC_RATE_UPDATE_INTERVAL_MILLIS * 1000000L);
1574 0 : did_work = 1;
1575 : #ifdef FD_GUI_USE_HANDHOLDING
1576 : fd_gui_peers_live_table_verify( peers->live_table, peers->contact_info_table );
1577 : #endif
1578 0 : }
1579 :
1580 0 : if( FD_LIKELY( now >= peers->next_gossip_stats_update_nanos ) ) {
1581 0 : fd_gui_peers_gossip_stats_snap( peers, peers->gossip_stats, now );
1582 0 : fd_gui_peers_printf_gossip_stats( peers );
1583 0 : fd_http_server_ws_broadcast( peers->http );
1584 :
1585 0 : peers->next_gossip_stats_update_nanos = now + (FD_GUI_PEERS_GOSSIP_STATS_UPDATE_INTERVAL_MILLIS * 1000000L);
1586 0 : return 1; /* preserve STEM_BURST */
1587 0 : }
1588 :
1589 0 : return did_work;
1590 0 : }
1591 :
1592 : void
1593 : fd_gui_peers_ws_open( fd_gui_peers_ctx_t * peers,
1594 : ulong ws_conn_id,
1595 0 : long now ) {
1596 0 : peers->client_viewports[ ws_conn_id ].connected = 1;
1597 0 : peers->client_viewports[ ws_conn_id ].connected_time = now;
1598 0 : peers->client_viewports[ ws_conn_id ].start_row = 0;
1599 0 : peers->client_viewports[ ws_conn_id ].row_cnt = 0;
1600 0 : peers->client_viewports[ ws_conn_id ].sort_key = FD_GUI_PEERS_LIVE_TABLE_DEFAULT_SORT_KEY;
1601 0 : fd_gui_peers_ws_conn_rr_grow( peers, ws_conn_id );
1602 :
1603 0 : fd_gui_peers_printf_node_all( peers );
1604 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1605 :
1606 0 : if( FD_UNLIKELY( peers->wfs_stakes_sent ) ) {
1607 0 : fd_gui_peers_printf_wfs_stakes( peers );
1608 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1609 0 : }
1610 :
1611 0 : if( FD_UNLIKELY( peers->wfs_peers_valid ) ) {
1612 0 : ulong added_cnt = 0UL;
1613 0 : for( ulong i=0UL; i<peers->wfs_peers_cnt; i++ ) {
1614 0 : if( FD_UNLIKELY( peers->wfs_peers[ i ].is_online ) ) peers->scratch.wfs_peers[ added_cnt++ ] = i;
1615 0 : }
1616 0 : if( FD_LIKELY( added_cnt ) ) {
1617 0 : fd_gui_peers_printf_wfs_add( peers, peers->scratch.wfs_peers, added_cnt );
1618 0 : FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
1619 0 : }
1620 0 : }
1621 0 : }
1622 :
1623 : void
1624 : fd_gui_peers_ws_close( fd_gui_peers_ctx_t * peers,
1625 0 : ulong ws_conn_id ) {
1626 0 : fd_gui_peers_live_table_sort_key_remove( peers->live_table, &peers->client_viewports[ ws_conn_id ].sort_key );
1627 0 : peers->client_viewports[ ws_conn_id ].connected = 0;
1628 0 : fd_gui_peers_ws_conn_rr_shrink( peers, ws_conn_id );
1629 0 : }
1630 :
1631 : #undef LOGGING
|