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