LCOV - code coverage report
Current view: top level - disco/gui - fd_gui_peers.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 862 0.0 %
Date: 2025-11-25 04:50:41 Functions: 0 23 0.0 %

          Line data    Source code
       1             : #include "fd_gui_peers.h"
       2             : #include "fd_gui_printf.h"
       3             : #include "fd_gui_config_parse.h"
       4             : #include "fd_gui_metrics.h"
       5             : 
       6             : #include "../../flamenco/gossip/fd_gossip_private.h"
       7             : #include "../../disco/metrics/fd_metrics_base.h"
       8             : 
       9             : FD_IMPORT_BINARY( ipinfo, "src/disco/gui/ipinfo.bin" );
      10             : #define IPINFO_MAX_NODES (1UL<<22UL) /* 4M nodes */
      11             : 
      12             : #define LOGGING 0
      13             : 
      14             : FD_FN_CONST ulong
      15           0 : fd_gui_peers_align( void ) {
      16           0 :   ulong a = 128UL;
      17           0 :   a = fd_ulong_max( a, alignof(fd_gui_peers_ctx_t)              );
      18           0 :   a = fd_ulong_max( a, fd_gui_peers_live_table_align()          );
      19           0 :   a = fd_ulong_max( a, fd_gui_peers_bandwidth_tracking_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 :   a = fd_ulong_max( a, alignof(fd_gui_ipinfo_node_t)            );
      26           0 :   FD_TEST( fd_ulong_pow2_up( a )==a );
      27           0 :   return a;
      28           0 : }
      29             : 
      30             : FD_FN_CONST ulong
      31           0 : fd_gui_peers_footprint( ulong max_ws_conn_cnt ) {
      32           0 :   ulong info_chain_cnt   = fd_gui_peers_node_info_map_chain_cnt_est  ( FD_CONTACT_INFO_TABLE_SIZE );
      33           0 :   ulong pubkey_chain_cnt = fd_gui_peers_node_pubkey_map_chain_cnt_est( FD_CONTACT_INFO_TABLE_SIZE );
      34           0 :   ulong sock_chain_cnt   = fd_gui_peers_node_sock_map_chain_cnt_est  ( FD_CONTACT_INFO_TABLE_SIZE );
      35             : 
      36           0 :   ulong l = FD_LAYOUT_INIT;
      37           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_gui_peers_ctx_t),             sizeof(fd_gui_peers_ctx_t)                                              );
      38           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_peers_live_table_align(),         fd_gui_peers_live_table_footprint        ( FD_CONTACT_INFO_TABLE_SIZE ) );
      39           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_peers_bandwidth_tracking_align(), fd_gui_peers_bandwidth_tracking_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
      40           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 ) );
      41           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_info_map_align(),      fd_gui_peers_node_info_map_footprint     ( info_chain_cnt )             );
      42           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_pubkey_map_align(),    fd_gui_peers_node_pubkey_map_footprint   ( pubkey_chain_cnt )           );
      43           0 :   l = FD_LAYOUT_APPEND( l, fd_gui_peers_node_sock_map_align(),      fd_gui_peers_node_sock_map_footprint     ( sock_chain_cnt )             );
      44           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_gui_peers_ws_conn_t),         max_ws_conn_cnt*sizeof(fd_gui_peers_ws_conn_t)                          );
      45           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_gui_ipinfo_node_t), sizeof(fd_gui_ipinfo_node_t)*IPINFO_MAX_NODES );
      46             : 
      47           0 :   return FD_LAYOUT_FINI( l, fd_gui_peers_align() );
      48           0 : }
      49             : 
      50             : /* We sort the country codes so that fd_gui_peers_live_table can sort by
      51             :    table index instead of the codes themselves. */
      52             : #define SORT_NAME fd_gui_country_code_sort
      53           0 : #define SORT_KEY_T fd_gui_country_code_t
      54           0 : #define SORT_BEFORE(a,b) (strcmp( (char *)&(a), (char *)&(b) ) < 0)
      55             : #include "../../util/tmpl/fd_sort.c"
      56             : 
      57             : static void
      58             : build_ipinfo_trie( fd_gui_peers_ctx_t *   peers,
      59           0 :                    fd_gui_ipinfo_node_t * nodes ) {
      60           0 :   peers->ipinfo.nodes = nodes;
      61           0 :   ulong country_code_cnt = FD_LOAD( ulong, ipinfo );
      62           0 :   FD_TEST( country_code_cnt && country_code_cnt<256UL ); /* 256 reserved for unknown */
      63           0 :   FD_TEST( ipinfo_sz>=8UL+country_code_cnt*2UL );
      64             : 
      65           0 :   for( ulong i=0UL; i<country_code_cnt; i++ ) {
      66           0 :     fd_memcpy( peers->ipinfo.country_code[ i ].cc, ipinfo+8UL+i*2UL, 2UL );
      67           0 :     peers->ipinfo.country_code[ i ].cc[ 2 ] = '\0';
      68           0 :   }
      69             : 
      70           0 :   fd_gui_country_code_sort_insert( peers->ipinfo.country_code, country_code_cnt );
      71             : 
      72           0 :   ulong processed = 8UL+country_code_cnt*2UL;
      73           0 :   FD_TEST( !((ipinfo_sz-processed)%6UL) );
      74           0 :   FD_TEST( (ipinfo_sz-processed)/6UL<=IPINFO_MAX_NODES-1UL );
      75             : 
      76           0 :   fd_gui_ipinfo_node_t * root = &nodes[ 0 ];
      77           0 :   root->left = NULL;
      78           0 :   root->right = NULL;
      79           0 :   root->has_prefix = 0;
      80             : 
      81           0 :   ulong node_cnt = 1UL;
      82           0 :   while( processed<ipinfo_sz ) {
      83           0 :     uint ip_addr = fd_uint_bswap( FD_LOAD( uint, ipinfo+processed ) );
      84           0 :     uchar prefix_len = *( ipinfo+processed+4UL );
      85           0 :     FD_TEST( prefix_len<=32UL );
      86           0 :     uchar country_idx = *( ipinfo+processed+5UL );
      87           0 :     FD_TEST( country_idx<country_code_cnt );
      88             : 
      89           0 :     fd_gui_ipinfo_node_t * node = root;
      90           0 :     for( uchar bit_pos=0; bit_pos<prefix_len; bit_pos++ ) {
      91           0 :       uchar bit = (ip_addr >> (31 - bit_pos)) & 1;
      92             : 
      93           0 :       fd_gui_ipinfo_node_t * child;
      94           0 :       if( FD_LIKELY( !bit ) ) {
      95           0 :         child = node->left;
      96           0 :         if( FD_LIKELY( !child ) ) {
      97           0 :           FD_TEST( node_cnt<IPINFO_MAX_NODES );
      98           0 :           child = &nodes[ node_cnt++ ];
      99           0 :           child->left = NULL;
     100           0 :           child->right = NULL;
     101           0 :           child->has_prefix = 0;
     102           0 :           node->left = child;
     103           0 :         }
     104           0 :       } else {
     105           0 :         child = node->right;
     106           0 :         if( FD_LIKELY( !child ) ) {
     107           0 :           FD_TEST( node_cnt<IPINFO_MAX_NODES );
     108           0 :           child = &nodes[ node_cnt++ ];
     109           0 :           child->left = NULL;
     110           0 :           child->right = NULL;
     111           0 :           child->has_prefix = 0;
     112           0 :           node->right = child;
     113           0 :         }
     114           0 :       }
     115           0 :       node = child;
     116           0 :     }
     117             : 
     118           0 :     node->has_prefix = 1;
     119           0 :     node->country_code_idx = country_idx;
     120             : 
     121           0 :     processed += 6UL;
     122           0 :   }
     123           0 : }
     124             : 
     125             : void *
     126             : fd_gui_peers_new( void *             shmem,
     127             :                   fd_http_server_t * http,
     128             :                   fd_topo_t *        topo,
     129             :                   ulong              max_ws_conn_cnt,
     130           0 :                   long               now ) {
     131           0 :     if( FD_UNLIKELY( !shmem ) ) {
     132           0 :       FD_LOG_WARNING(( "NULL shmem" ));
     133           0 :       return NULL;
     134           0 :     }
     135             : 
     136           0 :     if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_peers_align() ) ) ) {
     137           0 :       FD_LOG_WARNING(( "misaligned shmem" ));
     138           0 :       return NULL;
     139           0 :     }
     140             : 
     141           0 :     ulong info_chain_cnt   = fd_gui_peers_node_info_map_chain_cnt_est  ( FD_CONTACT_INFO_TABLE_SIZE );
     142           0 :     ulong pubkey_chain_cnt = fd_gui_peers_node_pubkey_map_chain_cnt_est( FD_CONTACT_INFO_TABLE_SIZE );
     143           0 :     ulong sock_chain_cnt   = fd_gui_peers_node_sock_map_chain_cnt_est  ( FD_CONTACT_INFO_TABLE_SIZE );
     144             : 
     145           0 :     FD_SCRATCH_ALLOC_INIT( l, shmem );
     146           0 :     fd_gui_peers_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_gui_peers_ctx_t),             sizeof(fd_gui_peers_ctx_t)                                              );
     147           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 ) );
     148           0 :     void * _bw_tracking      = FD_SCRATCH_ALLOC_APPEND( l, fd_gui_peers_bandwidth_tracking_align(), fd_gui_peers_bandwidth_tracking_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
     149           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 ) );
     150           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 )             );
     151           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 )           );
     152           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 )             );
     153           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)                          );
     154           0 :     void * _nodes            = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_gui_ipinfo_node_t),           sizeof(fd_gui_ipinfo_node_t)*IPINFO_MAX_NODES                           );
     155             : 
     156           0 :     for( ulong i = 0UL; i<max_ws_conn_cnt; i++ ) ctx->client_viewports[ i ].connected = 0;
     157             : 
     158           0 :     ctx->http = http;
     159           0 :     ctx->topo = topo;
     160             : 
     161           0 :     ctx->max_ws_conn_cnt   = max_ws_conn_cnt;
     162           0 :     ctx->open_ws_conn_cnt  = 0UL;
     163           0 :     ctx->active_ws_conn_id = ULONG_MAX;
     164             : 
     165           0 :     ctx->slot_voted = ULONG_MAX;
     166             : 
     167           0 :     ctx->next_client_nanos              = now;
     168           0 :     ctx->next_metric_rate_update_nanos  = now;
     169           0 :     ctx->next_gossip_stats_update_nanos = now;
     170           0 :     memset( &ctx->gossip_stats, 0, sizeof(ctx->gossip_stats) );
     171             : 
     172           0 :     for( ulong i = 0; i<FD_CONTACT_INFO_TABLE_SIZE; i++) ctx->contact_info_table[ i ].valid = 0;
     173             : 
     174           0 :     ctx->live_table      = fd_gui_peers_live_table_join( fd_gui_peers_live_table_new( _live_table, FD_CONTACT_INFO_TABLE_SIZE ) );
     175           0 :     fd_gui_peers_live_table_seed( ctx->contact_info_table, FD_CONTACT_INFO_TABLE_SIZE, 42UL );
     176             : 
     177           0 :     ctx->bw_tracking     = fd_gui_peers_bandwidth_tracking_join( fd_gui_peers_bandwidth_tracking_new( _bw_tracking, FD_CONTACT_INFO_TABLE_SIZE ) );
     178           0 :     fd_gui_peers_bandwidth_tracking_seed( ctx->contact_info_table, FD_CONTACT_INFO_TABLE_SIZE, 42UL );
     179             : 
     180           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 ) );
     181           0 :     ctx->node_info_map   = fd_gui_peers_node_info_map_join  ( fd_gui_peers_node_info_map_new  ( _info_map,   info_chain_cnt,   42UL ) );
     182           0 :     ctx->node_pubkey_map = fd_gui_peers_node_pubkey_map_join( fd_gui_peers_node_pubkey_map_new( _pubkey_map, pubkey_chain_cnt, 42UL ) );
     183           0 :     ctx->node_sock_map   = fd_gui_peers_node_sock_map_join  ( fd_gui_peers_node_sock_map_new  ( _sock_map,   sock_chain_cnt,   42UL ) );
     184             : 
     185           0 :     build_ipinfo_trie( ctx, _nodes );
     186             : 
     187           0 :     return shmem;
     188           0 : }
     189             : 
     190             : fd_gui_peers_ctx_t *
     191           0 : fd_gui_peers_join( void * shmem ) {
     192           0 :   if( FD_UNLIKELY( !shmem ) ) {
     193           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     194           0 :     return NULL;
     195           0 :   }
     196             : 
     197           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gui_peers_align() ) ) ) {
     198           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     199           0 :     return NULL;
     200           0 :   }
     201             : 
     202           0 :   fd_gui_peers_ctx_t * ctx = (fd_gui_peers_ctx_t *)shmem;
     203             : 
     204           0 :   return ctx;
     205           0 : }
     206             : 
     207             : static void
     208             : fd_gui_peers_gossip_stats_snap( fd_gui_peers_ctx_t *          peers,
     209             :                                 fd_gui_peers_gossip_stats_t * gossip_stats,
     210           0 :                                 long                          now ) {
     211           0 :   gossip_stats->sample_time = now;
     212           0 :   ulong gossvf_tile_cnt = fd_topo_tile_name_cnt( peers->topo, "gossvf"  );
     213           0 :   ulong gossip_tile_cnt = 1UL;
     214             : 
     215           0 :   gossip_stats->network_health_pull_response_msg_rx_success =
     216           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PULL_RESPONSE ) );
     217           0 :   gossip_stats->network_health_pull_response_msg_rx_failure =
     218           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_RESPONSE_NO_VALID_CRDS ) );
     219           0 :   gossip_stats->network_health_push_msg_rx_success =
     220           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PUSH ) );
     221           0 :   gossip_stats->network_health_push_msg_rx_failure =
     222           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PUSH_NO_VALID_CRDS ) );
     223           0 :   gossip_stats->network_health_push_crds_rx_success =
     224           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_UPSERTED_PUSH ) );
     225           0 :   gossip_stats->network_health_push_crds_rx_failure =
     226           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PUSH_STALE ) )
     227           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PUSH_DUPLICATE ) )
     228           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_SIGNATURE ) )
     229           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_RELAYER_NO_CONTACT_INFO ) )
     230           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_RELAYER_SHRED_VERSION ) )
     231           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_ORIGIN_NO_CONTACT_INFO ) )
     232           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_ORIGIN_SHRED_VERSION ) )
     233           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_INACTIVE ) )
     234           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PUSH_WALLCLOCK ) );
     235           0 :   gossip_stats->network_health_pull_response_crds_rx_success =
     236           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_UPSERTED_PULL_RESPONSE ) );
     237           0 :   gossip_stats->network_health_pull_response_crds_rx_failure =
     238           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_STALE ) )
     239           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_WALLCLOCK ) )
     240           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_DUPLICATE ) )
     241           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_DUPLICATE ) )
     242           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_SIGNATURE ) )
     243           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_RELAYER_SHRED_VERSION ) )
     244           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_ORIGIN_NO_CONTACT_INFO ) )
     245           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_ORIGIN_SHRED_VERSION ) )
     246           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_INACTIVE ) );
     247           0 :   gossip_stats->network_health_push_crds_rx_duplicate =
     248           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PUSH_DUPLICATE ) );
     249           0 :   gossip_stats->network_health_pull_response_crds_rx_duplicate =
     250           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_DUPLICATE ) )
     251           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, CRDS_RX_COUNT_DROPPED_PULL_RESPONSE_DUPLICATE ) );
     252             : 
     253           0 :   gossip_stats->network_health_total_stake = 0UL; /* todo ... fetch from RPC */
     254           0 :   gossip_stats->network_health_total_peers = 0UL; /* todo ... fetch from RPC */
     255             : 
     256           0 :   gossip_stats->network_health_connected_stake          = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_TOTAL_STAKE ) );
     257           0 :   gossip_stats->network_health_connected_staked_peers   = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_STAKED_COUNT ) );
     258           0 :   gossip_stats->network_health_connected_unstaked_peers = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_PEER_UNSTAKED_COUNT ) );
     259             : 
     260           0 :   gossip_stats->network_ingress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
     261           0 :   gossip_stats->network_ingress_total_bytes_per_sec = 0UL;
     262             : 
     263           0 :   for( fd_gui_peers_bandwidth_tracking_fwd_iter_t iter = fd_gui_peers_bandwidth_tracking_fwd_iter_init( peers->bw_tracking, &FD_GUI_PEERS_BW_TRACKING_INGRESS_SORT_KEY, peers->contact_info_table ), j = 0UL;
     264           0 :        !fd_gui_peers_bandwidth_tracking_fwd_iter_done( iter );
     265           0 :        iter = fd_gui_peers_bandwidth_tracking_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
     266           0 :     fd_gui_peers_node_t * cur = fd_gui_peers_bandwidth_tracking_fwd_iter_ele( iter, peers->contact_info_table );
     267             : 
     268           0 :     if( FD_UNLIKELY( j<gossip_stats->network_ingress_peer_sz ) ) {
     269           0 :       fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &cur->contact_info.pubkey, NULL, peers->node_info_pool );
     270           0 :       if( FD_LIKELY( node_info ) ) FD_TEST( fd_cstr_printf_check( gossip_stats->network_ingress_peer_names[ j ], FD_GUI_CONFIG_PARSE_VALIDATOR_INFO_NAME_SZ+1UL, NULL, "%s", node_info->name ) );
     271           0 :       else                         gossip_stats->network_ingress_peer_names[ j ][ 0 ] = '\0';
     272           0 :       gossip_stats->network_ingress_peer_bytes_per_sec[ j ] = cur->gossvf_rx_sum.rate_ema;
     273           0 :       fd_memcpy( &gossip_stats->network_ingress_peer_identities[ j ], cur->contact_info.pubkey.uc, 32UL );
     274           0 :     }
     275             : 
     276           0 :     gossip_stats->network_ingress_total_bytes_per_sec += cur->gossvf_rx_sum.rate_ema;
     277           0 :   }
     278             : 
     279           0 :   gossip_stats->network_ingress_total_bytes = fd_gui_metrics_gossip_total_ingress_bytes( peers->topo, gossvf_tile_cnt );
     280             : 
     281           0 :   gossip_stats->network_egress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
     282             : 
     283           0 :   FD_TEST( gossip_stats->network_egress_peer_sz==gossip_stats->network_ingress_peer_sz );
     284             : 
     285           0 :   gossip_stats->network_egress_peer_sz = fd_ulong_min( fd_gui_peers_bandwidth_tracking_ele_cnt( peers->bw_tracking ), FD_GUI_PEERS_GOSSIP_TOP_PEERS_CNT );
     286           0 :   gossip_stats->network_egress_total_bytes_per_sec = 0UL;
     287             : 
     288           0 :   for( fd_gui_peers_bandwidth_tracking_fwd_iter_t iter = fd_gui_peers_bandwidth_tracking_fwd_iter_init( peers->bw_tracking, &FD_GUI_PEERS_BW_TRACKING_EGRESS_SORT_KEY, peers->contact_info_table ), j = 0UL;
     289           0 :        !fd_gui_peers_bandwidth_tracking_fwd_iter_done( iter );
     290           0 :        iter = fd_gui_peers_bandwidth_tracking_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
     291           0 :     fd_gui_peers_node_t * cur = fd_gui_peers_bandwidth_tracking_fwd_iter_ele( iter, peers->contact_info_table );
     292             : 
     293           0 :     if( FD_UNLIKELY( j<gossip_stats->network_egress_peer_sz ) ) {
     294           0 :       fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &cur->contact_info.pubkey, NULL, peers->node_info_pool );
     295           0 :       if( FD_LIKELY( node_info ) ) FD_TEST( fd_cstr_printf_check( gossip_stats->network_egress_peer_names[ j ], FD_GUI_CONFIG_PARSE_VALIDATOR_INFO_NAME_SZ+1UL, NULL, "%s", node_info->name ) );
     296           0 :       else                         gossip_stats->network_egress_peer_names[ j ][ 0 ] = '\0';
     297           0 :       gossip_stats->network_egress_peer_bytes_per_sec[ j ] = cur->gossip_tx_sum.rate_ema;
     298           0 :       fd_memcpy( &gossip_stats->network_egress_peer_identities[ j ], cur->contact_info.pubkey.uc, 32UL );
     299           0 :     }
     300             : 
     301           0 :     gossip_stats->network_egress_total_bytes_per_sec += cur->gossip_tx_sum.rate_ema;
     302           0 :   }
     303             : 
     304           0 :   gossip_stats->network_egress_total_bytes = fd_gui_metrics_gosip_total_egress_bytes( peers->topo, gossip_tile_cnt );
     305             : 
     306           0 :   gossip_stats->storage_capacity = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_CAPACITY ) );
     307           0 :   gossip_stats->storage_expired_cnt = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_EXPIRED_COUNT ) );
     308           0 :   gossip_stats->storage_evicted_cnt = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_EVICTED_COUNT ) );
     309             : 
     310           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX               ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_CONTACT_INFO_V1 )               );
     311           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX                          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_VOTE )                          );
     312           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX                   ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_LOWEST_SLOT )                   );
     313           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX               ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_SNAPSHOT_HASHES )               );
     314           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX               ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_ACCOUNTS_HASHES )               );
     315           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX                   ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_EPOCH_SLOTS )                   );
     316           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX                    ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_VERSION_V1 )                    );
     317           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX                    ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_VERSION_V2 )                    );
     318           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX                 ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_NODE_INSTANCE )                 );
     319           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX               ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_DUPLICATE_SHRED )               );
     320           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX   ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_INCREMENTAL_SNAPSHOT_HASHES )   );
     321           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX               ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_CONTACT_INFO_V2 )               );
     322           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_RESTART_LAST_VOTED_FORK_SLOTS ) );
     323           0 :   gossip_stats->storage_active_cnt[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX         ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( GAUGE, GOSSIP, CRDS_COUNT_RESTART_HEAVIEST_FORK )         );
     324             : 
     325           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX ] =
     326           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_CONTACT_INFO_V1 ) )
     327           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_CONTACT_INFO_V1 ) );
     328           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX ] =
     329           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_VOTE ) )
     330           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_VOTE ) );
     331           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX ] =
     332           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_LOWEST_SLOT ) )
     333           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_LOWEST_SLOT ) );
     334           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX ] =
     335           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_SNAPSHOT_HASHES ) )
     336           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_SNAPSHOT_HASHES ) );
     337           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX ] =
     338           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_ACCOUNTS_HASHES ) )
     339           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_ACCOUNTS_HASHES ) );
     340           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX ] =
     341           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_EPOCH_SLOTS ) )
     342           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_EPOCH_SLOTS ) );
     343           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX ] =
     344           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_VERSION_V1 ) )
     345           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_VERSION_V1 ) );
     346           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX ] =
     347           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_VERSION_V2 ) )
     348           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_VERSION_V2 ) );
     349           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX ] =
     350           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_NODE_INSTANCE ) )
     351           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_NODE_INSTANCE ) );
     352           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX ] =
     353           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_DUPLICATE_SHRED ) )
     354           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_DUPLICATE_SHRED ) );
     355           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX ] =
     356           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_INCREMENTAL_SNAPSHOT_HASHES ) )
     357           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_INCREMENTAL_SNAPSHOT_HASHES ) );
     358           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX ] =
     359           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_CONTACT_INFO_V2 ) )
     360           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_CONTACT_INFO_V2 ) );
     361           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] =
     362           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_RESTART_LAST_VOTED_FORK_SLOTS ) )
     363           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_RESTART_LAST_VOTED_FORK_SLOTS ) );
     364           0 :   gossip_stats->storage_cnt_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX ] =
     365           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_COUNT_RESTART_HEAVIEST_FORK ) )
     366           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_COUNT_RESTART_HEAVIEST_FORK ) );
     367             : 
     368           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V1_IDX ] =
     369           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_CONTACT_INFO_V1 ) )
     370           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_CONTACT_INFO_V1 ) );
     371           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VOTE_IDX ] =
     372           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_VOTE ) )
     373           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_VOTE ) );
     374           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_LOWEST_SLOT_IDX ] =
     375           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_LOWEST_SLOT ) )
     376           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_LOWEST_SLOT ) );
     377           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_SNAPSHOT_HASHES_IDX ] =
     378           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_SNAPSHOT_HASHES ) )
     379           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_SNAPSHOT_HASHES ) );
     380           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_ACCOUNTS_HASHES_IDX ] =
     381           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_ACCOUNTS_HASHES ) )
     382           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_ACCOUNTS_HASHES ) );
     383           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_EPOCH_SLOTS_IDX ] =
     384           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_EPOCH_SLOTS ) )
     385           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_EPOCH_SLOTS ) );
     386           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V1_IDX ] =
     387           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_VERSION_V1 ) )
     388           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_VERSION_V1 ) );
     389           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_VERSION_V2_IDX ] =
     390           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_VERSION_V2 ) )
     391           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_VERSION_V2 ) );
     392           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_NODE_INSTANCE_IDX ] =
     393           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_NODE_INSTANCE ) )
     394           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_NODE_INSTANCE ) );
     395           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_DUPLICATE_SHRED_IDX ] =
     396           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_DUPLICATE_SHRED ) )
     397           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_DUPLICATE_SHRED ) );
     398           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_INCREMENTAL_SNAPSHOT_HASHES_IDX ] =
     399           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_INCREMENTAL_SNAPSHOT_HASHES ) )
     400           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_INCREMENTAL_SNAPSHOT_HASHES ) );
     401           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_CONTACT_INFO_V2_IDX ] =
     402           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_CONTACT_INFO_V2 ) )
     403           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_CONTACT_INFO_V2 ) );
     404           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_LAST_VOTED_FORK_SLOTS_IDX ] =
     405           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_RESTART_LAST_VOTED_FORK_SLOTS ) )
     406           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_RESTART_LAST_VOTED_FORK_SLOTS ) );
     407           0 :   gossip_stats->storage_bytes_tx[ FD_METRICS_ENUM_CRDS_VALUE_V_RESTART_HEAVIEST_FORK_IDX ] =
     408           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PUSH_BYTES_RESTART_HEAVIEST_FORK ) )
     409           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, CRDS_TX_PULL_RESPONSE_BYTES_RESTART_HEAVIEST_FORK ) );
     410             : 
     411           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX  ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PULL_REQUEST ) );
     412           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PULL_RESPONSE ) );
     413           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PUSH ) );
     414           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PING ) );
     415           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PONG ) );
     416           0 :   gossip_stats->messages_bytes_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX         ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_BYTES_SUCCESS_PRUNE ) );
     417             : 
     418           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX  ] =
     419           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PULL_REQUEST ) )
     420           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_NOT_CONTACT_INFO ) )
     421           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_LOOPBACK ) )
     422           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_INACTIVE ) )
     423           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_WALLCLOCK ) )
     424           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_SIGNATURE ) )
     425           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_REQUEST_SHRED_VERSION ) );
     426           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] =
     427           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PULL_RESPONSE ) )
     428           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PULL_RESPONSE_NO_VALID_CRDS ) );
     429           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX          ] =
     430           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PUSH ) )
     431           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PUSH_NO_VALID_CRDS ) );
     432           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX          ] =
     433           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PING ) )
     434           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PING_SIGNATURE ) );
     435           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX          ] =
     436           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PONG ) )
     437           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PONG_SIGNATURE ) );
     438           0 :   gossip_stats->messages_count_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX         ] =
     439           0 :       fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_SUCCESS_PRUNE ) )
     440           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PRUNE_DESTINATION ) )
     441           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PRUNE_WALLCLOCK ) )
     442           0 :     + fd_gui_metrics_sum_tiles_counter( peers->topo, "gossvf", gossvf_tile_cnt, MIDX( COUNTER, GOSSVF, MESSAGE_RX_COUNT_DROPPED_PRUNE_SIGNATURE ) );
     443             : 
     444           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX  ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PULL_REQUEST ) );
     445           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PULL_RESPONSE ) );
     446           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PUSH ) );
     447           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PING ) );
     448           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PONG ) );
     449           0 :   gossip_stats->messages_bytes_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX         ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_BYTES_PRUNE ) );
     450             : 
     451           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_REQUEST_IDX  ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PULL_REQUEST ) );
     452           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PULL_RESPONSE ) );
     453           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PUSH ) );
     454           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PING_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PING ) );
     455           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PONG_IDX          ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PONG ) );
     456           0 :   gossip_stats->messages_count_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PRUNE_IDX         ] = fd_gui_metrics_sum_tiles_counter( peers->topo, "gossip", gossip_tile_cnt, MIDX( COUNTER, GOSSIP, MESSAGE_TX_COUNT_PRUNE ) );
     457           0 : }
     458             : 
     459             : static int
     460             : fd_gui_peers_contact_info_eq( fd_contact_info_t const * ci1,
     461           0 :                               fd_contact_info_t const * ci2 ) {
     462           0 :   int ci_eq =
     463           0 :        ci1->shred_version                    == ci2->shred_version
     464           0 :     && ci1->instance_creation_wallclock_nanos== ci2->instance_creation_wallclock_nanos
     465             :  // && ci1->wallclock_nanos                  == ci2->wallclock_nanos
     466           0 :     && ci1->version.client                   == ci2->version.client
     467           0 :     && ci1->version.major                    == ci2->version.major
     468           0 :     && ci1->version.minor                    == ci2->version.minor
     469           0 :     && ci1->version.patch                    == ci2->version.patch
     470           0 :     && ci1->version.commit                   == ci2->version.commit
     471           0 :     && ci1->version.feature_set              == ci2->version.feature_set;
     472             : 
     473           0 :     if( FD_LIKELY( !ci_eq ) ) return 0;
     474           0 :     for( ulong j=0UL; j<(FD_CONTACT_INFO_SOCKET_CNT); j++ ) {
     475           0 :       if( FD_LIKELY( !(ci1->sockets[ j ].addr==ci2->sockets[ j ].addr && ci1->sockets[ j ].port==ci2->sockets[ j ].port) ) ) return 0;
     476           0 :     }
     477           0 :     return 1;
     478           0 : }
     479             : 
     480             : void
     481             : fd_gui_peers_handle_gossip_message( fd_gui_peers_ctx_t *  peers,
     482             :                                     uchar const *         payload,
     483             :                                     ulong                 payload_sz,
     484             :                                     fd_ip4_port_t const * peer_sock,
     485           0 :                                     int                   is_rx ) {
     486           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 );
     487             : 
     488             :   /* We set MAP_MULTI=1 since there are not guarantees that duplicates
     489             :      sockets wont exist. In cases where we see multiple sockets the
     490             :      update timestamp in fd_gui_peers_node_t is the tiebreaker */
     491           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 ) ) {
     492           0 :     if( peer->update_time_nanos>p->update_time_nanos ) peer = p;
     493           0 :   }
     494             : 
     495           0 :   if( FD_UNLIKELY( !peer ) ) return; /* NOP, peer not known yet */
     496             : 
     497           0 :   fd_gossip_view_t view[ 1 ];
     498           0 :   ulong decode_sz = fd_gossip_msg_parse( view, payload, payload_sz );
     499           0 :   if( FD_UNLIKELY( !decode_sz ) ) return; /* NOP, msg unparsable */
     500             : 
     501           0 :   FD_TEST( view->tag < FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT );
     502           0 :   fd_ptr_if( is_rx, &peer->gossvf_rx[ view->tag ], &peer->gossip_tx[ view->tag ] )->cur += payload_sz;
     503           0 :   fd_ptr_if( is_rx, (fd_gui_peers_metric_rate_t *)&peer->gossvf_rx_sum, (fd_gui_peers_metric_rate_t *)&peer->gossip_tx_sum )->cur += payload_sz;
     504             : #if LOGGING
     505             :   if( is_rx ) FD_LOG_WARNING(("payload rx=%lu", payload_sz ));
     506             :   else FD_LOG_WARNING(("payload tx=%lu", payload_sz ));
     507             : #endif
     508           0 : }
     509             : 
     510             : #if FD_HAS_ZSTD
     511             : 
     512             : static uchar
     513             : ipinfo_lookup( fd_gui_ipinfo_node_t const * nodes,
     514           0 :                uint                         ip_addr ) {
     515           0 :   uchar best_country_idx = 0;
     516           0 :   uchar found_match = 0;
     517             : 
     518           0 :   uint ip_addr_host = fd_uint_bswap( ip_addr );
     519             : 
     520           0 :   fd_gui_ipinfo_node_t const * node = &nodes[0];
     521             : 
     522           0 :   for( uchar bit_pos=0; bit_pos<32; bit_pos++ ) {
     523           0 :     if( FD_UNLIKELY( node->has_prefix ) ) {
     524           0 :       best_country_idx = node->country_code_idx;
     525           0 :       found_match = 1;
     526           0 :     }
     527             : 
     528           0 :     uchar bit = (ip_addr_host >> (31 - bit_pos)) & 1;
     529           0 :     fd_gui_ipinfo_node_t const * child = bit ? node->right : node->left;
     530           0 :     if( FD_UNLIKELY( !child ) ) break;
     531             : 
     532           0 :     node = child;
     533           0 :   }
     534             : 
     535           0 :   if( FD_UNLIKELY( node->has_prefix ) ) {
     536           0 :     best_country_idx = node->country_code_idx;
     537           0 :     found_match = 1;
     538           0 :   }
     539             : 
     540           0 :   if( FD_LIKELY( found_match ) ) return best_country_idx;
     541           0 :   return UCHAR_MAX;
     542           0 : }
     543             : 
     544             : #endif
     545             : 
     546             : void
     547             : fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t *               peers,
     548             :                                    fd_gossip_update_message_t const * update,
     549           0 :                                    long                               now ) {
     550           0 :     switch( update->tag ) {
     551           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: {
     552             : #ifdef FD_GUI_USE_HANDHOLDING
     553             :         /* origin_pubkey should be the same as the contact info pubkey */
     554             :         if( FD_UNLIKELY( memcmp( update->contact_info.contact_info->pubkey.uc, update->origin_pubkey, 32UL ) ) ) {
     555             :           char ci_pk[ FD_BASE58_ENCODED_32_SZ ];
     556             :           char og_pk[ FD_BASE58_ENCODED_32_SZ ];
     557             :           fd_base58_encode_32( update->contact_info.contact_info->pubkey.uc, NULL, ci_pk );
     558             :           fd_base58_encode_32( update->origin_pubkey, NULL, og_pk );
     559             : 
     560             :           FD_LOG_ERR(( "invariant violation: update->contact_info.contact_info->pubkey.uc=%s != update->origin_pubkey=%s ", ci_pk, og_pk ));
     561             :         }
     562             :         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 ));
     563             : #endif
     564           0 :         fd_gui_peers_node_t * peer = &peers->contact_info_table[ update->contact_info.idx ];
     565           0 :         if( FD_LIKELY( peer->valid ) ) {
     566             : #if LOGGING
     567             :           char _pk[ FD_BASE58_ENCODED_32_SZ ];
     568             :           fd_base58_encode_32( update->origin_pubkey, NULL, _pk );
     569             :           FD_LOG_WARNING(("UPDATE %lu pk=%s", update->contact_info.idx, _pk ));
     570             : #endif
     571             : #ifdef FD_GUI_USE_HANDHOLDING
     572             :           /* invariant checks */
     573             :           if( FD_UNLIKELY( memcmp( peer->contact_info.pubkey.uc, update->origin_pubkey, 32UL ) ) ) {
     574             :             char ci_pk[ FD_BASE58_ENCODED_32_SZ ];
     575             :             char og_pk[ FD_BASE58_ENCODED_32_SZ ];
     576             :             fd_base58_encode_32( peer->contact_info.pubkey.uc, NULL, ci_pk );
     577             :             fd_base58_encode_32( update->origin_pubkey, NULL, og_pk );
     578             : 
     579             :             /* A new pubkey is not allowed to overwrite an existing valid index */
     580             :             FD_LOG_ERR(( "invariant violation: peer->contact_info.pubkey.uc=%s != update->origin_pubkey=%s ", ci_pk, og_pk ));
     581             :           }
     582             :           FD_TEST( peer==fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, (fd_pubkey_t * )update->origin_pubkey, NULL, peers->contact_info_table ) );
     583             :           fd_gui_peers_node_t * peer_sock = fd_gui_peers_node_sock_map_ele_query( peers->node_sock_map, &peer->contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ], NULL, peers->contact_info_table );
     584             :           int found = 0;
     585             :           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 ) ) {
     586             :             if( peer==p ) {
     587             :               found = 1;
     588             :               break;
     589             :             }
     590             :           }
     591             :           FD_TEST( found );
     592             : #endif
     593             :           /* update does nothing */
     594           0 :           if( FD_UNLIKELY( fd_gui_peers_contact_info_eq( &peer->contact_info, update->contact_info.contact_info ) ) ) {
     595           0 :             peer->contact_info.wallclock_nanos = update->contact_info.contact_info->wallclock_nanos;
     596           0 :             break;
     597           0 :           }
     598             : 
     599           0 :           fd_gui_peers_node_sock_map_idx_remove_fast( peers->node_sock_map, update->contact_info.idx, peers->contact_info_table );
     600           0 :           fd_gui_peers_live_table_idx_remove        ( peers->live_table,    update->contact_info.idx, peers->contact_info_table );
     601             : 
     602           0 :           fd_memcpy( &peer->contact_info, update->contact_info.contact_info, sizeof(peer->contact_info) );
     603             : 
     604           0 :           peer->update_time_nanos = now;
     605             :           /* fetch and set country code */
     606           0 : #if FD_HAS_ZSTD
     607           0 :           peer->country_code_idx = ipinfo_lookup( peers->ipinfo.nodes, peer->contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ].addr );
     608             : #else
     609             :           peer->country_code_idx = UCHAR_MAX;
     610             : #endif
     611             : 
     612           0 :           fd_gui_peers_live_table_idx_insert        ( peers->live_table,    update->contact_info.idx, peers->contact_info_table );
     613           0 :           fd_gui_peers_node_sock_map_idx_insert     ( peers->node_sock_map, update->contact_info.idx, peers->contact_info_table );
     614             : 
     615             :           /* broadcast update to WebSocket clients */
     616           0 :           fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_UPDATE }, (ulong[]){ update->contact_info.idx }, 1UL );
     617           0 :           fd_http_server_ws_broadcast( peers->http );
     618           0 :         } else {
     619           0 :           FD_TEST( !fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, &update->contact_info.contact_info->pubkey, NULL, peers->contact_info_table ) );
     620             : #if LOGGING
     621             :           char _pk[ FD_BASE58_ENCODED_32_SZ ];
     622             :           fd_base58_encode_32( update->origin_pubkey, NULL, _pk );
     623             :           FD_LOG_WARNING(( "ADD %lu pk=%s", update->contact_info.idx, _pk ));
     624             : #endif
     625           0 :           memset( &peer->gossvf_rx,     0, sizeof(peer->gossvf_rx) );
     626           0 :           memset( &peer->gossip_tx,     0, sizeof(peer->gossip_tx) );
     627           0 :           memset( &peer->gossvf_rx_sum, 0, sizeof(peer->gossvf_rx_sum) );
     628           0 :           memset( &peer->gossip_tx_sum, 0, sizeof(peer->gossip_tx_sum) );
     629           0 :           peer->has_vote_info = 0;
     630           0 :           peer->stake = ULONG_MAX;
     631             : 
     632           0 :           fd_gui_config_parse_info_t * info =  fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &update->contact_info.contact_info->pubkey, NULL, peers->node_info_pool );
     633           0 :           if( FD_LIKELY( info ) ) fd_memcpy( peer->name, info->name, sizeof(info->name) );
     634           0 :           else                    peer->name[ 0 ] = '\0';
     635             : 
     636           0 :           peer->update_time_nanos = now;
     637           0 :           fd_memcpy( &peer->contact_info, update->contact_info.contact_info, sizeof(peer->contact_info) );
     638             : 
     639             :           /* fetch and set country code */
     640           0 : #if FD_HAS_ZSTD
     641           0 :           peer->country_code_idx = ipinfo_lookup( peers->ipinfo.nodes, peer->contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ].addr );
     642             : #else
     643             :           peer->country_code_idx = UCHAR_MAX;
     644             : #endif
     645           0 :           peer->valid = 1;
     646             : 
     647             :           /* update pubkey_map, sock_map */
     648           0 :           fd_gui_peers_node_sock_map_idx_insert  ( peers->node_sock_map,   update->contact_info.idx, peers->contact_info_table );
     649           0 :           fd_gui_peers_node_pubkey_map_idx_insert( peers->node_pubkey_map, update->contact_info.idx, peers->contact_info_table );
     650             : 
     651             :           /* update live tables */
     652           0 :           fd_gui_peers_live_table_idx_insert        ( peers->live_table,  update->contact_info.idx, peers->contact_info_table );
     653           0 :           fd_gui_peers_bandwidth_tracking_idx_insert( peers->bw_tracking, update->contact_info.idx, peers->contact_info_table );
     654             : 
     655           0 :           fd_gui_printf_peers_view_resize( peers, fd_gui_peers_live_table_ele_cnt( peers->live_table ) );
     656           0 :           fd_http_server_ws_broadcast( peers->http );
     657             : 
     658             :           /* broadcast update to WebSocket clients */
     659           0 :           fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_ADD }, (ulong[]){ update->contact_info.idx }, 1UL );
     660           0 :           fd_http_server_ws_broadcast( peers->http );
     661           0 :         }
     662           0 :         break;
     663           0 :       }
     664           0 :       case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: {
     665           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 ));
     666             : #if LOGGING
     667             :         char _pk[ FD_BASE58_ENCODED_32_SZ ];
     668             :         fd_base58_encode_32( update->origin_pubkey, NULL, _pk );
     669             :         FD_LOG_WARNING(( "REMOVE %lu pk=%s",update->contact_info_remove.idx, _pk ));
     670             : #endif
     671             : 
     672           0 :         fd_gui_peers_node_t * peer = &peers->contact_info_table[ update->contact_info_remove.idx ];
     673             : 
     674             : #ifdef FD_GUI_USE_HANDHOLDING
     675             :         /* invariant checks */
     676             :         FD_TEST( peer->valid ); /* Should have already been in the table */
     677             :         FD_TEST( peer==fd_gui_peers_node_pubkey_map_ele_query_const( peers->node_pubkey_map, (fd_pubkey_t * )update->origin_pubkey, NULL, peers->contact_info_table ) );
     678             :         fd_gui_peers_node_t * peer_sock = fd_gui_peers_node_sock_map_ele_query( peers->node_sock_map, &peer->contact_info.sockets[ FD_CONTACT_INFO_SOCKET_GOSSIP ], NULL, peers->contact_info_table );
     679             :         int found = 0;
     680             :         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 ) ) {
     681             :           if( peer==p ) {
     682             :             found = 1;
     683             :             break;
     684             :           }
     685             :         }
     686             :         FD_TEST( found );
     687             : #endif
     688           0 :         fd_gui_peers_live_table_idx_remove          ( peers->live_table,      update->contact_info_remove.idx, peers->contact_info_table );
     689           0 :         fd_gui_peers_bandwidth_tracking_idx_remove  ( peers->bw_tracking,     update->contact_info_remove.idx, peers->contact_info_table );
     690           0 :         fd_gui_peers_node_sock_map_idx_remove_fast  ( peers->node_sock_map,   update->contact_info_remove.idx, peers->contact_info_table );
     691           0 :         fd_gui_peers_node_pubkey_map_idx_remove_fast( peers->node_pubkey_map, update->contact_info_remove.idx, peers->contact_info_table );
     692           0 :         peer->valid = 0;
     693             : 
     694           0 :         fd_gui_printf_peers_view_resize( peers, fd_gui_peers_live_table_ele_cnt( peers->live_table ) );
     695           0 :         fd_http_server_ws_broadcast( peers->http );
     696             : 
     697             :         /* broadcast update to WebSocket clients */
     698           0 :         fd_gui_peers_printf_nodes( peers, (int[]){ FD_GUI_PEERS_NODE_DELETE }, (ulong[]){ update->contact_info_remove.idx }, 1UL );
     699           0 :         fd_http_server_ws_broadcast( peers->http );
     700           0 :         break;
     701           0 :       }
     702           0 :       default: break;
     703           0 :     }
     704           0 : }
     705             : 
     706             : #define SORT_NAME fd_gui_peers_votes_slot_sort
     707           0 : #define SORT_KEY_T fd_gui_peers_vote_t
     708           0 : #define SORT_BEFORE(a,b) ((a).last_vote_slot<(b).last_vote_slot)
     709             : #include "../../util/tmpl/fd_sort.c"
     710             : 
     711             : #define SORT_NAME fd_gui_peers_votes_stake_sort
     712           0 : #define SORT_KEY_T fd_gui_peers_vote_t
     713           0 : #define SORT_BEFORE(a,b) ((a).stake>(b).stake)
     714             : #include "../../util/tmpl/fd_sort.c"
     715             : 
     716             : #define SORT_NAME fd_gui_peers_votes_pkey_sort
     717           0 : #define SORT_KEY_T fd_gui_peers_vote_t
     718           0 : #define SORT_BEFORE(a,b) ( memcmp((a).node_account.uc, (b).node_account.uc, sizeof(fd_pubkey_t) ) < 0 )
     719             : #include "../../util/tmpl/fd_sort.c"
     720             : 
     721             : void
     722             : fd_gui_peers_handle_vote_update( fd_gui_peers_ctx_t *  peers,
     723             :                                  fd_gui_peers_vote_t * votes,
     724             :                                  ulong                 vote_cnt,
     725             :                                  long                  now,
     726           0 :                                  fd_pubkey_t *         identity ) {
     727           0 :   (void)now;
     728           0 :   fd_gui_peers_vote_t * votes_sorted  = votes;
     729           0 :   fd_gui_peers_vote_t * votes_scratch = peers->votes_scratch;
     730           0 :   fd_memcpy( votes_sorted, votes, vote_cnt*sizeof(fd_gui_peers_vote_t) );
     731             : 
     732             :   /* deduplicate node accounts, keeping the vote accounts with largest stake */
     733           0 :   fd_gui_peers_votes_stake_sort_inplace( votes_sorted, vote_cnt );
     734           0 :   fd_gui_peers_votes_pkey_sort_stable( votes_sorted, vote_cnt, votes_scratch );
     735             : 
     736           0 :   ulong total_stake = 0UL;
     737           0 :   fd_pubkey_t prev_peer = { 0 };
     738           0 :   for( ulong i=0UL; i<vote_cnt; i++ ) {
     739           0 :     if( FD_UNLIKELY( !memcmp( prev_peer.uc, votes_sorted[ i ].node_account.uc, sizeof(fd_pubkey_t) ) ) ) {
     740           0 :       votes_sorted[ i ].stake = ULONG_MAX; /* flag as duplicate */
     741           0 :     } else {
     742           0 :       total_stake += votes_sorted[ i ].stake;
     743           0 :     }
     744           0 :     prev_peer = votes_sorted[ i ].node_account;
     745           0 :   }
     746             : 
     747             :   /* get stake-weighted 67th percentile last_vote_slot */
     748           0 :   fd_gui_peers_votes_slot_sort_inplace( votes_sorted, vote_cnt );
     749             : 
     750           0 :   ulong cumulative_stake = 0UL;
     751           0 :   ulong last_vote_slot_p67 = ULONG_MAX;
     752           0 :   for( ulong i=0UL; i<vote_cnt; i++ ) {
     753           0 :     if( FD_UNLIKELY( votes_sorted[ i ].stake==ULONG_MAX ) ) continue;
     754           0 :     cumulative_stake += votes_sorted[ i ].stake;
     755           0 :     if( FD_LIKELY( 3*cumulative_stake>2*total_stake ) ) {
     756           0 :       last_vote_slot_p67 = votes_sorted[ i ].last_vote_slot;
     757           0 :     }
     758           0 :   }
     759             : 
     760             :   /* resuse scratch to for publish state */
     761           0 :   int * actions = (void *)votes_scratch;
     762           0 :   ulong * idxs = (ulong *)((uchar *)votes_scratch + FD_RUNTIME_MAX_VOTE_ACCOUNTS*sizeof(int));
     763           0 :   FD_STATIC_ASSERT( sizeof(peers->votes_scratch)>=(FD_RUNTIME_MAX_VOTE_ACCOUNTS*(sizeof(int) + sizeof(ulong))), "scratch too small" );
     764             : 
     765           0 :   ulong count = 0UL;
     766           0 :   for( ulong i=0UL; i<vote_cnt; i++ ) {
     767           0 :     if( FD_UNLIKELY( votes_sorted[ i ].stake==ULONG_MAX ) ) continue;
     768             : 
     769             :     /* votes_sorted is a copy of the vote_states bank field that has
     770             :        been sorted by stake descending and deduplicated.  Deduplicated
     771             :        here means if multiple vote accounts point to the same identity
     772             :        key, we go with the one with the most stake.  TODO: This logic
     773             :        will need to change once SIMD-0180 hits mainnet.
     774             : 
     775             :        As long as the vote account exists, it will be in vote_states,
     776             :        which get initialized at snapshot load and gets updated by the
     777             :        runtime. So, on any given fork, `last_voted_slot` should reflect
     778             :        the last landed vote for ALL the vote accounts (including those
     779             :        referencing identity->uc) from the perspective of that fork's
     780             :        bank, even if that slot didn't have landed votes for some of
     781             :        those accounts. */
     782           0 :     if( FD_UNLIKELY( !memcmp( &votes_sorted[ i ].node_account, identity->uc, sizeof(fd_pubkey_t) ) && peers->slot_voted!=votes_sorted[ i ].last_vote_slot ) ) {
     783           0 :       peers->slot_voted = fd_ulong_if( votes_sorted[ i ].last_vote_slot==0UL, ULONG_MAX, votes_sorted[ i ].last_vote_slot );
     784           0 :       fd_gui_peers_printf_vote_slot( peers );
     785           0 :       fd_http_server_ws_broadcast( peers->http );
     786           0 :     }
     787             : 
     788           0 :     ulong peer_idx = fd_gui_peers_node_pubkey_map_idx_query( peers->node_pubkey_map, &votes_sorted[ i ].node_account, ULONG_MAX, peers->contact_info_table );
     789           0 :     if( FD_UNLIKELY( peer_idx==ULONG_MAX ) ) continue; /* peer not on gossip */
     790             : 
     791           0 :     fd_gui_peers_node_t * peer = peers->contact_info_table + peer_idx;
     792             : 
     793             :     /* TODO: we only publish updates when stake changes, otherwise we'd
     794             :        have to republish for every peer every slot, which ends up being
     795             :        too much bandwidth because we republish all the peer info.
     796             :        Ideally, we decouple the vote updates from the reset of the peer
     797             :        info which would let us make updates quickly. */
     798           0 :     int is_delinquent = ((long)last_vote_slot_p67 - (long)votes_sorted[ i ].last_vote_slot) > 150L;
     799           0 :     int vote_eq = peer->has_vote_info
     800           0 :                && !memcmp( peer->vote_account.uc, votes_sorted[ i ].vote_account.uc, sizeof(fd_pubkey_t) )
     801           0 :                && peer->stake                   ==votes_sorted[ i ].stake
     802             :             // && peer->last_vote_slot          ==votes_sorted[ i ].last_vote_slot
     803             :             // && peer->last_vote_timestamp     ==votes_sorted[ i ].last_vote_timestamp
     804             :             // && peer->epoch_credits           ==votes_sorted[ i ].epoch_credits
     805           0 :                && peer->commission              ==votes_sorted[ i ].commission
     806           0 :                && peer->epoch                   ==votes_sorted[ i ].epoch
     807           0 :                && peer->delinquent              ==is_delinquent;
     808             : 
     809           0 :     if( FD_LIKELY( vote_eq ) ) continue; /* nop */
     810             : 
     811           0 :     peer->has_vote_info = 1;
     812           0 :     peer->vote_account        = votes_sorted[ i ].vote_account;
     813           0 :     peer->last_vote_slot      = votes_sorted[ i ].last_vote_slot;
     814           0 :     peer->last_vote_timestamp = votes_sorted[ i ].last_vote_timestamp;
     815           0 :     peer->epoch_credits       = votes_sorted[ i ].epoch_credits;
     816           0 :     peer->commission          = votes_sorted[ i ].commission;
     817           0 :     peer->epoch               = votes_sorted[ i ].epoch;
     818           0 :     peer->delinquent          = is_delinquent;
     819             : 
     820           0 :     if( FD_UNLIKELY( peer->stake!=votes_sorted[ i ].stake ) ) {
     821           0 :       fd_gui_peers_live_table_idx_remove( peers->live_table, peer_idx, peers->contact_info_table );
     822           0 :       peer->stake = votes_sorted[ i ].stake;
     823           0 :       fd_gui_peers_live_table_idx_insert( peers->live_table, peer_idx, peers->contact_info_table );
     824           0 :     }
     825             : 
     826           0 :     actions[ count ] = FD_GUI_PEERS_NODE_UPDATE;
     827           0 :     idxs   [ count ] = peer_idx;
     828           0 :     count++;
     829           0 :   }
     830             : 
     831           0 :   if( FD_UNLIKELY( count ) ) {
     832           0 :     fd_gui_peers_printf_nodes( peers, actions, idxs, count );
     833           0 :     fd_http_server_ws_broadcast( peers->http );
     834           0 :   }
     835           0 : }
     836             : 
     837             : void
     838             : fd_gui_peers_handle_config_account( fd_gui_peers_ctx_t *  peers,
     839             :                                     uchar const *         data,
     840           0 :                                     ulong                 sz ) {
     841             :   /* optimistically acquire node_info */
     842           0 :   if( FD_UNLIKELY( !fd_gui_peers_node_info_pool_free( peers->node_info_pool ) ) ) {
     843           0 :     FD_LOG_WARNING(( "On-chain ConfigProgram accounts count exceeded %lu", FD_CONTACT_INFO_TABLE_SIZE ));
     844           0 :     return;
     845           0 :   }
     846           0 :   fd_gui_config_parse_info_t * node_info = fd_gui_peers_node_info_pool_ele_acquire( peers->node_info_pool );
     847             : 
     848           0 :   cJSON * json;
     849           0 :   if( FD_UNLIKELY( !fd_gui_config_parse_validator_info_check( data, sz, &json, &node_info->pubkey ) ) ) {
     850           0 :     fd_gui_peers_node_info_pool_ele_release( peers->node_info_pool, node_info );
     851           0 :     return;
     852           0 :   }
     853             : 
     854           0 :   if( FD_UNLIKELY( fd_gui_peers_node_info_map_ele_query( peers->node_info_map, &node_info->pubkey, NULL, peers->node_info_pool ) ) ) {
     855           0 :     fd_gui_peers_node_info_pool_ele_release( peers->node_info_pool, node_info );
     856           0 :     cJSON_Delete( json );
     857           0 :     return; /* no duplicate entries */
     858           0 :   }
     859             : 
     860           0 :   fd_gui_config_parse_validator_info( json, node_info ); /* calls cJSON_delete( json ) */
     861           0 :   fd_gui_peers_node_info_map_ele_insert( peers->node_info_map, node_info, peers->node_info_pool );
     862           0 : }
     863             : 
     864             : 
     865             : static void
     866           0 : fd_gui_peers_viewport_snap( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
     867           0 :   FD_TEST( peers->client_viewports[ ws_conn_id ].connected );
     868           0 :   if( FD_UNLIKELY( peers->client_viewports[ ws_conn_id ].row_cnt==0UL ) ) return; /* empty viewport */
     869           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 ));
     870             : 
     871           0 :   if( FD_UNLIKELY( fd_gui_peers_live_table_active_sort_key_cnt( peers->live_table )==FD_GUI_PEERS_CI_TABLE_SORT_KEY_CNT ) ) {
     872             :     /* we're out of cached sort keys. disconnect the oldest client */
     873           0 :     ulong oldest_ws_conn_id    = ULONG_MAX;
     874           0 :     long oldest_connected_time = LONG_MAX;
     875           0 :     for( ulong i=0UL; i<peers->max_ws_conn_cnt; i++ ) {
     876           0 :       if( FD_UNLIKELY( peers->client_viewports[ i ].connected && peers->client_viewports[ i ].connected_time < oldest_connected_time ) ) {
     877           0 :         oldest_ws_conn_id = i;
     878           0 :         oldest_connected_time = peers->client_viewports[ i ].connected_time;
     879           0 :       }
     880           0 :     }
     881           0 :     FD_TEST( oldest_ws_conn_id!=ULONG_MAX );
     882           0 :     fd_gui_peers_live_table_sort_key_remove( peers->live_table, &peers->client_viewports[ oldest_ws_conn_id ].sort_key );
     883           0 :     FD_TEST( fd_gui_peers_live_table_active_sort_key_cnt( peers->live_table )==FD_GUI_PEERS_CI_TABLE_SORT_KEY_CNT-1UL );
     884           0 :   }
     885             : 
     886           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;
     887           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;
     888           0 :        iter = fd_gui_peers_live_table_fwd_iter_next( iter, peers->contact_info_table ), j++ ) {
     889           0 :     if( FD_LIKELY( j<peers->client_viewports[ ws_conn_id ].start_row ) ) continue;
     890           0 :     fd_gui_peers_node_t const * cur = fd_gui_peers_live_table_fwd_iter_ele_const( iter, peers->contact_info_table );
     891             : 
     892           0 :     ulong viewport_idx = j-peers->client_viewports[ ws_conn_id ].start_row;
     893           0 :     FD_TEST( viewport_idx<FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ );
     894           0 :     fd_gui_peers_node_t * ref = &peers->client_viewports[ ws_conn_id ].viewport[ viewport_idx ];
     895             : 
     896           0 :     fd_memcpy( ref, cur, sizeof(fd_gui_peers_node_t) );
     897           0 :   }
     898           0 : }
     899             : 
     900             : static int
     901             : fd_gui_peers_request_scroll( fd_gui_peers_ctx_t * peers,
     902             :                              ulong                ws_conn_id,
     903             :                              ulong                request_id,
     904           0 :                              cJSON const *        params ) {
     905           0 :   if( FD_UNLIKELY( !peers->client_viewports[ ws_conn_id ].connected ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     906             : 
     907           0 :   const cJSON * start_row_param = cJSON_GetObjectItemCaseSensitive( params, "start_row" );
     908           0 :   if( FD_UNLIKELY( !cJSON_IsNumber( start_row_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     909           0 :   ulong _start_row = start_row_param->valueulong;
     910             : 
     911           0 :   const cJSON * row_cnt_param = cJSON_GetObjectItemCaseSensitive( params, "row_cnt" );
     912           0 :   if( FD_UNLIKELY( !cJSON_IsNumber( row_cnt_param ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     913           0 :   ulong _row_cnt = row_cnt_param->valueulong;
     914             : 
     915           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 ) ) {
     916           0 :     fd_gui_printf_null_query_response( peers->http, "gossip", "query_scroll", request_id );
     917           0 :     FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
     918           0 :     return 0;
     919           0 :   }
     920             : 
     921           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 ) ) {
     922           0 :     return 0; /* NOP, scroll window hasn't changed */
     923           0 :   }
     924             : 
     925             :   /* update the client's viewport */
     926           0 :   peers->client_viewports[ ws_conn_id ].start_row = _start_row;
     927           0 :   peers->client_viewports[ ws_conn_id ].row_cnt   = _row_cnt;
     928             : 
     929           0 :   fd_gui_printf_peers_viewport_request( peers, "query_scroll", ws_conn_id, request_id );
     930           0 :   FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
     931           0 :   return 0;
     932           0 : }
     933             : 
     934             : static int
     935             : fd_gui_peers_request_sort( fd_gui_peers_ctx_t * peers,
     936             :                            ulong                ws_conn_id,
     937             :                            ulong                request_id,
     938           0 :                            cJSON const *        params ) {
     939           0 :   if( FD_UNLIKELY( !peers->client_viewports[ ws_conn_id ].connected ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     940             : 
     941           0 :   const cJSON * _col = cJSON_GetObjectItemCaseSensitive( params, "col" );
     942           0 :   if( FD_UNLIKELY( !cJSON_IsArray( _col ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     943             : 
     944           0 :   fd_gui_peers_live_table_sort_key_t sort_key;
     945             : 
     946           0 :   do {
     947           0 :     cJSON * c;
     948           0 :     ulong i;
     949           0 :     for( c = _col->child, i=0UL; c; c = c->next, i++ ) {
     950           0 :       if( FD_UNLIKELY( i >= fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     951           0 :       sort_key.col[ i ] = fd_gui_peers_live_table_col_name_to_idx( peers->live_table, c->valuestring );
     952           0 :       if( FD_UNLIKELY( sort_key.col[ i ]==ULONG_MAX ) ) {
     953           0 :         FD_LOG_WARNING(( "unexpected column name %s", c->valuestring ));
     954           0 :         return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     955           0 :       }
     956           0 :     }
     957           0 :   } while( 0 );
     958             : 
     959           0 :   const cJSON * _dir = cJSON_GetObjectItemCaseSensitive( params, "dir" );
     960           0 :   if( FD_UNLIKELY( !cJSON_IsArray( _dir ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     961             : 
     962           0 :   do {
     963           0 :     cJSON * c;
     964           0 :     ulong i;
     965           0 :     for( c = _dir->child, i=0UL; c; c = c->next, i++ ) {
     966           0 :       if( FD_UNLIKELY( i >= fd_gui_peers_live_table_col_cnt() ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     967           0 :       sort_key.dir[ i ] = c->valueint;
     968           0 :     }
     969           0 :   } while( 0 );
     970             : 
     971           0 :   if( FD_UNLIKELY( !fd_gui_peers_live_table_verify_sort_key( &sort_key ) ) ) return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     972             : 
     973           0 :   fd_memcpy( &peers->client_viewports[ ws_conn_id ].sort_key, &sort_key, sizeof(fd_gui_peers_live_table_sort_key_t) );
     974             : 
     975           0 :   fd_gui_printf_peers_viewport_request( peers, "query_sort", ws_conn_id, request_id );
     976           0 :   FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
     977           0 :   return 0;
     978           0 : }
     979             : 
     980             : int
     981             : fd_gui_peers_ws_message( fd_gui_peers_ctx_t * peers,
     982             :                          ulong                ws_conn_id,
     983             :                          uchar const *        data,
     984           0 :                          ulong                data_len ) {
     985             :   /* TODO: cJSON allocates, might fail SIGSYS due to brk(2)...
     986             :      switch off this (or use wksp allocator) */
     987           0 :   const char * parse_end;
     988           0 :   cJSON * json = cJSON_ParseWithLengthOpts( (char *)data, data_len, &parse_end, 0 );
     989           0 :   if( FD_UNLIKELY( !json ) ) {
     990           0 :     return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     991           0 :   }
     992             : 
     993           0 :   const cJSON * node = cJSON_GetObjectItemCaseSensitive( json, "id" );
     994           0 :   if( FD_UNLIKELY( !cJSON_IsNumber( node ) ) ) {
     995           0 :     cJSON_Delete( json );
     996           0 :     return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
     997           0 :   }
     998           0 :   ulong id = node->valueulong;
     999             : 
    1000           0 :   const cJSON * topic = cJSON_GetObjectItemCaseSensitive( json, "topic" );
    1001           0 :   if( FD_UNLIKELY( !cJSON_IsString( topic ) || topic->valuestring==NULL ) ) {
    1002           0 :     cJSON_Delete( json );
    1003           0 :     return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
    1004           0 :   }
    1005             : 
    1006           0 :   const cJSON * key = cJSON_GetObjectItemCaseSensitive( json, "key" );
    1007           0 :   if( FD_UNLIKELY( !cJSON_IsString( key ) || key->valuestring==NULL ) ) {
    1008           0 :     cJSON_Delete( json );
    1009           0 :     return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
    1010           0 :   }
    1011             : 
    1012           0 :   if( FD_LIKELY( !strcmp( topic->valuestring, "gossip" ) && !strcmp( key->valuestring, "query_sort" ) ) ) {
    1013           0 :     const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
    1014           0 :     if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
    1015           0 :       cJSON_Delete( json );
    1016           0 :       return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
    1017           0 :     }
    1018             : 
    1019           0 :     int result = fd_gui_peers_request_sort( peers, ws_conn_id, id, params );
    1020           0 :     cJSON_Delete( json );
    1021           0 :     return result;
    1022           0 :   } else if( FD_LIKELY( !strcmp( topic->valuestring, "gossip" ) && !strcmp( key->valuestring, "query_scroll" ) ) ) {
    1023           0 :     const cJSON * params = cJSON_GetObjectItemCaseSensitive( json, "params" );
    1024           0 :     if( FD_UNLIKELY( !cJSON_IsObject( params ) ) ) {
    1025           0 :       cJSON_Delete( json );
    1026           0 :       return FD_HTTP_SERVER_CONNECTION_CLOSE_BAD_REQUEST;
    1027           0 :     }
    1028             : 
    1029           0 :     int result = fd_gui_peers_request_scroll( peers, ws_conn_id, id, params );
    1030           0 :     cJSON_Delete( json );
    1031           0 :     return result;
    1032           0 :   }
    1033             : 
    1034           0 :   cJSON_Delete( json );
    1035           0 :   return FD_HTTP_SERVER_CONNECTION_CLOSE_UNKNOWN_METHOD;
    1036           0 : }
    1037             : 
    1038             : static void
    1039             : fd_gui_peers_viewport_log( fd_gui_peers_ctx_t *  peers,
    1040           0 :                            ulong                 ws_conn_id) {
    1041             : 
    1042           0 :   FD_TEST( peers->client_viewports[ ws_conn_id ].row_cnt<=FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ );
    1043             : 
    1044           0 :   char out[ 1<<14 ];
    1045           0 :   char * p = fd_cstr_init( out );
    1046             : 
    1047           0 :   p = fd_cstr_append_printf( p,
    1048           0 :     "\n[Viewport] table_size=%lu max_viewport_size=%lu\n"
    1049           0 :     "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+\n"
    1050           0 :     "| Row # | RX Push (bps)  | RX Pull (bps)  | TX Push (bps)  | TX Pull (bps)  | Pubkey                                             | IP Address      |\n"
    1051           0 :     "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+\n",
    1052           0 :     fd_gui_peers_live_table_ele_cnt( peers->live_table ), peers->client_viewports[ ws_conn_id ].row_cnt );
    1053             : 
    1054           0 :   FD_TEST( peers->client_viewports[ ws_conn_id ].connected );
    1055           0 :   for( fd_gui_peers_live_table_fwd_iter_t iter = fd_gui_peers_live_table_fwd_iter_init( peers->live_table, &peers->client_viewports[ ws_conn_id ].sort_key, peers->contact_info_table ), j = 0UL;
    1056           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;
    1057           0 :        iter = fd_gui_peers_live_table_fwd_iter_next(iter, peers->contact_info_table), j++ ) {
    1058           0 :     if( FD_LIKELY( j < peers->client_viewports[ ws_conn_id ].start_row ) ) continue;
    1059             : 
    1060           0 :     fd_gui_peers_node_t const * cur = fd_gui_peers_live_table_fwd_iter_ele_const( iter, peers->contact_info_table );
    1061             : 
    1062           0 :     char pubkey_base58[ FD_BASE58_ENCODED_32_SZ ];
    1063           0 :     fd_base58_encode_32( cur->contact_info.pubkey.uc, NULL, pubkey_base58 );
    1064             : 
    1065           0 :     char peer_addr[ 16 ]; /* 255.255.255.255 + '\0' */
    1066           0 :     FD_TEST(fd_cstr_printf_check( peer_addr, sizeof(peer_addr), NULL, FD_IP4_ADDR_FMT,
    1067           0 :                                   FD_IP4_ADDR_FMT_ARGS( cur->contact_info.sockets[FD_CONTACT_INFO_SOCKET_GOSSIP].addr ) ) );
    1068             : 
    1069           0 :     long cur_egress_push_bps           = cur->gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate_ema;
    1070           0 :     long cur_ingress_push_bps          = cur->gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PUSH_IDX ].rate_ema;
    1071           0 :     long cur_egress_pull_response_bps  = cur->gossip_tx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate_ema;
    1072           0 :     long cur_ingress_pull_response_bps = cur->gossvf_rx[ FD_METRICS_ENUM_GOSSIP_MESSAGE_V_PULL_RESPONSE_IDX ].rate_ema;
    1073             : 
    1074           0 :     p = fd_cstr_append_printf( p,
    1075           0 :                                "| %5lu | %14ld | %14ld | %14ld | %14ld | %-50s | %-15s |\n",
    1076           0 :                                peers->client_viewports[ ws_conn_id ].start_row + j,
    1077           0 :                                cur_ingress_push_bps,
    1078           0 :                                cur_ingress_pull_response_bps,
    1079           0 :                                cur_egress_push_bps,
    1080           0 :                                cur_egress_pull_response_bps,
    1081           0 :                                pubkey_base58,
    1082           0 :                                peer_addr );
    1083           0 :   }
    1084           0 :   p = fd_cstr_append_printf(p, "+-------+----------------+----------------+----------------+----------------+----------------------------------------------------+-----------------+" );
    1085           0 :   fd_cstr_fini( p );
    1086           0 :   FD_LOG_NOTICE(( "%s", out ));
    1087           0 : }
    1088             : 
    1089             : static void
    1090           0 : fd_gui_peers_ws_conn_rr_grow( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
    1091           0 :   if( FD_UNLIKELY( !peers->open_ws_conn_cnt ) ) peers->active_ws_conn_id = ws_conn_id;
    1092           0 :   peers->open_ws_conn_cnt++;
    1093           0 : }
    1094             : 
    1095             : static void
    1096           0 : fd_gui_peers_ws_conn_rr_shrink( fd_gui_peers_ctx_t * peers, ulong ws_conn_id ) {
    1097           0 :   peers->open_ws_conn_cnt--;
    1098             : 
    1099           0 :   if( FD_UNLIKELY( peers->open_ws_conn_cnt && peers->active_ws_conn_id==ws_conn_id ) ) {
    1100           0 :     for( ulong i=1UL; i<peers->max_ws_conn_cnt+1UL; i++ ) {
    1101           0 :       ulong next_ws_conn_id = (ws_conn_id + i) % peers->max_ws_conn_cnt;
    1102           0 :       if( FD_UNLIKELY( peers->client_viewports[ next_ws_conn_id ].connected ) ) {
    1103           0 :         peers->active_ws_conn_id = next_ws_conn_id;
    1104           0 :         break;
    1105           0 :       }
    1106           0 :     }
    1107           0 :   }
    1108           0 : }
    1109             : 
    1110             : static int
    1111           0 : fd_gui_peers_ws_conn_rr_advance( fd_gui_peers_ctx_t * peers, long now ) {
    1112           0 :   if( FD_LIKELY( !peers->open_ws_conn_cnt || now <= peers->next_client_nanos ) ) return 0;
    1113             : 
    1114           0 :   for( ulong i=1UL; i<peers->max_ws_conn_cnt+1UL; i++ ) {
    1115           0 :     ulong next_ws_conn_id = (peers->active_ws_conn_id + i) % peers->max_ws_conn_cnt;
    1116           0 :     if( FD_UNLIKELY( peers->client_viewports[ next_ws_conn_id ].connected ) ) {
    1117           0 :       peers->active_ws_conn_id = next_ws_conn_id;
    1118           0 :       break;
    1119           0 :     }
    1120           0 :   }
    1121           0 :   return 1;
    1122           0 : }
    1123             : 
    1124             : int
    1125           0 : fd_gui_peers_poll( fd_gui_peers_ctx_t * peers, long now ) {
    1126           0 :   int did_work = 0;
    1127             : 
    1128             :   /* update client viewports in a round-robin */
    1129           0 :   if( FD_UNLIKELY( fd_gui_peers_ws_conn_rr_advance( peers, now ) ) ) {
    1130           0 :     FD_TEST( peers->client_viewports[ peers->active_ws_conn_id ].connected );
    1131           0 :     if( FD_LIKELY( peers->client_viewports[ peers->active_ws_conn_id ].row_cnt ) ) {
    1132             :       /* broadcast the diff as cell updates */
    1133           0 :       fd_gui_printf_peers_viewport_update( peers, peers->active_ws_conn_id );
    1134             : 
    1135             : #if LOGGING
    1136             :       /* log the diff */
    1137             :       fd_gui_peers_viewport_log( peers, peers->active_ws_conn_id );
    1138             : #endif
    1139           0 :       (void)fd_gui_peers_viewport_log;
    1140             : 
    1141             :       /* update client state to the latest viewport */
    1142           0 :       fd_gui_peers_viewport_snap( peers, peers->active_ws_conn_id );
    1143             : 
    1144             :       /* In rare cases, fd_http_server_ws_send can close the websocket
    1145             :       connection. Since fd_gui_peers_viewport_snap assumes the connected
    1146             :       peer has not disconnected, we call it before. */
    1147           0 :       FD_TEST( !fd_http_server_ws_send( peers->http, peers->active_ws_conn_id ) );
    1148           0 :     }
    1149             : 
    1150           0 :     peers->next_client_nanos = now + ((FD_GUI_PEERS_WS_VIEWPORT_UPDATE_INTERVAL_MILLIS * 1000000L) / (long)peers->open_ws_conn_cnt);
    1151           0 :     did_work = 1;
    1152           0 :   }
    1153             : 
    1154           0 :   if( FD_UNLIKELY( now >= peers->next_metric_rate_update_nanos ) ) {
    1155           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 );
    1156           0 :          !fd_gui_peers_node_pubkey_map_iter_done( iter, peers->node_pubkey_map, peers->contact_info_table );
    1157           0 :          iter = fd_gui_peers_node_pubkey_map_iter_next( iter, peers->node_pubkey_map, peers->contact_info_table ) ) {
    1158           0 :       fd_gui_peers_node_t * peer = fd_gui_peers_node_pubkey_map_iter_ele( iter, peers->node_pubkey_map, peers->contact_info_table );
    1159             : 
    1160           0 :       double window = (double)(now - (peers->next_metric_rate_update_nanos - (FD_GUI_PEERS_METRIC_RATE_UPDATE_INTERVAL_MILLIS * 1000000L)));
    1161             : 
    1162             :       /* optimization: no need to remove / re-insert if the rates haven't changed */
    1163           0 :       int change = 0;
    1164           0 :       for( ulong i=0UL; !change && i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
    1165           0 :         fd_gui_peers_metric_rate_t * metric = &peer->gossvf_rx[ i ];
    1166           0 :         long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
    1167           0 :         long new_rate_ema = fd_gui_peers_adaptive_ema( metric->update_timestamp_ns, now, (long)new_rate, (long)metric->rate_ema );
    1168           0 :         if( FD_LIKELY( new_rate_ema==0L && metric->rate_ema==0L ) ) continue; /* don't update zero-bandwith peers */
    1169           0 :         change = 1;
    1170           0 :       }
    1171             : 
    1172           0 :       for( ulong i=0UL; !change && i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
    1173           0 :         fd_gui_peers_metric_rate_t * metric = &peer->gossip_tx[ i ];
    1174           0 :         long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
    1175           0 :         long new_rate_ema = fd_gui_peers_adaptive_ema( metric->update_timestamp_ns, now, (long)new_rate, (long)metric->rate_ema );
    1176           0 :         if( FD_LIKELY( new_rate_ema==0L && metric->rate_ema==0L ) ) continue; /* don't update zero-bandwith peers */
    1177           0 :         change = 1;
    1178           0 :       }
    1179             : 
    1180           0 :       if( !change ) continue;
    1181             : 
    1182             :       /* live_table */
    1183           0 :       fd_gui_peers_live_table_ele_remove( peers->live_table, peer, peers->contact_info_table );
    1184           0 :       for( ulong i=0UL; i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
    1185           0 :         fd_gui_peers_metric_rate_t * metric = &peer->gossvf_rx[ i ];
    1186           0 :         long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
    1187           0 :         long new_rate_ema = fd_gui_peers_adaptive_ema( metric->update_timestamp_ns, now, (long)new_rate, (long)metric->rate_ema );
    1188           0 :         metric->rate_ema  = fd_long_if( new_rate_ema<100L, 0L, new_rate_ema ); /* snap near-zero ema to zero. 100 bytes/s threshold */
    1189           0 :         metric->ref       = metric->cur;
    1190           0 :         metric->update_timestamp_ns = now;
    1191           0 :       }
    1192             : 
    1193           0 :       for( ulong i=0UL; i<FD_METRICS_ENUM_GOSSIP_MESSAGE_CNT; i++ ) {
    1194           0 :         fd_gui_peers_metric_rate_t * metric = &peer->gossip_tx[ i ];
    1195           0 :         long new_rate = (long)(((double)((long)metric->cur - (long)metric->ref) * 1e9 / window));
    1196           0 :         long new_rate_ema = fd_gui_peers_adaptive_ema( metric->update_timestamp_ns, now, new_rate, metric->rate_ema );
    1197           0 :         metric->rate_ema  = fd_long_if( new_rate_ema<100L, 0L, new_rate_ema ); /* snap near-zero ema to zero. 100 bytes/s threshold */
    1198           0 :         metric->ref       = metric->cur;
    1199           0 :         metric->update_timestamp_ns = now;
    1200           0 :       }
    1201           0 :       fd_gui_peers_live_table_ele_insert( peers->live_table, peer, peers->contact_info_table );
    1202             : 
    1203             :       /* bandwidth_tracking */
    1204           0 :       fd_gui_peers_bandwidth_tracking_ele_remove( peers->bw_tracking, peer, peers->contact_info_table );
    1205           0 :       peer->gossvf_rx_sum.rate_ema = fd_gui_peers_adaptive_ema( peer->gossvf_rx_sum.update_timestamp_ns, now, (long)(((double)((long)peer->gossvf_rx_sum.cur - (long)peer->gossvf_rx_sum.ref) * 1e9 / window)), peer->gossvf_rx_sum.rate_ema );
    1206           0 :       peer->gossvf_rx_sum.ref      = peer->gossvf_rx_sum.cur;
    1207           0 :       peer->gossvf_rx_sum.update_timestamp_ns = now;
    1208             : 
    1209           0 :       peer->gossip_tx_sum.rate_ema = fd_gui_peers_adaptive_ema( peer->gossip_tx_sum.update_timestamp_ns, now, (long)(((double)((long)peer->gossip_tx_sum.cur - (long)peer->gossip_tx_sum.ref) * 1e9 / window)), peer->gossip_tx_sum.rate_ema );
    1210           0 :       peer->gossip_tx_sum.ref      = peer->gossip_tx_sum.cur;
    1211           0 :       peer->gossip_tx_sum.update_timestamp_ns = now;
    1212           0 :       fd_gui_peers_bandwidth_tracking_ele_insert( peers->bw_tracking, peer, peers->contact_info_table );
    1213           0 :     }
    1214             : 
    1215           0 :     peers->next_metric_rate_update_nanos = now + (FD_GUI_PEERS_METRIC_RATE_UPDATE_INTERVAL_MILLIS * 1000000L);
    1216           0 :     did_work = 1;
    1217             : #ifdef FD_GUI_USE_HANDHOLDING
    1218             :     fd_gui_peers_live_table_verify( peers->live_table, peers->contact_info_table );
    1219             : #endif
    1220           0 :   }
    1221             : 
    1222           0 :   if( FD_LIKELY( now >= peers->next_gossip_stats_update_nanos ) ) {
    1223           0 :     fd_gui_peers_gossip_stats_snap( peers, peers->gossip_stats, now );
    1224           0 :     fd_gui_peers_printf_gossip_stats( peers );
    1225           0 :     fd_http_server_ws_broadcast( peers->http );
    1226             : 
    1227           0 :     peers->next_gossip_stats_update_nanos = now + (FD_GUI_PEERS_GOSSIP_STATS_UPDATE_INTERVAL_MILLIS * 1000000L);
    1228           0 :     did_work = 1;
    1229           0 :   }
    1230             : 
    1231           0 :   return did_work;
    1232           0 : }
    1233             : 
    1234             : void
    1235             : fd_gui_peers_ws_open( fd_gui_peers_ctx_t *  peers,
    1236             :                       ulong                 ws_conn_id,
    1237           0 :                       long                  now ) {
    1238           0 :   peers->client_viewports[ ws_conn_id ].connected = 1;
    1239           0 :   peers->client_viewports[ ws_conn_id ].connected_time = now;
    1240           0 :   peers->client_viewports[ ws_conn_id ].start_row = 0;
    1241           0 :   peers->client_viewports[ ws_conn_id ].row_cnt = 0;
    1242           0 :   peers->client_viewports[ ws_conn_id ].sort_key = FD_GUI_PEERS_LIVE_TABLE_DEFAULT_SORT_KEY;
    1243           0 :   fd_gui_peers_ws_conn_rr_grow( peers, ws_conn_id );
    1244             : 
    1245           0 :   fd_gui_peers_printf_node_all( peers );
    1246           0 :   FD_TEST( !fd_http_server_ws_send( peers->http, ws_conn_id ) );
    1247           0 : }
    1248             : 
    1249             : void
    1250             : fd_gui_peers_ws_close( fd_gui_peers_ctx_t * peers,
    1251           0 :                        ulong                ws_conn_id ) {
    1252           0 :   fd_gui_peers_live_table_sort_key_remove( peers->live_table, &peers->client_viewports[ ws_conn_id ].sort_key );
    1253           0 :   peers->client_viewports[ ws_conn_id ].connected = 0;
    1254           0 :   fd_gui_peers_ws_conn_rr_shrink( peers, ws_conn_id );
    1255           0 : }
    1256             : 
    1257             : #undef LOGGING

Generated by: LCOV version 1.14