LCOV - code coverage report
Current view: top level - flamenco/gossip - fd_crds.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 598 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 30 0.0 %

          Line data    Source code
       1             : #include "fd_crds.h"
       2             : 
       3             : #include "fd_active_set.h"
       4             : #include "fd_gossip_hset.h"
       5             : #include "../../ballet/sha256/fd_sha256.h"
       6             : #include "../../util/fd_hash32.h" /* for fd_hash32, which we use for CRDS eviction */
       7             : 
       8             : #include <string.h>
       9             : 
      10             : struct fd_crds_contact_info_entry {
      11             :   fd_gossip_contact_info_t contact_info[1];
      12             :   long                     received_wallclock_nanos;
      13             :   uchar                    is_me;
      14             :   uchar                    is_entrypoint;
      15             : 
      16             :   fd_crds_entry_t *        crds_entry; /* Back-pointer to CRDS pool entry */
      17             : 
      18             :   /* A list of "fresh" contact info entries is maintained, holding
      19             :      entries that have been refreshed/inserted in the last 60s in
      20             :      upsertion order (oldest first).
      21             : 
      22             :      fd_crds_advance periodically checks for and removes peers from
      23             :      this list if they exceed the threshold. Peers removed in this
      24             :      loop are also re-scored in the peer sampler. This is different
      25             :      from dropping the CRDS entry entirely, which also removes the
      26             :      entry from this list. To avoid double-popping an entry we use
      27             :      in_list as a presence check prior to removing */
      28             :   struct {
      29             :     ulong prev;
      30             :     ulong next;
      31             :     uchar in_list; /* 1 if in the fresh list, 0 otherwise */
      32             :   } fresh_dlist;
      33             : 
      34             :   /* Similar to fresh_dlist, but with a 15s timeout instead.
      35             :      Additionally, fresh_dlist explicitly excludes our own contact info
      36             :      while fresh_15s_dlist includes it. */
      37             :   struct {
      38             :     ulong prev;
      39             :     ulong next;
      40             :     uchar in_list; /* 1 if in the fresh list, 0 otherwise */
      41             :   } fresh_15s_dlist;
      42             : 
      43             :   /* The contact info side table has a separate size limit, so
      44             :      we maintain a separate evict treap sorted by
      45             :      (stake ASC, wallclock ASC) to evict the lowest-stake
      46             :      (and oldest among equally-staked) contact info first. */
      47             :   struct {
      48             :     ulong parent;
      49             :     ulong left;
      50             :     ulong right;
      51             :     ulong prio;
      52             :     ulong next;
      53             :     ulong prev;
      54             :   } ci_evict_treap;
      55             : 
      56             :   struct {
      57             :     ulong next;
      58             :   } pool;
      59             : };
      60             : 
      61             : typedef struct fd_crds_contact_info_entry fd_crds_contact_info_entry_t;
      62             : 
      63             : #define POOL_NAME  crds_contact_info_pool
      64           0 : #define POOL_T     fd_crds_contact_info_entry_t
      65           0 : #define POOL_NEXT  pool.next
      66             : #include "../../util/tmpl/fd_pool.c"
      67             : 
      68             : struct fd_crds_key {
      69             :   uchar tag;
      70             :   uchar pubkey[ 32UL ];
      71             :   union {
      72             :     uchar  vote_index;
      73             :     uchar  epoch_slots_index;
      74             :     ushort duplicate_shred_index;
      75             :   };
      76             : };
      77             : 
      78             : typedef struct fd_crds_key fd_crds_key_t;
      79             : 
      80             : /* The CRDS at a high level is just a list of all the messages we have
      81             :    received over gossip.  These are called the CRDS values.  Values
      82             :    are not arbitrary, and must conform to a strictly typed schema of
      83             :    around 10 different messages. */
      84             : 
      85             : struct fd_crds_entry_private {
      86             :   /* The core operation of the CRDS is to "upsert" a value.  Basically,
      87             :      all of the message types are keyed by the originators public key,
      88             :      and we only want to store the most recent message of each type.
      89             : 
      90             :     This key field is the key for the hash table. */
      91             :   fd_crds_key_t key;
      92             : 
      93             :   union {
      94             :     fd_crds_contact_info_entry_t * ci;
      95             :     ulong node_instance_token;
      96             :   };
      97             : 
      98             :   /* When an originator creates a CRDS message, they attach their local
      99             :      wallclock time to it.  This time is used to determine when a
     100             :      message should be upserted.  If messages have the same key, the
     101             :      newer one (as created by the originator) is used. */
     102             :   ulong wallclock;
     103             : 
     104             :   ushort value_sz;
     105             :   uchar  value_bytes[ FD_GOSSIP_VALUE_MAX_SZ ];
     106             :   uchar  value_hash[ 32UL ];
     107             : 
     108             :   ulong num_push_recv;
     109             :   ulong stake;
     110             : 
     111             :   struct {
     112             :     uint next;
     113             :   } pool;
     114             : 
     115             :   /* The CRDS needs to perform a variety of actions on the message table
     116             :      quickly, so there are various indexes woven through them values to
     117             :      support these actions.  They are ...
     118             : 
     119             :      lookup is used to enable the core map<key, value> functionality
     120             :      described for upserts defined by value->key. */
     121             :   struct {
     122             :     uint next;
     123             :     uint prev;
     124             :   } lookup;
     125             : 
     126             :   /* The table has a fixed size message capacity, and supports eviction
     127             :      so insertion never fails.  If the table is full and we wish to
     128             :      insert a new value, the "lowest priority" message is evicted to
     129             :      make room.  This is accomplished with a treap sorted by stake, so
     130             :      the lowest stake message is removed. */
     131             :   struct {
     132             :     uint parent;
     133             :     uint left;
     134             :     uint right;
     135             :     uint prio;
     136             :     uint next;
     137             :     uint prev;
     138             :   } evict;
     139             : 
     140             :   /* Values in the table expire after a pre-determined amount of time,
     141             :      so we also keep a linked list of values sorted by creation time.
     142             :      The time used here is our nodes wallclock when we received the
     143             :      CRDS, not the originators local wallclock, which they could skew
     144             :      to cause their values to live longer.
     145             : 
     146             :      There are actually two lists that reuse the same pointers here,
     147             :      and a value will be in exactly one of the lists.  One is for staked
     148             :      nodes, which values expire after 48 hours, and one is for unstaked
     149             :      nodes, which expire after 15 seconds (or also 48hours if the node
     150             :      is configured as unstaked). */
     151             :   struct {
     152             :     long  wallclock_nanos;
     153             :     uint  prev;
     154             :     uint  next;
     155             :   } expire;
     156             : 
     157             :   /* Relays of one key are coalesced to one per FD_CRDS_RELAY_COALESCE_NANOS:
     158             :      a version arriving sooner waits in relay_treap, ordered by due time,
     159             :      and only the latest one is pushed.  nanos is the last relay time, or
     160             :      the due time while pending. */
     161             :   struct {
     162             :     long nanos;
     163             :     uint parent;
     164             :     uint left;
     165             :     uint right;
     166             :     uint prio;
     167             :     int  pending;
     168             :   } relay;
     169             : };
     170             : 
     171             : FD_STATIC_ASSERT( sizeof(fd_crds_entry_t)==1384UL, crds_entry_footprint );
     172             : 
     173           0 : #define FD_CRDS_RELAY_COALESCE_NANOS (100L*1000L*1000L)
     174             : 
     175             : #define POOL_NAME   crds_pool
     176           0 : #define POOL_T      fd_crds_entry_t
     177             : #define POOL_IDX_T  uint
     178           0 : #define POOL_NEXT   pool.next
     179             : 
     180             : #include "../../util/tmpl/fd_pool.c"
     181             : 
     182             : #define TREAP_NAME      evict_treap
     183             : #define TREAP_T         fd_crds_entry_t
     184             : #define TREAP_QUERY_T   void *                                         /* We don't use query ... */
     185             : #define TREAP_CMP(q,e)  (__extension__({ (void)(q); (void)(e); -1; })) /* which means we don't need to give a real
     186             :                                                                           implementation to cmp either */
     187           0 : #define TREAP_IDX_T     uint
     188           0 : #define TREAP_LT(e0,e1) ((e0)->stake<(e1)->stake)
     189           0 : #define TREAP_PARENT    evict.parent
     190           0 : #define TREAP_LEFT      evict.left
     191           0 : #define TREAP_RIGHT     evict.right
     192           0 : #define TREAP_PRIO      evict.prio
     193             : #define TREAP_OPTIMIZE_ITERATION 1
     194           0 : #define TREAP_NEXT      evict.next
     195           0 : #define TREAP_PREV      evict.prev
     196             : 
     197             : #include "../../util/tmpl/fd_treap.c"
     198             : 
     199             : /* staked_expire_dlist tracks contact info crds entries inserted in the
     200             :    last 432000L*SLOT_DURATION_NANOS nanoseconds with nonzero active
     201             :    stake according to their epoch stake at the time they are inserted. */
     202             : #define DLIST_NAME      staked_expire_dlist
     203             : #define DLIST_ELE_T     fd_crds_entry_t
     204             : #define DLIST_IDX_T     uint
     205           0 : #define DLIST_PREV      expire.prev
     206           0 : #define DLIST_NEXT      expire.next
     207             : 
     208             : #include "../../util/tmpl/fd_dlist.c"
     209             : 
     210             : /* unstaked_expire_dlist tracks contact info crds entries from the last
     211             :    432000L*SLOT_DURATION_NANOS nanoseconds (or from the last 15 seconds,
     212             :    if this node is itself running as unstaked) with zero active stake
     213             :    according to their epoch stake at the time they are inserted. */
     214             : #define DLIST_NAME      unstaked_expire_dlist
     215             : #define DLIST_ELE_T     fd_crds_entry_t
     216             : #define DLIST_IDX_T     uint
     217           0 : #define DLIST_PREV      expire.prev
     218           0 : #define DLIST_NEXT      expire.next
     219             : 
     220             : #include "../../util/tmpl/fd_dlist.c"
     221             : 
     222             : #define TREAP_NAME      relay_treap
     223             : #define TREAP_T         fd_crds_entry_t
     224             : #define TREAP_QUERY_T   void *
     225             : #define TREAP_CMP(q,e)  (__extension__({ (void)(q); (void)(e); -1; }))
     226           0 : #define TREAP_IDX_T     uint
     227           0 : #define TREAP_LT(e0,e1) ((e0)->relay.nanos<(e1)->relay.nanos)
     228           0 : #define TREAP_PARENT    relay.parent
     229           0 : #define TREAP_LEFT      relay.left
     230           0 : #define TREAP_RIGHT     relay.right
     231           0 : #define TREAP_PRIO      relay.prio
     232             : 
     233             : #include "../../util/tmpl/fd_treap.c"
     234             : 
     235             : /* fresh_15s_dlist tracks all contact info crds entries from the last
     236             :    15 seconds. */
     237             : #define DLIST_NAME      ci_fresh_15s_dlist
     238             : #define DLIST_ELE_T     fd_crds_contact_info_entry_t
     239           0 : #define DLIST_PREV      fresh_15s_dlist.prev
     240           0 : #define DLIST_NEXT      fresh_15s_dlist.next
     241             : #include "../../util/tmpl/fd_dlist.c"
     242             : 
     243             : /* crds_contact_info_fresh_list tracks all contact info crds entries
     244             :    from the last 60 seconds. */
     245             : #define DLIST_NAME  crds_contact_info_fresh_list
     246             : #define DLIST_ELE_T fd_crds_contact_info_entry_t
     247           0 : #define DLIST_PREV  fresh_dlist.prev
     248           0 : #define DLIST_NEXT  fresh_dlist.next
     249             : #include "../../util/tmpl/fd_dlist.c"
     250             : 
     251             : #define TREAP_NAME      ci_evict_treap
     252             : #define TREAP_T         fd_crds_contact_info_entry_t
     253             : #define TREAP_QUERY_T   void *
     254             : #define TREAP_CMP(q,e)  (__extension__({ (void)(q); (void)(e); -1; }))
     255           0 : #define TREAP_IDX_T     ulong
     256             : 
     257             : #if FD_DCHECK_STYLE>0
     258             : #define TREAP_LT(a,b)   (__extension__({ \
     259             :   FD_TEST( (a)->crds_entry ); \
     260             :   FD_TEST( (b)->crds_entry ); \
     261             :   ((a)->crds_entry->stake<(b)->crds_entry->stake) | (((a)->crds_entry->stake==(b)->crds_entry->stake) & ((a)->crds_entry->expire.wallclock_nanos<(b)->crds_entry->expire.wallclock_nanos)); \
     262             : }))
     263             : #else
     264           0 : #define TREAP_LT(a,b) ((a)->crds_entry->stake<(b)->crds_entry->stake) | (((a)->crds_entry->stake==(b)->crds_entry->stake) & ((a)->crds_entry->expire.wallclock_nanos<(b)->crds_entry->expire.wallclock_nanos))
     265             : #endif
     266             : 
     267           0 : #define TREAP_PARENT    ci_evict_treap.parent
     268           0 : #define TREAP_LEFT      ci_evict_treap.left
     269           0 : #define TREAP_RIGHT     ci_evict_treap.right
     270           0 : #define TREAP_PRIO      ci_evict_treap.prio
     271             : #define TREAP_OPTIMIZE_ITERATION 1
     272           0 : #define TREAP_NEXT      ci_evict_treap.next
     273           0 : #define TREAP_PREV      ci_evict_treap.prev
     274             : #include "../../util/tmpl/fd_treap.c"
     275             : 
     276             : static inline ulong
     277             : lookup_hash( fd_crds_key_t const * key,
     278           0 :              ulong                 seed ) {
     279           0 :   ulong hash_fn = ((ulong)key->tag)<<16;
     280           0 :   switch( key->tag ) {
     281           0 :   case FD_GOSSIP_VALUE_VOTE:
     282           0 :     hash_fn ^= key->vote_index;
     283           0 :     break;
     284           0 :   case FD_GOSSIP_VALUE_EPOCH_SLOTS:
     285           0 :     hash_fn ^= key->epoch_slots_index;
     286           0 :     break;
     287           0 :   case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
     288           0 :     hash_fn ^= key->duplicate_shred_index;
     289           0 :     break;
     290           0 :   default:
     291           0 :     break;
     292           0 :   }
     293           0 :   return fd_hash32( key->pubkey, seed^hash_fn );
     294           0 : }
     295             : 
     296             : static inline int
     297             : lookup_eq( fd_crds_key_t const * key0,
     298           0 :            fd_crds_key_t const * key1 ) {
     299           0 :   if( FD_UNLIKELY( key0->tag!=key1->tag ) ) return 0;
     300           0 :   if( FD_UNLIKELY( !!memcmp( key0->pubkey, key1->pubkey, 32UL ) ) ) return 0;
     301           0 :   switch( key0->tag ) {
     302           0 :     case FD_GOSSIP_VALUE_VOTE:
     303           0 :       return key0->vote_index==key1->vote_index;
     304           0 :     case FD_GOSSIP_VALUE_EPOCH_SLOTS:
     305           0 :       return key0->epoch_slots_index==key1->epoch_slots_index;
     306           0 :     case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
     307           0 :       return key0->duplicate_shred_index==key1->duplicate_shred_index;
     308           0 :     default:
     309           0 :       break;
     310           0 :   }
     311           0 :   return 1;
     312           0 : }
     313             : 
     314             : #define MAP_NAME          lookup_map
     315             : #define MAP_ELE_T         fd_crds_entry_t
     316             : #define MAP_KEY_T         fd_crds_key_t
     317           0 : #define MAP_KEY           key
     318           0 : #define MAP_IDX_T         uint
     319           0 : #define MAP_NEXT          lookup.next
     320           0 : #define MAP_PREV          lookup.prev
     321           0 : #define MAP_KEY_HASH(k,s) (lookup_hash( k, s ))
     322           0 : #define MAP_KEY_EQ(k0,k1) (lookup_eq( k0, k1 ))
     323             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     324             : 
     325             : #include "../../util/tmpl/fd_map_chain.c"
     326             : 
     327             : struct fd_crds_private {
     328             :   fd_gossip_out_ctx_t * gossip_update;
     329             : 
     330             :   fd_gossip_activity_update_fn activity_update_fn;
     331             :   void *                       activity_update_fn_ctx;
     332             : 
     333             :   fd_sha256_t sha256[1];
     334             : 
     335             :   int has_staked_node;
     336             : 
     337             :   fd_ip4_port_t entrypoints[ 16UL ];
     338             :   ulong         entrypoints_cnt;
     339             :   ulong         entrypoint_fresh_cnt;
     340             : 
     341             :   fd_crds_entry_t * pool;
     342             :   fd_crds_contact_info_entry_t * ci_pool;
     343             : 
     344             :   evict_treap_t *           evict_treap;
     345             :   staked_expire_dlist_t *   staked_expire_dlist;
     346             :   unstaked_expire_dlist_t * unstaked_expire_dlist;
     347             :   relay_treap_t *           relay_treap;
     348             :   ci_fresh_15s_dlist_t *    ci_fresh_15s_dlist;
     349             :   lookup_map_t *            lookup_map;
     350             :   fd_gossip_hset_t *        hset;
     351             : 
     352             :   fd_gossip_purged_t *      purged;
     353             : 
     354             :   crds_contact_info_fresh_list_t *  ci_fresh_dlist;
     355             :   ci_evict_treap_t *                ci_evict_treap;
     356             : 
     357             :   fd_gossip_wsample_t *    wsample;
     358             :   fd_active_set_t *        active_set;
     359             : 
     360             :   fd_crds_metrics_t metrics[1];
     361             : 
     362             :   ulong magic;
     363             : };
     364             : 
     365             : FD_FN_CONST ulong
     366           0 : fd_crds_align( void ) {
     367           0 :   return FD_CRDS_ALIGN;
     368           0 : }
     369             : 
     370             : FD_FN_CONST ulong
     371           0 : fd_crds_footprint( ulong ele_max ) {
     372           0 :   ulong l;
     373           0 :   l = FD_LAYOUT_INIT;
     374           0 :   l = FD_LAYOUT_APPEND( l, FD_CRDS_ALIGN,                         sizeof(fd_crds_t) );
     375           0 :   l = FD_LAYOUT_APPEND( l, crds_pool_align(),                     crds_pool_footprint( ele_max )                                 );
     376           0 :   l = FD_LAYOUT_APPEND( l, evict_treap_align(),                   evict_treap_footprint( ele_max )                               );
     377           0 :   l = FD_LAYOUT_APPEND( l, staked_expire_dlist_align(),           staked_expire_dlist_footprint()                                );
     378           0 :   l = FD_LAYOUT_APPEND( l, unstaked_expire_dlist_align(),         unstaked_expire_dlist_footprint()                              );
     379           0 :   l = FD_LAYOUT_APPEND( l, relay_treap_align(),                   relay_treap_footprint( ele_max )                               );
     380           0 :   l = FD_LAYOUT_APPEND( l, ci_fresh_15s_dlist_align(),            ci_fresh_15s_dlist_footprint()                                 );
     381           0 :   l = FD_LAYOUT_APPEND( l, lookup_map_align(),                    lookup_map_footprint( ele_max )                                );
     382           0 :   l = FD_LAYOUT_APPEND( l, fd_gossip_hset_align(),                fd_gossip_hset_footprint( ele_max )                            );
     383           0 :   l = FD_LAYOUT_APPEND( l, crds_contact_info_pool_align(),        crds_contact_info_pool_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
     384           0 :   l = FD_LAYOUT_APPEND( l, crds_contact_info_fresh_list_align(),  crds_contact_info_fresh_list_footprint()                       );
     385           0 :   l = FD_LAYOUT_APPEND( l, ci_evict_treap_align(),                ci_evict_treap_footprint( FD_CONTACT_INFO_TABLE_SIZE )         );
     386           0 :   return FD_LAYOUT_FINI( l, FD_CRDS_ALIGN );
     387           0 : }
     388             : 
     389             : void *
     390             : fd_crds_new( void *                       shmem,
     391             :              fd_ip4_port_t const *        entrypoints,
     392             :              ulong                        entrypoints_cnt,
     393             :              fd_gossip_wsample_t *        wsample,
     394             :              fd_active_set_t *            active_set, /* TODO: Remove .. circular dep */
     395             :              fd_rng_t *                   rng,
     396             :              ulong                        ele_max,
     397             :              fd_gossip_purged_t *         purged,
     398             :              fd_gossip_activity_update_fn activity_update_fn,
     399             :              void *                       activity_update_fn_ctx,
     400           0 :              fd_gossip_out_ctx_t *        gossip_update_out ) {
     401           0 :   if( FD_UNLIKELY( !shmem ) ) {
     402           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     403           0 :     return NULL;
     404           0 :   }
     405             : 
     406           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_crds_align() ) ) ) {
     407           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     408           0 :     return NULL;
     409           0 :   }
     410             : 
     411           0 :   if( FD_UNLIKELY( !fd_ulong_is_pow2( ele_max ) ) ) {
     412           0 :     FD_LOG_WARNING(( "ele_max must be a power of 2" ));
     413           0 :     return NULL;
     414           0 :   }
     415             : 
     416           0 :   if( FD_UNLIKELY( !rng ) ) {
     417           0 :     FD_LOG_WARNING(( "NULL rng" ));
     418           0 :     return NULL;
     419           0 :   }
     420             : 
     421           0 :   if( FD_UNLIKELY( !purged ) ) {
     422           0 :     FD_LOG_WARNING(( "NULL purged" ));
     423           0 :     return NULL;
     424           0 :   }
     425             : 
     426           0 :   if( FD_UNLIKELY( !gossip_update_out ) ) {
     427           0 :     FD_LOG_WARNING(( "NULL gossip_out" ));
     428           0 :     return NULL;
     429           0 :   }
     430             : 
     431           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     432           0 :   fd_crds_t * crds              = FD_SCRATCH_ALLOC_APPEND( l, FD_CRDS_ALIGN,                         sizeof(fd_crds_t) );
     433           0 :   void * _pool                  = FD_SCRATCH_ALLOC_APPEND( l, crds_pool_align(),                     crds_pool_footprint( ele_max )                                 );
     434           0 :   void * _evict_treap           = FD_SCRATCH_ALLOC_APPEND( l, evict_treap_align(),                   evict_treap_footprint( ele_max )                               );
     435           0 :   void * _staked_expire_dlist   = FD_SCRATCH_ALLOC_APPEND( l, staked_expire_dlist_align(),           staked_expire_dlist_footprint()                                );
     436           0 :   void * _unstaked_expire_dlist = FD_SCRATCH_ALLOC_APPEND( l, unstaked_expire_dlist_align(),         unstaked_expire_dlist_footprint()                              );
     437           0 :   void * _relay_treap           = FD_SCRATCH_ALLOC_APPEND( l, relay_treap_align(),                   relay_treap_footprint( ele_max )                               );
     438           0 :   void * _ci_fresh_15s_dlist    = FD_SCRATCH_ALLOC_APPEND( l, ci_fresh_15s_dlist_align(),            ci_fresh_15s_dlist_footprint()                                 );
     439           0 :   void * _lookup_map            = FD_SCRATCH_ALLOC_APPEND( l, lookup_map_align(),                    lookup_map_footprint( ele_max )                                );
     440           0 :   void * _hset                  = FD_SCRATCH_ALLOC_APPEND( l, fd_gossip_hset_align(),                fd_gossip_hset_footprint( ele_max )                            );
     441           0 :   void * _ci_pool               = FD_SCRATCH_ALLOC_APPEND( l, crds_contact_info_pool_align(),        crds_contact_info_pool_footprint( FD_CONTACT_INFO_TABLE_SIZE ) );
     442           0 :   void * _ci_dlist              = FD_SCRATCH_ALLOC_APPEND( l, crds_contact_info_fresh_list_align(),  crds_contact_info_fresh_list_footprint()                       );
     443           0 :   void * _ci_evict_treap        = FD_SCRATCH_ALLOC_APPEND( l, ci_evict_treap_align(),                ci_evict_treap_footprint( FD_CONTACT_INFO_TABLE_SIZE )         );
     444             : 
     445           0 :   crds->activity_update_fn = activity_update_fn;
     446           0 :   FD_TEST( crds->activity_update_fn );
     447             : 
     448           0 :   crds->activity_update_fn_ctx = activity_update_fn_ctx;
     449             : 
     450           0 :   crds->pool = crds_pool_join( crds_pool_new( _pool, ele_max ) );
     451           0 :   FD_TEST( crds->pool );
     452             : 
     453           0 :   crds->evict_treap = evict_treap_join( evict_treap_new( _evict_treap, ele_max ) );
     454           0 :   FD_TEST( crds->evict_treap );
     455           0 :   evict_treap_seed( crds->pool, ele_max, fd_rng_ulong( rng ) );
     456             : 
     457           0 :   crds->staked_expire_dlist = staked_expire_dlist_join( staked_expire_dlist_new( _staked_expire_dlist ) );
     458           0 :   FD_TEST( crds->staked_expire_dlist );
     459             : 
     460           0 :   crds->unstaked_expire_dlist = unstaked_expire_dlist_join( unstaked_expire_dlist_new( _unstaked_expire_dlist ) );
     461           0 :   FD_TEST( crds->unstaked_expire_dlist );
     462             : 
     463           0 :   crds->relay_treap = relay_treap_join( relay_treap_new( _relay_treap, ele_max ) );
     464           0 :   FD_TEST( crds->relay_treap );
     465           0 :   relay_treap_seed( crds->pool, ele_max, fd_rng_ulong( rng ) );
     466             : 
     467           0 :   crds->ci_fresh_15s_dlist = ci_fresh_15s_dlist_join( ci_fresh_15s_dlist_new( _ci_fresh_15s_dlist ) );
     468           0 :   FD_TEST( crds->ci_fresh_15s_dlist );
     469             : 
     470           0 :   crds->lookup_map = lookup_map_join( lookup_map_new( _lookup_map, ele_max, fd_rng_ulong( rng ) ) );
     471           0 :   FD_TEST( crds->lookup_map );
     472             : 
     473           0 :   crds->hset = fd_gossip_hset_join( fd_gossip_hset_new( _hset, ele_max ) );
     474           0 :   FD_TEST( crds->hset );
     475             : 
     476           0 :   crds->purged = purged;
     477             : 
     478           0 :   crds->ci_pool = crds_contact_info_pool_join( crds_contact_info_pool_new( _ci_pool, FD_CONTACT_INFO_TABLE_SIZE ) );
     479           0 :   FD_TEST( crds->ci_pool );
     480             : 
     481           0 :   crds->ci_fresh_dlist = crds_contact_info_fresh_list_join( crds_contact_info_fresh_list_new( _ci_dlist ) );
     482           0 :   FD_TEST( crds->ci_fresh_dlist );
     483             : 
     484           0 :   crds->ci_evict_treap = ci_evict_treap_join( ci_evict_treap_new( _ci_evict_treap, FD_CONTACT_INFO_TABLE_SIZE ) );
     485           0 :   FD_TEST( crds->ci_evict_treap );
     486           0 :   ci_evict_treap_seed( crds->ci_pool, FD_CONTACT_INFO_TABLE_SIZE, fd_rng_ulong( rng ) );
     487             : 
     488           0 :   FD_TEST( fd_sha256_join( fd_sha256_new( crds->sha256 ) ) );
     489             : 
     490           0 :   crds->wsample = wsample;
     491           0 :   crds->active_set = active_set;
     492             : 
     493           0 :   FD_TEST( entrypoints_cnt<=16UL );
     494           0 :   for( ulong i=0UL; i<entrypoints_cnt; i++ ) crds->entrypoints[ i ] = entrypoints[ i ];
     495           0 :   crds->entrypoints_cnt = entrypoints_cnt;
     496           0 :   crds->entrypoint_fresh_cnt = 0UL;
     497             : 
     498           0 :   memset( crds->metrics, 0, sizeof(fd_crds_metrics_t) );
     499             : 
     500           0 :   crds->gossip_update   = gossip_update_out;
     501           0 :   crds->has_staked_node = 0;
     502             : 
     503           0 :   FD_COMPILER_MFENCE();
     504           0 :   FD_VOLATILE( crds->magic ) = FD_CRDS_MAGIC;
     505           0 :   FD_COMPILER_MFENCE();
     506             : 
     507           0 :   return (void *)crds;
     508           0 : }
     509             : 
     510             : fd_crds_t *
     511           0 : fd_crds_join( void * shcrds ) {
     512           0 :   if( FD_UNLIKELY( !shcrds ) ) {
     513           0 :     FD_LOG_WARNING(( "NULL shcrds" ));
     514           0 :     return NULL;
     515           0 :   }
     516             : 
     517           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shcrds, fd_crds_align() ) ) ) {
     518           0 :     FD_LOG_WARNING(( "misaligned shcrds" ));
     519           0 :     return NULL;
     520           0 :   }
     521             : 
     522           0 :   fd_crds_t * crds = (fd_crds_t *)shcrds;
     523             : 
     524           0 :   if( FD_UNLIKELY( crds->magic!=FD_CRDS_MAGIC ) ) {
     525           0 :     FD_LOG_WARNING(( "bad magic" ));
     526           0 :     return NULL;
     527           0 :   }
     528             : 
     529           0 :   return crds;
     530           0 : }
     531             : 
     532             : fd_crds_metrics_t const *
     533           0 : fd_crds_metrics( fd_crds_t const * crds ) {
     534           0 :   return crds->metrics;
     535           0 : }
     536             : 
     537             : ulong
     538           0 : fd_crds_len( fd_crds_t const * crds ) {
     539           0 :   return crds_pool_used( crds->pool );
     540           0 : }
     541             : 
     542             : static inline void
     543             : crds_unindex( fd_crds_t *       crds,
     544           0 :               fd_crds_entry_t * entry ) {
     545           0 :   if( FD_LIKELY( entry->stake ) ) staked_expire_dlist_ele_remove( crds->staked_expire_dlist, entry, crds->pool );
     546           0 :   else                            unstaked_expire_dlist_ele_remove( crds->unstaked_expire_dlist, entry, crds->pool );
     547             : 
     548           0 :   evict_treap_ele_remove( crds->evict_treap, entry, crds->pool );
     549           0 :   fd_gossip_hset_remove( crds->hset, crds_pool_idx( crds->pool, entry ) );
     550           0 :   lookup_map_ele_remove( crds->lookup_map, &entry->key, NULL, crds->pool );
     551             : 
     552           0 :   if( FD_UNLIKELY( entry->key.tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     553           0 :     if( FD_LIKELY( entry->stake ) ) crds->metrics->peer_staked_cnt--;
     554           0 :     else                            crds->metrics->peer_unstaked_cnt--;
     555           0 :     crds->metrics->peer_visible_stake -= entry->stake;
     556             : 
     557           0 :     if( FD_LIKELY( entry->ci->fresh_dlist.in_list ) ) {
     558           0 :       crds_contact_info_fresh_list_ele_remove( crds->ci_fresh_dlist, entry->ci, crds->ci_pool );
     559           0 :       crds->entrypoint_fresh_cnt -= entry->ci->is_entrypoint;
     560           0 :     }
     561           0 :     if( FD_LIKELY( entry->ci->fresh_15s_dlist.in_list ) ) {
     562           0 :       ci_fresh_15s_dlist_ele_remove( crds->ci_fresh_15s_dlist, entry->ci, crds->ci_pool );
     563           0 :       crds->activity_update_fn( crds->activity_update_fn_ctx, (fd_pubkey_t const *)entry->key.pubkey, entry->ci->contact_info, FD_GOSSIP_ACTIVITY_CHANGE_TYPE_INACTIVE );
     564           0 :     }
     565           0 :     ci_evict_treap_ele_remove( crds->ci_evict_treap, entry->ci, crds->ci_pool );
     566           0 :   }
     567             : 
     568           0 :   crds->metrics->count[ entry->key.tag ]--;
     569           0 : }
     570             : 
     571             : static inline void
     572             : crds_index( fd_crds_t *       crds,
     573           0 :             fd_crds_entry_t * entry ) {
     574           0 :   if( FD_LIKELY( entry->stake ) ) staked_expire_dlist_ele_push_tail( crds->staked_expire_dlist, entry, crds->pool );
     575           0 :   else                            unstaked_expire_dlist_ele_push_tail( crds->unstaked_expire_dlist, entry, crds->pool );
     576             : 
     577           0 :   evict_treap_ele_insert( crds->evict_treap, entry, crds->pool );
     578           0 :   fd_gossip_hset_insert( crds->hset, crds_pool_idx( crds->pool, entry ), entry->value_hash );
     579           0 :   lookup_map_ele_insert( crds->lookup_map, entry, crds->pool );
     580             : 
     581           0 :   if( FD_UNLIKELY( entry->key.tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     582           0 :     if( FD_LIKELY( entry->stake ) ) crds->metrics->peer_staked_cnt++;
     583           0 :     else                            crds->metrics->peer_unstaked_cnt++;
     584           0 :     crds->metrics->peer_visible_stake += entry->stake;
     585             : 
     586           0 :     fd_gossip_socket_t const * sock = &entry->ci->contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ];
     587           0 :     entry->ci->is_entrypoint = 0;
     588           0 :     for( ulong i=0UL; !entry->ci->is_me && i<crds->entrypoints_cnt; i++ ) {
     589           0 :       entry->ci->is_entrypoint |= (uchar)( !sock->is_ipv6 && sock->ip4==crds->entrypoints[ i ].addr && sock->port==crds->entrypoints[ i ].port );
     590           0 :     }
     591           0 :     crds->entrypoint_fresh_cnt += entry->ci->is_entrypoint;
     592             : 
     593           0 :     ci_evict_treap_ele_insert( crds->ci_evict_treap, entry->ci, crds->ci_pool );
     594           0 :     crds_contact_info_fresh_list_ele_push_tail( crds->ci_fresh_dlist, entry->ci, crds->ci_pool );
     595           0 :     ci_fresh_15s_dlist_ele_push_tail( crds->ci_fresh_15s_dlist, entry->ci, crds->ci_pool );
     596           0 :     entry->ci->fresh_dlist.in_list = 1;
     597           0 :     entry->ci->fresh_15s_dlist.in_list = 1;
     598           0 :     crds->activity_update_fn( crds->activity_update_fn_ctx, (fd_pubkey_t const *)entry->key.pubkey, entry->ci->contact_info, FD_GOSSIP_ACTIVITY_CHANGE_TYPE_ACTIVE );
     599           0 :   }
     600             : 
     601           0 :   crds->metrics->count[ entry->key.tag ]++;
     602           0 : }
     603             : 
     604             : /* publish_ci_seen mirrors a CONTACT_INFO update already published on
     605             :    gossip_ciaddr onto gossip_ciseen, which carries every contact info
     606             :    update including refreshes of unchanged content. */
     607             : 
     608             : static inline void
     609             : publish_ci_seen( fd_crds_t *                        crds,
     610             :                  fd_gossip_update_message_t const * msg,
     611             :                  ulong                              sz,
     612             :                  long                               now,
     613           0 :                  fd_stem_context_t *                stem ) {
     614           0 :   fd_gossip_out_ctx_t * out = crds->gossip_update + FD_GOSSIP_UPDATE_LINK_CI_SEEN;
     615           0 :   if( FD_UNLIKELY( out->idx==ULONG_MAX ) ) return; /* no gui */
     616             : 
     617           0 :   fd_memcpy( fd_gossip_out_get_chunk( out ), msg, sz );
     618           0 :   fd_gossip_tx_publish_chunk( out, stem, (ulong)msg->tag, sz, now );
     619           0 : }
     620             : 
     621             : static inline void
     622             : crds_release( fd_crds_t *         crds,
     623             :               fd_crds_entry_t *   entry,
     624             :               long                now,
     625             :               int                 evicting,
     626           0 :               fd_stem_context_t * stem ) {
     627           0 :   crds_unindex( crds, entry );
     628           0 :   if( FD_UNLIKELY( entry->relay.pending ) ) relay_treap_ele_remove( crds->relay_treap, entry, crds->pool );
     629           0 :   fd_gossip_purged_insert_replaced( crds->purged, entry->value_hash, now );
     630             : 
     631           0 :   if( FD_UNLIKELY( entry->key.tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     632           0 :     if( FD_UNLIKELY( evicting ) ) crds->metrics->peer_evicted_cnt++;
     633             : 
     634           0 :     fd_gossip_out_ctx_t * out = crds->gossip_update + FD_GOSSIP_UPDATE_LINK_CI_ADDR;
     635           0 :     fd_gossip_update_message_t * msg = fd_gossip_out_get_chunk( out );
     636           0 :     msg->tag = FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE;
     637           0 :     msg->wallclock = (ulong)FD_NANOSEC_TO_MILLI( now );
     638           0 :     msg->contact_info_remove->idx = crds_contact_info_pool_idx( crds->ci_pool, entry->ci );
     639           0 :     fd_memcpy( msg->origin, entry->key.pubkey, 32UL );
     640           0 :     fd_gossip_tx_publish_chunk( out, stem, (ulong)msg->tag, FD_GOSSIP_UPDATE_SZ_CONTACT_INFO_REMOVE, now );
     641           0 :     publish_ci_seen( crds, msg, FD_GOSSIP_UPDATE_SZ_CONTACT_INFO_REMOVE, now, stem );
     642             : 
     643           0 :     ulong ci_idx = crds_contact_info_pool_idx( crds->ci_pool, entry->ci );
     644           0 :     fd_active_set_remove_peer( crds->active_set, ci_idx );
     645           0 :     fd_gossip_wsample_remove( crds->wsample, ci_idx );
     646             : 
     647           0 :     crds_contact_info_pool_ele_release( crds->ci_pool, entry->ci );
     648           0 :   }
     649             : 
     650           0 :   if( FD_UNLIKELY( evicting ) ) crds->metrics->evicted_cnt++;
     651           0 :   else                          crds->metrics->expired_cnt++;
     652             : 
     653           0 :   crds_pool_ele_release( crds->pool, entry );
     654           0 : }
     655             : 
     656             : static inline fd_crds_entry_t *
     657             : crds_acquire( fd_crds_t *         crds,
     658             :               int                 is_contact_info,
     659             :               long                now,
     660           0 :               fd_stem_context_t * stem ) {
     661           0 :   if( FD_UNLIKELY( is_contact_info ) ) {
     662           0 :     if( FD_UNLIKELY( !crds_contact_info_pool_free( crds->ci_pool ) ) ) {
     663           0 :       ci_evict_treap_fwd_iter_t ci_it = ci_evict_treap_fwd_iter_init( crds->ci_evict_treap, crds->ci_pool );
     664           0 :       FD_TEST( !ci_evict_treap_fwd_iter_done( ci_it ) );
     665           0 :       fd_crds_contact_info_entry_t * ci_evict = ci_evict_treap_fwd_iter_ele( ci_it, crds->ci_pool );
     666           0 :       crds_release( crds, ci_evict->crds_entry, now, 1, stem );
     667           0 :     } else if( FD_UNLIKELY( !crds_pool_free( crds->pool ) ) ) {
     668           0 :       evict_treap_fwd_iter_t it = evict_treap_fwd_iter_init( crds->evict_treap, crds->pool );
     669           0 :       FD_TEST( !evict_treap_fwd_iter_done( it ) );
     670           0 :       crds_release( crds, evict_treap_fwd_iter_ele( it, crds->pool ), now, 1, stem );
     671           0 :     }
     672           0 :     fd_crds_contact_info_entry_t * ci = crds_contact_info_pool_ele_acquire( crds->ci_pool );
     673           0 :     fd_crds_entry_t * entry = crds_pool_ele_acquire( crds->pool );
     674           0 :     entry->ci = ci;
     675           0 :     entry->ci->crds_entry = entry;
     676           0 :     return entry;
     677           0 :   } else {
     678           0 :     if( FD_UNLIKELY( !crds_pool_free( crds->pool ) ) ) {
     679           0 :       evict_treap_fwd_iter_t it = evict_treap_fwd_iter_init( crds->evict_treap, crds->pool );
     680           0 :       FD_TEST( !evict_treap_fwd_iter_done( it ) );
     681           0 :       crds_release( crds, evict_treap_fwd_iter_ele( it, crds->pool ), now, 1, stem );
     682           0 :     }
     683           0 :     return crds_pool_ele_acquire( crds->pool );
     684           0 :   }
     685           0 : }
     686             : 
     687             : static inline void
     688             : expire( fd_crds_t *         crds,
     689             :         long                now,
     690             :         fd_stem_context_t * stem,
     691           0 :         int *               charge_busy ){
     692             :   /* Gossip's slot time does not change with the reduce_slot_time
     693             :      feature gates. */
     694           0 :   static const long SLOT_DURATION_NANOS            = 400L*1000L*1000L;
     695           0 :   static const long STAKED_EXPIRE_DURATION_NANOS   = 432000L*SLOT_DURATION_NANOS;
     696           0 :   static const long UNSTAKED_EXPIRE_DURATION_NANOS = 15L*1000L*1000L*1000L;
     697             : 
     698           0 :   while( !staked_expire_dlist_is_empty( crds->staked_expire_dlist, crds->pool ) ) {
     699           0 :     fd_crds_entry_t * head = staked_expire_dlist_ele_peek_head( crds->staked_expire_dlist, crds->pool );
     700             : 
     701           0 :     if( FD_LIKELY( head->expire.wallclock_nanos>now-STAKED_EXPIRE_DURATION_NANOS ) ) break;
     702           0 :     crds_release( crds, head, now, 0, stem );
     703           0 :     if( charge_busy ) *charge_busy = 1;
     704           0 :   }
     705             : 
     706           0 :   long unstaked_expire_duration_nanos = fd_long_if( crds->has_staked_node,
     707           0 :                                                     UNSTAKED_EXPIRE_DURATION_NANOS,
     708           0 :                                                     STAKED_EXPIRE_DURATION_NANOS );
     709             : 
     710           0 :   while( !unstaked_expire_dlist_is_empty( crds->unstaked_expire_dlist, crds->pool ) ) {
     711           0 :     fd_crds_entry_t * head = unstaked_expire_dlist_ele_peek_head( crds->unstaked_expire_dlist, crds->pool );
     712             : 
     713           0 :     if( FD_LIKELY( head->expire.wallclock_nanos>now-unstaked_expire_duration_nanos ) ) break;
     714           0 :     crds_release( crds, head, now, 0, stem );
     715           0 :     if( charge_busy ) *charge_busy = 1;
     716           0 :   }
     717           0 : }
     718             : 
     719             : static void
     720             : unfresh( fd_crds_t * crds,
     721             :          long        now,
     722           0 :          int *       charge_busy ) {
     723           0 :   while( !crds_contact_info_fresh_list_is_empty( crds->ci_fresh_dlist, crds->ci_pool ) ) {
     724           0 :     fd_crds_contact_info_entry_t * head = crds_contact_info_fresh_list_ele_peek_head( crds->ci_fresh_dlist, crds->ci_pool );
     725             : 
     726           0 :     if( FD_LIKELY( head->received_wallclock_nanos>now-60L*1000L*1000L*1000L ) ) break;
     727           0 :     head = crds_contact_info_fresh_list_ele_pop_head( crds->ci_fresh_dlist, crds->ci_pool );
     728           0 :     FD_TEST( head->fresh_dlist.in_list );
     729           0 :     head->fresh_dlist.in_list = 0;
     730           0 :     crds->entrypoint_fresh_cnt -= head->is_entrypoint;
     731             : 
     732           0 :     fd_gossip_wsample_fresh( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, head ), 0 );
     733           0 :     if( charge_busy ) *charge_busy = 1;
     734           0 :   }
     735             : 
     736           0 :   while( !ci_fresh_15s_dlist_is_empty( crds->ci_fresh_15s_dlist, crds->ci_pool ) ) {
     737           0 :     fd_crds_contact_info_entry_t * head = ci_fresh_15s_dlist_ele_peek_head( crds->ci_fresh_15s_dlist, crds->ci_pool );
     738             : 
     739           0 :     if( FD_LIKELY( head->received_wallclock_nanos>now-15L*1000L*1000L*1000L ) ) break;
     740             : 
     741           0 :     head = ci_fresh_15s_dlist_ele_pop_head( crds->ci_fresh_15s_dlist, crds->ci_pool );
     742             : 
     743           0 :     FD_TEST( head->fresh_15s_dlist.in_list );
     744           0 :     head->fresh_15s_dlist.in_list = 0U;
     745           0 :     crds->activity_update_fn( crds->activity_update_fn_ctx, (fd_pubkey_t const *)head->crds_entry->key.pubkey, head->contact_info, FD_GOSSIP_ACTIVITY_CHANGE_TYPE_INACTIVE );
     746           0 :     if( charge_busy ) *charge_busy = 1;
     747           0 :   }
     748           0 : }
     749             : 
     750             : /* uncoalesce pushes the latest version of each key whose relay was
     751             :    held back and is now due. */
     752             : 
     753             : static inline void
     754             : uncoalesce( fd_crds_t *         crds,
     755             :             long                now,
     756             :             fd_stem_context_t * stem,
     757           0 :             int *               charge_busy ) {
     758           0 :   ulong now_ms = (ulong)FD_NANOSEC_TO_MILLI( now );
     759           0 :   while( relay_treap_ele_cnt( crds->relay_treap ) ) {
     760           0 :     fd_crds_entry_t * head = relay_treap_fwd_iter_ele( relay_treap_fwd_iter_init( crds->relay_treap, crds->pool ), crds->pool );
     761           0 :     if( FD_LIKELY( head->relay.nanos>now ) ) break;
     762           0 :     relay_treap_ele_remove( crds->relay_treap, head, crds->pool );
     763           0 :     head->relay.pending = 0;
     764           0 :     if( FD_UNLIKELY( head->wallclock+15000UL<now_ms || head->wallclock>now_ms+15000UL ) ) {
     765           0 :       head->relay.nanos -= FD_CRDS_RELAY_COALESCE_NANOS; /* back to the last relay, none went out */
     766           0 :       continue;
     767           0 :     }
     768           0 :     head->relay.nanos = now;
     769           0 :     fd_active_set_push( crds->active_set, head->value_bytes, head->value_sz, head->key.pubkey, head->stake, stem, now, 0 );
     770           0 :     if( charge_busy ) *charge_busy = 1;
     771           0 :   }
     772           0 : }
     773             : 
     774             : void
     775             : fd_crds_advance( fd_crds_t *         crds,
     776             :                  long                now,
     777             :                  fd_stem_context_t * stem,
     778           0 :                  int *               charge_busy ) {
     779           0 :   expire( crds, now, stem, charge_busy );
     780           0 :   unfresh( crds, now, charge_busy );
     781           0 :   uncoalesce( crds, now, stem, charge_busy );
     782           0 : }
     783             : 
     784             : static inline void
     785             : publish_update_msg( fd_crds_t *               crds,
     786             :                     fd_crds_entry_t *         entry,
     787             :                     fd_gossip_value_t const * entry_view,
     788             :                     int                       ci_changed,
     789             :                     long                      now,
     790           0 :                     fd_stem_context_t *       stem ) {
     791           0 :   FD_TEST( stem );
     792           0 :   if( FD_LIKELY( entry->key.tag!=FD_GOSSIP_VALUE_CONTACT_INFO    &&
     793           0 :                  entry->key.tag!=FD_GOSSIP_VALUE_VOTE            &&
     794           0 :                  entry->key.tag!=FD_GOSSIP_VALUE_DUPLICATE_SHRED &&
     795           0 :                  entry->key.tag!=FD_GOSSIP_VALUE_SNAPSHOT_HASHES ) ) {
     796           0 :     return;
     797           0 :   }
     798             : 
     799           0 :   int tag;
     800           0 :   switch( entry->key.tag ) {
     801           0 :     case FD_GOSSIP_VALUE_CONTACT_INFO:    tag = FD_GOSSIP_UPDATE_TAG_CONTACT_INFO;    break;
     802           0 :     case FD_GOSSIP_VALUE_VOTE:            tag = FD_GOSSIP_UPDATE_TAG_VOTE;            break;
     803           0 :     case FD_GOSSIP_VALUE_DUPLICATE_SHRED: tag = FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED; break;
     804           0 :     default:                              tag = FD_GOSSIP_UPDATE_TAG_SNAPSHOT_HASHES; break;
     805           0 :   }
     806           0 :   ulong link = fd_gossip_update_link( (ulong)tag );
     807           0 :   if( FD_UNLIKELY( tag==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && !ci_changed ) ) link = FD_GOSSIP_UPDATE_LINK_CI_SEEN;
     808           0 :   fd_gossip_out_ctx_t * out = crds->gossip_update + link;
     809           0 :   if( FD_UNLIKELY( out->idx==ULONG_MAX ) ) return; /* nobody reads this kind */
     810             : 
     811           0 :   fd_gossip_update_message_t * msg = fd_gossip_out_get_chunk( out );
     812           0 :   msg->tag       = tag;
     813           0 :   msg->wallclock = entry->wallclock;
     814           0 :   fd_memcpy( msg->origin, entry->key.pubkey, 32UL );
     815             : 
     816           0 :   ulong sz;
     817           0 :   switch( entry->key.tag ) {
     818           0 :     case FD_GOSSIP_VALUE_CONTACT_INFO:
     819           0 :       *msg->contact_info->value = *entry->ci->contact_info;
     820           0 :       msg->contact_info->idx = crds_contact_info_pool_idx( crds->ci_pool, entry->ci );
     821           0 :       sz = FD_GOSSIP_UPDATE_SZ_CONTACT_INFO;
     822           0 :       break;
     823           0 :     case FD_GOSSIP_VALUE_VOTE:
     824             :       /* TODO: dynamic sizing */
     825           0 :       sz = FD_GOSSIP_UPDATE_SZ_VOTE;
     826           0 :       fd_crds_key_t lookup_ci;
     827           0 :       lookup_ci.tag = FD_GOSSIP_VALUE_CONTACT_INFO;
     828           0 :       fd_memcpy( &lookup_ci.pubkey, entry->key.pubkey, sizeof(fd_pubkey_t) );
     829           0 :       fd_crds_entry_t * ci = lookup_map_ele_query( crds->lookup_map, &lookup_ci, NULL, crds->pool );
     830             : 
     831           0 :       if( FD_LIKELY( ci && ci->key.tag == FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     832           0 :         msg->vote->socket->is_ipv6 = ci->ci->contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].is_ipv6;
     833           0 :         if( msg->vote->socket->is_ipv6 ) {
     834           0 :           fd_memcpy( msg->vote->socket->ip6, ci->ci->contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].ip6, 16UL );
     835           0 :         } else {
     836           0 :           msg->vote->socket->ip4 = ci->ci->contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].ip4;
     837           0 :         }
     838           0 :         msg->vote->socket->port = ci->ci->contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_GOSSIP ].port;
     839           0 :       } else {
     840           0 :         msg->vote->socket->is_ipv6 = 0;
     841           0 :         msg->vote->socket->ip4 = 0;
     842           0 :         msg->vote->socket->port = 0;
     843           0 :       }
     844             : 
     845           0 :       msg->vote->first_seen_nanos = now;
     846           0 :       msg->vote->value->index = entry->key.vote_index;
     847           0 :       msg->vote->value->transaction_len = entry_view->vote->transaction_len;
     848           0 :       fd_memcpy( msg->vote->value->transaction, entry_view->vote->transaction, entry_view->vote->transaction_len );
     849           0 :       break;
     850           0 :     case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
     851             :       /* TODO: dynamic sizing */
     852           0 :       sz = FD_GOSSIP_UPDATE_SZ_DUPLICATE_SHRED;
     853           0 :       {
     854           0 :         fd_gossip_duplicate_shred_t const * ds     = entry_view->duplicate_shred;
     855           0 :         fd_gossip_duplicate_shred_t *            ds_msg = msg->duplicate_shred;
     856             : 
     857           0 :         ds_msg->index       = ds->index;
     858           0 :         ds_msg->slot        = ds->slot;
     859           0 :         ds_msg->num_chunks  = ds->num_chunks;
     860           0 :         ds_msg->chunk_index = ds->chunk_index;
     861           0 :         ds_msg->chunk_len   = ds->chunk_len;
     862           0 :         fd_memcpy( ds_msg->chunk, ds->chunk, ds->chunk_len );
     863           0 :       }
     864           0 :       break;
     865           0 :     case FD_GOSSIP_VALUE_SNAPSHOT_HASHES:
     866             :       /* TODO: dynamic sizing */
     867           0 :       sz = FD_GOSSIP_UPDATE_SZ_SNAPSHOT_HASHES;
     868           0 :       {
     869           0 :         fd_gossip_snapshot_hashes_t const * sh     = entry_view->snapshot_hashes;
     870           0 :         fd_gossip_snapshot_hashes_t *            sh_msg = msg->snapshot_hashes;
     871             : 
     872           0 :         sh_msg->full_slot = sh->full_slot;
     873           0 :         fd_memcpy( sh_msg->full_hash, sh->full_hash, 32UL );
     874           0 :         sh_msg->incremental_len = sh->incremental_len;
     875           0 :         for( ulong i=0; i<sh->incremental_len; i++ ) {
     876           0 :           sh_msg->incremental[ i ].slot = sh->incremental[ i ].slot;
     877           0 :           fd_memcpy( sh_msg->incremental[ i ].hash, sh->incremental[ i ].hash, 32UL );
     878           0 :         }
     879           0 :       }
     880           0 :       break;
     881           0 :     default:
     882           0 :       FD_LOG_ERR(( "impossible" ));
     883           0 :   }
     884           0 :   fd_gossip_tx_publish_chunk( out,
     885           0 :                               stem,
     886           0 :                               (ulong)msg->tag,
     887           0 :                               sz,
     888           0 :                               now );
     889             : 
     890           0 :   if( FD_UNLIKELY( link==FD_GOSSIP_UPDATE_LINK_CI_ADDR && tag==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO ) ) {
     891           0 :     publish_ci_seen( crds, msg, sz, now, stem );
     892           0 :   }
     893           0 : }
     894             : 
     895             : static int
     896             : crds_compare( fd_crds_entry_t const *   incumbent,
     897           0 :               fd_gossip_value_t const * candidate ){
     898           0 :   int compare = 0;
     899           0 :   switch( candidate->tag ) {
     900           0 :     case FD_GOSSIP_VALUE_CONTACT_INFO:
     901           0 :       if( FD_UNLIKELY( candidate->contact_info->outset<incumbent->ci->contact_info->outset ) ) compare = 1;
     902           0 :       else if( FD_UNLIKELY( candidate->contact_info->outset>incumbent->ci->contact_info->outset ) ) compare = -1;
     903           0 :       break;
     904             :     /* NodeInstance has no special override logic in Agave — it uses
     905             :        the default wallclock + hash tiebreaker like all other types. */
     906           0 :     default:
     907           0 :       break;
     908           0 :   }
     909             : 
     910           0 :   if( FD_UNLIKELY( compare ) ) return compare;
     911             : 
     912           0 :   if( FD_UNLIKELY( candidate->wallclock<incumbent->wallclock ) ) return 1;
     913           0 :   else if( FD_UNLIKELY( candidate->wallclock>incumbent->wallclock ) ) return -1;
     914           0 :   else return 0;
     915           0 : }
     916             : 
     917             : long
     918             : fd_crds_insert( fd_crds_t *               crds,
     919             :                 fd_gossip_value_t const * value,
     920             :                 uchar const *             value_bytes,
     921             :                 ulong                     value_bytes_len,
     922             :                 ulong                     origin_stake,
     923             :                 int                       origin_ping_tracked,
     924             :                 int                       is_me,
     925             :                 int                       is_push,
     926             :                 long                      now,
     927             :                 fd_stem_context_t *       stem,
     928           0 :                 int *                     opt_relay ) {
     929           0 :   if( opt_relay ) *opt_relay = 0;
     930           0 :   fd_crds_key_t candidate_key = {
     931           0 :     .tag = (uchar)value->tag,
     932           0 :   };
     933           0 :   switch( candidate_key.tag ) {
     934           0 :     case FD_GOSSIP_VALUE_VOTE: candidate_key.vote_index = value->vote->index; break;
     935           0 :     case FD_GOSSIP_VALUE_EPOCH_SLOTS: candidate_key.epoch_slots_index = value->epoch_slots->index; break;
     936           0 :     case FD_GOSSIP_VALUE_DUPLICATE_SHRED: candidate_key.duplicate_shred_index = value->duplicate_shred->index; break;
     937           0 :     default: break;
     938           0 :   }
     939           0 :   fd_memcpy( candidate_key.pubkey, value->origin, 32UL );
     940             : 
     941           0 :   fd_crds_entry_t * incumbent = lookup_map_ele_query( crds->lookup_map, &candidate_key, NULL, crds->pool );
     942           0 :   int replacing = !!incumbent;
     943           0 :   int ci_changed = 1;
     944             : 
     945           0 :   uchar value_hash[ 32UL ];
     946           0 :   if( FD_UNLIKELY( !replacing ) ) {
     947           0 :     fd_sha256_hash( value_bytes, value_bytes_len, value_hash );
     948             : 
     949           0 :     incumbent = crds_acquire( crds, value->tag==FD_GOSSIP_VALUE_CONTACT_INFO, now, stem );
     950           0 :     incumbent->key           = candidate_key;
     951           0 :     incumbent->relay.nanos   = now-FD_CRDS_RELAY_COALESCE_NANOS;
     952           0 :     incumbent->relay.pending = 0;
     953           0 :     if( FD_UNLIKELY( value->tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     954           0 :       fd_gossip_wsample_add( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, incumbent->ci ), origin_stake, origin_ping_tracked, is_me );
     955           0 :     }
     956           0 :   } else {
     957             :     /* Fast duplicate check by signature before computing expensive
     958             :        sha256 hash. */
     959           0 :     if( FD_UNLIKELY( fd_ulong_load_8( incumbent->value_bytes )==fd_ulong_load_8( value->signature ) ) ) return is_push ? (long)(1UL+incumbent->num_push_recv++) : 1L;
     960             : 
     961           0 :     fd_sha256_hash( value_bytes, value_bytes_len, value_hash );
     962           0 :     switch( crds_compare( incumbent, value ) ) {
     963           0 :       case -1: break; /* upserting */
     964           0 :       case 0: {
     965           0 :         int result = memcmp( value_hash, incumbent->value_hash, 32UL );
     966           0 :         if( FD_UNLIKELY( !result ) ) return is_push ? (long)(1UL+incumbent->num_push_recv++) : 1L;
     967           0 :         else if( FD_UNLIKELY( result<0 ) ) {
     968           0 :           fd_gossip_purged_insert_failed_insert( crds->purged, value_hash, now );
     969           0 :           return -1L; /* stale */
     970           0 :         }
     971           0 :         else break; /* upserting */
     972           0 :       }
     973           0 :       case 1: {
     974           0 :         fd_gossip_purged_insert_failed_insert( crds->purged, value_hash, now );
     975           0 :         return -1L; /* stale */
     976           0 :       }
     977           0 :     }
     978             : 
     979           0 :     fd_gossip_purged_insert_replaced( crds->purged, incumbent->value_hash, now );
     980           0 :     crds_unindex( crds, incumbent );
     981             : 
     982           0 :     if( FD_UNLIKELY( value->tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
     983           0 :       ci_changed = !fd_gossip_contact_info_eq( incumbent->ci->contact_info, value->contact_info );
     984           0 :       fd_gossip_wsample_fresh( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, incumbent->ci ), 1 );
     985           0 :       fd_gossip_wsample_stake( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, incumbent->ci ), origin_stake );
     986           0 :       fd_gossip_wsample_ping_tracked( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, incumbent->ci ), origin_ping_tracked );
     987           0 :       fd_gossip_wsample_is_me( crds->wsample, crds_contact_info_pool_idx( crds->ci_pool, incumbent->ci ), is_me );
     988           0 :     }
     989           0 :   }
     990             : 
     991           0 :   incumbent->wallclock              = value->wallclock;
     992           0 :   incumbent->stake                  = origin_stake;
     993           0 :   incumbent->num_push_recv          = (ulong)!!is_push;
     994           0 :   incumbent->expire.wallclock_nanos = now;
     995           0 :   incumbent->value_sz               = (ushort)value_bytes_len;
     996           0 :   fd_memcpy( incumbent->value_bytes, value_bytes, value_bytes_len );
     997           0 :   fd_memcpy( incumbent->value_hash, value_hash, 32UL );
     998             : 
     999           0 :   if( FD_UNLIKELY( value->tag==FD_GOSSIP_VALUE_NODE_INSTANCE ) ) {
    1000           0 :     incumbent->node_instance_token = value->node_instance->token;
    1001           0 :   } else if( FD_UNLIKELY( value->tag==FD_GOSSIP_VALUE_CONTACT_INFO ) ) {
    1002           0 :     *incumbent->ci->contact_info            = *value->contact_info;
    1003           0 :     incumbent->ci->received_wallclock_nanos = now;
    1004           0 :     incumbent->ci->is_me                    = (uchar)!!is_me;
    1005           0 :   }
    1006             : 
    1007           0 :   crds_index( crds, incumbent );
    1008             : 
    1009           0 :   crds->has_staked_node |= incumbent->stake ? 1 : 0;
    1010             : 
    1011           0 :   publish_update_msg( crds, incumbent, value, ci_changed, now, stem );
    1012             : 
    1013           0 :   if( FD_UNLIKELY( incumbent->relay.pending ) ) {
    1014           0 :     crds->metrics->relay_coalesced_cnt++;
    1015           0 :     return 0L;
    1016           0 :   }
    1017           0 :   ulong now_ms = (ulong)FD_NANOSEC_TO_MILLI( now );
    1018           0 :   if( FD_UNLIKELY( !opt_relay || value->wallclock+15000UL<now_ms || value->wallclock>now_ms+15000UL ) ) return 0L;
    1019           0 :   if( FD_LIKELY( now-incumbent->relay.nanos>=FD_CRDS_RELAY_COALESCE_NANOS ) ) {
    1020           0 :     incumbent->relay.nanos = now;
    1021           0 :     *opt_relay = 1;
    1022           0 :   } else {
    1023           0 :     incumbent->relay.pending = 1;
    1024           0 :     incumbent->relay.nanos  += FD_CRDS_RELAY_COALESCE_NANOS;
    1025           0 :     relay_treap_ele_insert( crds->relay_treap, incumbent, crds->pool );
    1026           0 :   }
    1027           0 :   return 0L;
    1028           0 : }
    1029             : 
    1030             : void
    1031             : fd_crds_entry_value( fd_crds_entry_t const * entry,
    1032             :                      uchar const **          value_bytes,
    1033           0 :                      ulong *                 value_sz ) {
    1034           0 :   *value_bytes = entry->value_bytes;
    1035           0 :   *value_sz    = entry->value_sz;
    1036           0 : }
    1037             : 
    1038             : ulong
    1039           0 : fd_crds_entry_wallclock( fd_crds_entry_t const * entry ) {
    1040           0 :   return entry->wallclock;
    1041           0 : }
    1042             : 
    1043             : uchar const *
    1044           0 : fd_crds_entry_hash( fd_crds_entry_t const * entry ) {
    1045           0 :   return entry->value_hash;
    1046           0 : }
    1047             : 
    1048             : fd_gossip_hset_t const *
    1049           0 : fd_crds_hset( fd_crds_t const * crds ) {
    1050           0 :   return crds->hset;
    1051           0 : }
    1052             : 
    1053             : ulong
    1054           0 : fd_crds_peer_count( fd_crds_t const * crds ){
    1055           0 :   return crds_contact_info_pool_used( crds->ci_pool );
    1056           0 : }
    1057             : 
    1058             : ulong
    1059           0 : fd_crds_entrypoint_fresh_cnt( fd_crds_t const * crds ) {
    1060           0 :   return crds->entrypoint_fresh_cnt;
    1061           0 : }
    1062             : 
    1063             : fd_gossip_contact_info_t const *
    1064             : fd_crds_ci( fd_crds_t const * crds,
    1065           0 :             ulong             ci_idx ) {
    1066           0 :   fd_crds_contact_info_entry_t const * ci = crds_contact_info_pool_ele_const( crds->ci_pool, ci_idx );
    1067           0 :   FD_TEST( ci );
    1068           0 :   return ci->contact_info;
    1069           0 : }
    1070             : 
    1071             : uchar const *
    1072             : fd_crds_ci_pubkey( fd_crds_t const * crds,
    1073           0 :                    ulong             ci_idx ) {
    1074           0 :   fd_crds_contact_info_entry_t const * ci = crds_contact_info_pool_ele_const( crds->ci_pool, ci_idx );
    1075           0 :   FD_TEST( ci );
    1076           0 :   return ci->crds_entry->key.pubkey;
    1077           0 : }
    1078             : 
    1079             : ulong
    1080             : fd_crds_ci_idx( fd_crds_t const * crds,
    1081           0 :                 uchar const *     pubkey ) {
    1082           0 :   fd_crds_key_t lookup_ci = {
    1083           0 :     .tag = FD_GOSSIP_VALUE_CONTACT_INFO,
    1084           0 :   };
    1085           0 :   fd_memcpy( lookup_ci.pubkey, pubkey, 32UL );
    1086             : 
    1087           0 :   fd_crds_entry_t const * ci_entry = lookup_map_ele_query( crds->lookup_map, &lookup_ci, NULL, crds->pool );
    1088           0 :   if( FD_UNLIKELY( !ci_entry ) ) return ULONG_MAX;
    1089           0 :   FD_TEST( ci_entry->key.tag==FD_GOSSIP_VALUE_CONTACT_INFO );
    1090           0 :   return crds_contact_info_pool_idx( crds->ci_pool, ci_entry->ci );
    1091           0 : }
    1092             : 
    1093             : fd_crds_entry_t const *
    1094             : fd_crds_entry_at( fd_crds_t const * crds,
    1095           0 :                   ulong             idx ) {
    1096           0 :   return crds_pool_ele_const( crds->pool, idx );
    1097           0 : }

Generated by: LCOV version 1.14