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

Generated by: LCOV version 1.14