LCOV - code coverage report
Current view: top level - disco/gui - fd_gui_peers.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1169 0.0 %
Date: 2026-04-29 06:59:03 Functions: 0 30 0.0 %

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

Generated by: LCOV version 1.14