LCOV - code coverage report
Current view: top level - flamenco/gossip - fd_gossip_purged.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 146 182 80.2 %
Date: 2026-10-17 04:27:35 Functions: 13 14 92.9 %

          Line data    Source code
       1             : #include "fd_gossip_purged_private.h"
       2             : #include "fd_gossip_hset.h"
       3             : #include "../../util/rng/fd_rng.h"
       4             : 
       5             : struct fd_gossip_purged_private {
       6             :   fd_crds_purged_t *            pool;
       7             :   purged_map_t *                map; /* dedup by hash prefix */
       8             :   replaced_dlist_t *            replaced_dlist;
       9             :   failed_inserts_dlist_t *      failed_inserts_dlist;
      10             :   no_contact_info_dlist_t *     no_contact_info_dlist;
      11             : 
      12             :   /* Per-origin-pubkey map (MAP_MULTI) for no_contact_info entries.
      13             :      When we learn a contact info for a pubkey, we drain all
      14             :      associated hashes from the purged table so peers re-send them.
      15             :      Elements live in the purged pool above. */
      16             :   nci_origin_map_t *            nci_origin_map;
      17             : 
      18             :   /* Dense copy of the hashes for building pull request filters */
      19             :   fd_gossip_hset_t *            hset;
      20             : 
      21             :   fd_gossip_purged_metrics_t    metrics[1];
      22             : 
      23             :   ulong magic; /* ==FD_GOSSIP_PURGED_MAGIC */
      24             : };
      25             : 
      26             : static const long STAKED_EXPIRE_DURATION_NANOS = 432000L*400L*1000L*1000L;
      27             : static const long REPLACED_EXPIRE_DURATION_NANOS = 60L*1000L*1000L*1000L;
      28             : static const long FAILED_INSERTS_EXPIRE_DURATION_NANOS = 60L*1000L*1000L*1000L;
      29             : 
      30             : FD_FN_CONST ulong
      31          33 : fd_gossip_purged_align( void ) {
      32          33 :   return FD_GOSSIP_PURGED_ALIGN;
      33          33 : }
      34             : 
      35             : FD_FN_CONST ulong
      36          24 : fd_gossip_purged_footprint( ulong purged_max ) {
      37          24 :   ulong nci_origin_max = fd_ulong_pow2_up( purged_max/4UL );
      38          24 :   ulong l;
      39          24 :   l = FD_LAYOUT_INIT;
      40          24 :   l = FD_LAYOUT_APPEND( l, FD_GOSSIP_PURGED_ALIGN,        sizeof(fd_gossip_purged_t) );
      41          24 :   l = FD_LAYOUT_APPEND( l, purged_pool_align(),           purged_pool_footprint( purged_max ) );
      42          24 :   l = FD_LAYOUT_APPEND( l, purged_map_align(),            purged_map_footprint( purged_map_chain_cnt_est( purged_max ) ) );
      43          24 :   l = FD_LAYOUT_APPEND( l, replaced_dlist_align(),        replaced_dlist_footprint() );
      44          24 :   l = FD_LAYOUT_APPEND( l, failed_inserts_dlist_align(),  failed_inserts_dlist_footprint() );
      45          24 :   l = FD_LAYOUT_APPEND( l, no_contact_info_dlist_align(), no_contact_info_dlist_footprint() );
      46          24 :   l = FD_LAYOUT_APPEND( l, nci_origin_map_align(),        nci_origin_map_footprint( nci_origin_max ) );
      47          24 :   l = FD_LAYOUT_APPEND( l, fd_gossip_hset_align(),        fd_gossip_hset_footprint( purged_max ) );
      48          24 :   return FD_LAYOUT_FINI( l, FD_GOSSIP_PURGED_ALIGN );
      49          24 : }
      50             : 
      51             : void *
      52             : fd_gossip_purged_new( void *     shmem,
      53             :                       fd_rng_t * rng,
      54           9 :                       ulong      purged_max ) {
      55           9 :   if( FD_UNLIKELY( !shmem ) ) {
      56           0 :     FD_LOG_WARNING(( "NULL shmem" ));
      57           0 :     return NULL;
      58           0 :   }
      59             : 
      60           9 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_gossip_purged_align() ) ) ) {
      61           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
      62           0 :     return NULL;
      63           0 :   }
      64             : 
      65           9 :   if( FD_UNLIKELY( !fd_ulong_is_pow2( purged_max ) ) ) {
      66           0 :     FD_LOG_WARNING(( "purged_max must be a power of 2" ));
      67           0 :     return NULL;
      68           0 :   }
      69             : 
      70           9 :   if( FD_UNLIKELY( !rng ) ) {
      71           0 :     FD_LOG_WARNING(( "NULL rng" ));
      72           0 :     return NULL;
      73           0 :   }
      74             : 
      75           9 :   ulong nci_origin_max = fd_ulong_pow2_up( purged_max/4UL );
      76             : 
      77           9 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
      78           9 :   fd_gossip_purged_t * purged       = FD_SCRATCH_ALLOC_APPEND( l, FD_GOSSIP_PURGED_ALIGN,          sizeof(fd_gossip_purged_t) );
      79           9 :   void * _pool                      = FD_SCRATCH_ALLOC_APPEND( l, purged_pool_align(),              purged_pool_footprint( purged_max ) );
      80           9 :   void * _map                       = FD_SCRATCH_ALLOC_APPEND( l, purged_map_align(),               purged_map_footprint( purged_map_chain_cnt_est( purged_max ) ) );
      81           9 :   void * _replaced_dlist            = FD_SCRATCH_ALLOC_APPEND( l, replaced_dlist_align(),           replaced_dlist_footprint() );
      82           9 :   void * _failed_inserts_dlist      = FD_SCRATCH_ALLOC_APPEND( l, failed_inserts_dlist_align(),     failed_inserts_dlist_footprint() );
      83           9 :   void * _nci_dlist                 = FD_SCRATCH_ALLOC_APPEND( l, no_contact_info_dlist_align(),    no_contact_info_dlist_footprint() );
      84           9 :   void * _nci_origin_map            = FD_SCRATCH_ALLOC_APPEND( l, nci_origin_map_align(),           nci_origin_map_footprint( nci_origin_max ) );
      85           9 :   void * _hset                      = FD_SCRATCH_ALLOC_APPEND( l, fd_gossip_hset_align(),           fd_gossip_hset_footprint( purged_max ) );
      86           9 :   FD_TEST( FD_SCRATCH_ALLOC_FINI( l, FD_GOSSIP_PURGED_ALIGN ) == (ulong)shmem + fd_gossip_purged_footprint( purged_max ) );
      87             : 
      88           9 :   purged->pool = purged_pool_join( purged_pool_new( _pool, purged_max ) );
      89           9 :   FD_TEST( purged->pool );
      90             : 
      91           9 :   purged->map = purged_map_join( purged_map_new( _map, purged_map_chain_cnt_est( purged_max ), fd_rng_ulong( rng ) ) );
      92           9 :   FD_TEST( purged->map );
      93             : 
      94           9 :   purged->replaced_dlist = replaced_dlist_join( replaced_dlist_new( _replaced_dlist ) );
      95           9 :   FD_TEST( purged->replaced_dlist );
      96             : 
      97           9 :   purged->failed_inserts_dlist = failed_inserts_dlist_join( failed_inserts_dlist_new( _failed_inserts_dlist ) );
      98           9 :   FD_TEST( purged->failed_inserts_dlist );
      99             : 
     100           9 :   purged->no_contact_info_dlist = no_contact_info_dlist_join( no_contact_info_dlist_new( _nci_dlist ) );
     101           9 :   FD_TEST( purged->no_contact_info_dlist );
     102             : 
     103           9 :   purged->nci_origin_map = nci_origin_map_join( nci_origin_map_new( _nci_origin_map, nci_origin_max, fd_rng_ulong( rng ) ) );
     104           9 :   FD_TEST( purged->nci_origin_map );
     105             : 
     106           9 :   purged->hset = fd_gossip_hset_join( fd_gossip_hset_new( _hset, purged_max ) );
     107           9 :   FD_TEST( purged->hset );
     108             : 
     109           9 :   memset( purged->metrics, 0, sizeof(fd_gossip_purged_metrics_t) );
     110             : 
     111           9 :   FD_COMPILER_MFENCE();
     112           9 :   FD_VOLATILE( purged->magic ) = FD_GOSSIP_PURGED_MAGIC;
     113           9 :   FD_COMPILER_MFENCE();
     114             : 
     115           9 :   return (void *)purged;
     116           9 : }
     117             : 
     118             : fd_gossip_purged_t *
     119           9 : fd_gossip_purged_join( void * shpurged ) {
     120           9 :   if( FD_UNLIKELY( !shpurged ) ) {
     121           0 :     FD_LOG_WARNING(( "NULL shpurged" ));
     122           0 :     return NULL;
     123           0 :   }
     124             : 
     125           9 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shpurged, fd_gossip_purged_align() ) ) ) {
     126           0 :     FD_LOG_WARNING(( "misaligned shpurged" ));
     127           0 :     return NULL;
     128           0 :   }
     129             : 
     130           9 :   fd_gossip_purged_t * purged = (fd_gossip_purged_t *)shpurged;
     131             : 
     132           9 :   if( FD_UNLIKELY( purged->magic!=FD_GOSSIP_PURGED_MAGIC ) ) {
     133           0 :     FD_LOG_WARNING(( "bad magic" ));
     134           0 :     return NULL;
     135           0 :   }
     136             : 
     137           9 :   return purged;
     138           9 : }
     139             : 
     140             : fd_gossip_purged_metrics_t const *
     141           0 : fd_gossip_purged_metrics( fd_gossip_purged_t const * purged ) {
     142           0 :   return purged->metrics;
     143           0 : }
     144             : 
     145             : ulong
     146       36033 : fd_gossip_purged_len( fd_gossip_purged_t const * purged ) {
     147       36033 :   return purged_pool_used( purged->pool );
     148       36033 : }
     149             : 
     150             : fd_gossip_hset_t const *
     151       12294 : fd_gossip_purged_hset( fd_gossip_purged_t const * purged ) {
     152       12294 :   return purged->hset;
     153       12294 : }
     154             : 
     155             : static inline void
     156             : purged_unindex( fd_gossip_purged_t * purged,
     157       36009 :                 fd_crds_purged_t *   ele ) {
     158       36009 :   purged_map_ele_remove_fast( purged->map, ele, purged->pool );
     159       36009 :   fd_gossip_hset_remove( purged->hset, purged_pool_idx( purged->pool, ele ) );
     160       36009 : }
     161             : 
     162             : static fd_crds_purged_t *
     163             : acquire_ele( fd_gossip_purged_t * purged,
     164             :              uchar const *        hash,
     165             :              ulong                hash_prefix,
     166       85161 :              long                 now ) {
     167       85161 :   fd_crds_purged_t * ele;
     168       85161 :   if( FD_UNLIKELY( !purged_pool_free( purged->pool ) ) ) {
     169       36000 :     if( FD_LIKELY( !replaced_dlist_is_empty( purged->replaced_dlist, purged->pool ) ) ) {
     170       36000 :       ele = replaced_dlist_ele_pop_head( purged->replaced_dlist, purged->pool );
     171       36000 :     } else if( FD_LIKELY( !failed_inserts_dlist_is_empty( purged->failed_inserts_dlist, purged->pool ) ) ) {
     172           0 :       ele = failed_inserts_dlist_ele_pop_head( purged->failed_inserts_dlist, purged->pool );
     173           0 :     } else {
     174           0 :       ele = no_contact_info_dlist_ele_pop_head( purged->no_contact_info_dlist, purged->pool );
     175           0 :       nci_origin_map_ele_remove_fast( purged->nci_origin_map, ele, purged->pool );
     176           0 :     }
     177       36000 :     purged_unindex( purged, ele );
     178       36000 :     purged->metrics->purged_evicted_cnt++;
     179       49161 :   } else {
     180       49161 :     ele = purged_pool_ele_acquire( purged->pool );
     181       49161 :     purged->metrics->purged_cnt++;
     182       49161 :   }
     183             : 
     184       85161 :   fd_memcpy( ele->hash, hash, 32UL );
     185       85161 :   ele->map.hash_prefix        = hash_prefix;
     186       85161 :   ele->expire.wallclock_nanos = now;
     187       85161 :   purged_map_ele_insert( purged->map, ele, purged->pool );
     188       85161 :   fd_gossip_hset_insert( purged->hset, purged_pool_idx( purged->pool, ele ), hash );
     189       85161 :   return ele;
     190       85161 : }
     191             : 
     192             : void
     193             : fd_gossip_purged_insert_replaced( fd_gossip_purged_t * purged,
     194             :                                   uchar const *        hash,
     195       48477 :                                   long                 now ) {
     196       48477 :   ulong hash_prefix = fd_ulong_load_8( hash );
     197       48477 :   if( FD_UNLIKELY( purged_map_ele_query_const( purged->map, &hash_prefix, NULL, purged->pool ) ) ) return;
     198             : 
     199       48477 :   fd_crds_purged_t * ele = acquire_ele( purged, hash, hash_prefix, now );
     200       48477 :   replaced_dlist_ele_push_tail( purged->replaced_dlist, ele, purged->pool );
     201       48477 : }
     202             : 
     203             : void
     204             : fd_gossip_purged_insert_failed_insert( fd_gossip_purged_t * purged,
     205             :                                        uchar const *        hash,
     206       30672 :                                        long                 now ) {
     207       30672 :   ulong hash_prefix = fd_ulong_load_8( hash );
     208       30672 :   if( FD_UNLIKELY( purged_map_ele_query_const( purged->map, &hash_prefix, NULL, purged->pool ) ) ) return;
     209             : 
     210       30666 :   fd_crds_purged_t * ele = acquire_ele( purged, hash, hash_prefix, now );
     211       30666 :   failed_inserts_dlist_ele_push_tail( purged->failed_inserts_dlist, ele, purged->pool );
     212       30666 : }
     213             : 
     214             : void
     215             : fd_gossip_purged_insert_no_contact_info( fd_gossip_purged_t * purged,
     216             :                                          uchar const *        origin,
     217             :                                          uchar const *        hash,
     218        6021 :                                          long                 now ) {
     219        6021 :   ulong hash_prefix = fd_ulong_load_8( hash );
     220        6021 :   if( FD_UNLIKELY( purged_map_ele_query_const( purged->map, &hash_prefix, NULL, purged->pool ) ) ) return;
     221             : 
     222        6018 :   fd_crds_purged_t * ele = acquire_ele( purged, hash, hash_prefix, now );
     223        6018 :   fd_memcpy( ele->origin.uc, origin, 32UL );
     224        6018 :   no_contact_info_dlist_ele_push_tail( purged->no_contact_info_dlist, ele, purged->pool );
     225        6018 :   nci_origin_map_ele_insert( purged->nci_origin_map, ele, purged->pool );
     226        6018 : }
     227             : 
     228             : void
     229             : fd_gossip_purged_drain_no_contact_info( fd_gossip_purged_t * purged,
     230           6 :                                         uchar const *        origin ) {
     231             :   /* Iterate all entries in the MAP_MULTI chain for this origin,
     232             :      removing each one.  We use idx-based iteration so we can
     233             :      capture the next index before removing the current element
     234             :      (which clobbers its chain pointers). */
     235           6 :   fd_pubkey_t const * key = (fd_pubkey_t const *)origin;
     236           6 :   ulong idx = nci_origin_map_idx_query_const( purged->nci_origin_map, key, ULONG_MAX, purged->pool );
     237           9 :   while( idx!=ULONG_MAX ) {
     238           3 :     ulong next_idx = nci_origin_map_idx_next_const( idx, ULONG_MAX, purged->pool );
     239           3 :     fd_crds_purged_t * entry = &purged->pool[ idx ];
     240             : 
     241           3 :     nci_origin_map_ele_remove_fast( purged->nci_origin_map, entry, purged->pool );
     242           3 :     no_contact_info_dlist_ele_remove( purged->no_contact_info_dlist, entry, purged->pool );
     243           3 :     purged_unindex( purged, entry );
     244           3 :     purged_pool_ele_release( purged->pool, entry );
     245             : 
     246           3 :     purged->metrics->purged_cnt--;
     247           3 :     idx = next_idx;
     248           3 :   }
     249           6 : }
     250             : 
     251             : void
     252             : fd_gossip_purged_expire( fd_gossip_purged_t * purged,
     253           6 :                          long                 now ) {
     254           9 :   while( !replaced_dlist_is_empty( purged->replaced_dlist, purged->pool ) ) {
     255           6 :     fd_crds_purged_t * head = replaced_dlist_ele_peek_head( purged->replaced_dlist, purged->pool );
     256             : 
     257           6 :     if( FD_LIKELY( head->expire.wallclock_nanos>now-REPLACED_EXPIRE_DURATION_NANOS ) ) break;
     258             : 
     259           3 :     replaced_dlist_ele_pop_head( purged->replaced_dlist, purged->pool );
     260           3 :     purged_unindex( purged, head );
     261           3 :     purged_pool_ele_release( purged->pool, head );
     262             : 
     263           3 :     purged->metrics->purged_cnt--;
     264           3 :     purged->metrics->purged_expired_cnt++;
     265           3 :   }
     266             : 
     267           9 :   while( !failed_inserts_dlist_is_empty( purged->failed_inserts_dlist, purged->pool ) ) {
     268           6 :     fd_crds_purged_t * head = failed_inserts_dlist_ele_peek_head( purged->failed_inserts_dlist, purged->pool );
     269             : 
     270             :     /* Agave uses 20 seconds here for failed inserts, but it's a little
     271             :        bit short and causes occasional bandwidth jittering.  Use 60
     272             :        seconds instead. */
     273           6 :     if( FD_LIKELY( head->expire.wallclock_nanos>now-FAILED_INSERTS_EXPIRE_DURATION_NANOS ) ) break;
     274             : 
     275           3 :     failed_inserts_dlist_ele_pop_head( purged->failed_inserts_dlist, purged->pool );
     276           3 :     purged_unindex( purged, head );
     277           3 :     purged_pool_ele_release( purged->pool, head );
     278             : 
     279           3 :     purged->metrics->purged_cnt--;
     280           3 :     purged->metrics->purged_expired_cnt++;
     281           3 :   }
     282             : 
     283           6 :   while( !no_contact_info_dlist_is_empty( purged->no_contact_info_dlist, purged->pool ) ) {
     284           3 :     fd_crds_purged_t * head = no_contact_info_dlist_ele_peek_head( purged->no_contact_info_dlist, purged->pool );
     285             : 
     286             :     /* Super long expiry time ... these don't get expired from peer
     287             :        tables since they can last up to ~2 days, so we would otherwise
     288             :        keep re-requesting these.  Reasonable to just fill the LRU with
     289             :        them and let them cycle off then, or of course once we learn the
     290             :        contact info of whoever the origin was. */
     291           3 :     if( FD_LIKELY( head->expire.wallclock_nanos>now-STAKED_EXPIRE_DURATION_NANOS ) ) break;
     292             : 
     293           0 :     no_contact_info_dlist_ele_pop_head( purged->no_contact_info_dlist, purged->pool );
     294           0 :     purged_unindex( purged, head );
     295           0 :     nci_origin_map_ele_remove_fast( purged->nci_origin_map, head, purged->pool );
     296           0 :     purged_pool_ele_release( purged->pool, head );
     297             : 
     298           0 :     purged->metrics->purged_cnt--;
     299           0 :     purged->metrics->purged_expired_cnt++;
     300           0 :   }
     301           6 : }

Generated by: LCOV version 1.14