LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_sspeer_selector.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 427 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 18 0.0 %

          Line data    Source code
       1             : #include "fd_sspeer_selector.h"
       2             : #include "../../../util/bits/fd_sat.h"
       3             : #include "../../../util/log/fd_log.h"
       4             : 
       5             : struct fd_sspeer_private {
       6             :   fd_sspeer_key_t key;
       7             :   fd_ip4_port_t addr;
       8             :   ulong         full_slot;
       9             :   ulong         incr_slot;
      10             :   uchar         full_hash[ FD_HASH_FOOTPRINT ];
      11             :   uchar         incr_hash[ FD_HASH_FOOTPRINT ];
      12             :   ulong         latency;
      13             :   ulong         score;
      14             :   int           valid;
      15             : 
      16             :   struct {
      17             :     ulong next;
      18             :   } pool;
      19             : 
      20             :   struct {
      21             :     ulong next;
      22             :     ulong prev;
      23             :   } map_by_key;
      24             : 
      25             :   struct {
      26             :     ulong next;
      27             :     ulong prev;
      28             :   } map_by_addr;
      29             : 
      30             :   struct {
      31             :     ulong parent;
      32             :     ulong left;
      33             :     ulong right;
      34             :     ulong prio;
      35             :   } score_treap;
      36             : };
      37             : 
      38             : typedef struct fd_sspeer_private fd_sspeer_private_t;
      39             : 
      40             : #define POOL_NAME  peer_pool
      41           0 : #define POOL_T     fd_sspeer_private_t
      42             : #define POOL_IDX_T ulong
      43           0 : #define POOL_NEXT  pool.next
      44             : #include "../../../util/tmpl/fd_pool.c"
      45             : 
      46             : #define MAP_NAME               peer_map_by_key
      47           0 : #define MAP_KEY                key
      48           0 : #define MAP_ELE_T              fd_sspeer_private_t
      49             : #define MAP_KEY_T              fd_sspeer_key_t
      50           0 : #define MAP_PREV               map_by_key.prev
      51           0 : #define MAP_NEXT               map_by_key.next
      52           0 : #define MAP_KEY_EQ(k0,k1)      (fd_sspeer_key_eq(k0,k1))
      53           0 : #define MAP_KEY_HASH(key,seed) (fd_sspeer_key_hash(key,seed))
      54             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      55             : #include "../../../util/tmpl/fd_map_chain.c"
      56             : 
      57             : #define MAP_NAME               peer_map_by_addr
      58           0 : #define MAP_KEY                addr
      59           0 : #define MAP_ELE_T              fd_sspeer_private_t
      60             : #define MAP_KEY_T              fd_ip4_port_t
      61           0 : #define MAP_PREV               map_by_addr.prev
      62           0 : #define MAP_NEXT               map_by_addr.next
      63           0 : #define MAP_KEY_EQ(k0,k1)      ((k0)->l==(k1)->l)
      64           0 : #define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (seed)^(key)->l ))
      65             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
      66             : #define MAP_MULTI              1
      67             : #include "../../../util/tmpl/fd_map_chain.c"
      68             : 
      69           0 : #define COMPARE_WORSE(x,y) ( (x)->score<(y)->score )
      70             : 
      71             : #define TREAP_T         fd_sspeer_private_t
      72             : #define TREAP_NAME      score_treap
      73             : #define TREAP_QUERY_T   void *                                         /* We don't use query ... */
      74             : #define TREAP_CMP(a,b)  (__extension__({ (void)(a); (void)(b); -1; })) /* which means we don't need to give a real
      75             :                                                                           implementation to cmp either */
      76           0 : #define TREAP_IDX_T     ulong
      77           0 : #define TREAP_LT        COMPARE_WORSE
      78           0 : #define TREAP_PARENT    score_treap.parent
      79           0 : #define TREAP_LEFT      score_treap.left
      80           0 : #define TREAP_RIGHT     score_treap.right
      81           0 : #define TREAP_PRIO      score_treap.prio
      82             : #include "../../../util/tmpl/fd_treap.c"
      83             : 
      84           0 : #define DEFAULT_SLOTS_BEHIND         (1000UL*1000UL) /* 1,000,000 slots behind */
      85             : /* Intentionally less than DEFAULT_SLOTS_BEHIND so that peers with an
      86             :    unknown slot are penalized more than the worst known peer. */
      87           0 : #define MAX_SLOTS_BEHIND_CAP         (864000UL)      /* ~2 epochs worth of slots */
      88             : /* Assumed latency (in nanos) for peers that have not been pinged yet.
      89             :    Pings are sent immediately on peer discovery, so this default is
      90             :    short-lived.  100ms is a neutral middle-ground: high enough that
      91             :    any peer with a measured latency is preferred, low enough that slot
      92             :    distance still meaningfully differentiates unpinged peers. */
      93           0 : #define DEFAULT_PEER_LATENCY         (100UL*1000UL*1000UL)  /* 100ms */
      94           0 : #define DEFAULT_SLOTS_BEHIND_PENALTY (200UL)
      95             : 
      96             : #define FD_SSPEER_SELECTOR_DEBUG 0
      97             : 
      98             : struct fd_sspeer_selector_private {
      99             :   fd_sspeer_private_t *     pool;
     100             :   peer_map_by_key_t *       map_by_key;
     101             :   peer_map_by_addr_t *      map_by_addr;
     102             :   score_treap_t *           score_treap;
     103             :   score_treap_t *           shadow_score_treap;
     104             :   ulong *                   peer_idx_list;
     105             :   fd_sscluster_slot_t       cluster_slot;
     106             :   ulong                     max_peers;
     107             :   int                       cluster_slot_dirty;
     108             : 
     109             :   ulong                     magic; /* ==FD_SSPEER_SELECTOR_MAGIC */
     110             : };
     111             : 
     112             : FD_FN_CONST ulong
     113           0 : fd_sspeer_selector_align( void ) {
     114           0 :   return fd_ulong_max( alignof( fd_sspeer_selector_t), fd_ulong_max( peer_pool_align(),
     115           0 :           fd_ulong_max( peer_map_by_key_align(), fd_ulong_max( peer_map_by_addr_align(),
     116           0 :           fd_ulong_max( score_treap_align(), alignof(ulong) ) ) ) ) );
     117           0 : }
     118             : 
     119             : FD_FN_CONST ulong
     120           0 : fd_sspeer_selector_footprint( ulong max_peers ) {
     121           0 :   ulong l;
     122           0 :   l = FD_LAYOUT_INIT;
     123           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sspeer_selector_t), sizeof(fd_sspeer_selector_t) );
     124           0 :   l = FD_LAYOUT_APPEND( l, peer_pool_align(),             peer_pool_footprint( 2UL*max_peers ) );
     125           0 :   l = FD_LAYOUT_APPEND( l, peer_map_by_key_align(),       peer_map_by_key_footprint( peer_map_by_key_chain_cnt_est( 2UL*max_peers ) ) );
     126           0 :   l = FD_LAYOUT_APPEND( l, peer_map_by_addr_align(),      peer_map_by_addr_footprint( peer_map_by_addr_chain_cnt_est( 2UL*max_peers ) ) );
     127           0 :   l = FD_LAYOUT_APPEND( l, score_treap_align(),           score_treap_footprint( max_peers ) );
     128           0 :   l = FD_LAYOUT_APPEND( l, score_treap_align(),           score_treap_footprint( max_peers ) );
     129           0 :   l = FD_LAYOUT_APPEND( l, alignof(ulong),                max_peers * sizeof(ulong) ); /* pool indices for shadow treap swap */
     130           0 :   return FD_LAYOUT_FINI( l, fd_sspeer_selector_align() );
     131           0 : }
     132             : 
     133             : void *
     134             : fd_sspeer_selector_new( void * shmem,
     135             :                         ulong  max_peers,
     136           0 :                         ulong  seed ) {
     137           0 :   if( FD_UNLIKELY( !shmem ) ) {
     138           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     139           0 :     return NULL;
     140           0 :   }
     141             : 
     142           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sspeer_selector_align() ) ) ) {
     143           0 :     FD_LOG_WARNING(( "unaligned shmem" ));
     144           0 :     return NULL;
     145           0 :   }
     146             : 
     147           0 :   if( FD_UNLIKELY( max_peers < 1UL ) ) {
     148           0 :     FD_LOG_WARNING(( "max_peers must be at least 1" ));
     149           0 :     return NULL;
     150           0 :   }
     151             : 
     152           0 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     153           0 :   fd_sspeer_selector_t * selector = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sspeer_selector_t), sizeof(fd_sspeer_selector_t) );
     154           0 :   void * _pool                    = FD_SCRATCH_ALLOC_APPEND( l, peer_pool_align(),        peer_pool_footprint( 2UL*max_peers ) );
     155           0 :   void * _map                     = FD_SCRATCH_ALLOC_APPEND( l, peer_map_by_key_align(),  peer_map_by_key_footprint( peer_map_by_key_chain_cnt_est( 2UL*max_peers ) )  );
     156           0 :   void * _multimap_by_addr        = FD_SCRATCH_ALLOC_APPEND( l, peer_map_by_addr_align(), peer_map_by_addr_footprint( peer_map_by_addr_chain_cnt_est( 2UL*max_peers ) )  );
     157           0 :   void * _score_treap             = FD_SCRATCH_ALLOC_APPEND( l, score_treap_align(),      score_treap_footprint( max_peers ) );
     158           0 :   void * _shadow_score_treap      = FD_SCRATCH_ALLOC_APPEND( l, score_treap_align(),      score_treap_footprint( max_peers ) );
     159           0 :   void * _peer_idx_list           = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong),           max_peers * sizeof(ulong) ); /* pool indices for shadow treap swap */
     160             : 
     161           0 :   selector->pool               = peer_pool_join( peer_pool_new( _pool, 2UL*max_peers ) );
     162             :   /* Seed treap priorities so the treap is balanced. */
     163           0 :   score_treap_seed( selector->pool, 2UL*max_peers, seed );
     164           0 :   selector->map_by_key         = peer_map_by_key_join( peer_map_by_key_new( _map, peer_map_by_key_chain_cnt_est( 2UL*max_peers ), seed ) );
     165           0 :   selector->map_by_addr        = peer_map_by_addr_join( peer_map_by_addr_new( _multimap_by_addr, peer_map_by_addr_chain_cnt_est( 2UL*max_peers ), seed ) );
     166           0 :   selector->score_treap        = score_treap_join( score_treap_new( _score_treap, max_peers ) );
     167           0 :   selector->shadow_score_treap = score_treap_join( score_treap_new( _shadow_score_treap, max_peers ) );
     168           0 :   selector->peer_idx_list      = (ulong *)_peer_idx_list;
     169           0 :   selector->max_peers          = max_peers;
     170             : 
     171           0 :   selector->cluster_slot.full          = 0UL;
     172           0 :   selector->cluster_slot.incremental   = FD_SSPEER_SLOT_UNKNOWN;
     173           0 :   selector->cluster_slot_dirty         = 0;
     174             : 
     175           0 :   FD_COMPILER_MFENCE();
     176           0 :   FD_VOLATILE( selector->magic ) = FD_SSPEER_SELECTOR_MAGIC;
     177           0 :   FD_COMPILER_MFENCE();
     178             : 
     179           0 :   return (void *)selector;
     180           0 : }
     181             : 
     182             : fd_sspeer_selector_t *
     183           0 : fd_sspeer_selector_join( void * shselector ) {
     184           0 :   if( FD_UNLIKELY( !shselector ) ) {
     185           0 :     FD_LOG_WARNING(( "NULL shselector" ));
     186           0 :     return NULL;
     187           0 :   }
     188             : 
     189           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shselector, fd_sspeer_selector_align() ) ) ) {
     190           0 :     FD_LOG_WARNING(( "misaligned shselector" ));
     191           0 :     return NULL;
     192           0 :   }
     193             : 
     194           0 :   fd_sspeer_selector_t * selector = (fd_sspeer_selector_t *)shselector;
     195             : 
     196           0 :   if( FD_UNLIKELY( selector->magic!=FD_SSPEER_SELECTOR_MAGIC ) ) {
     197           0 :     FD_LOG_WARNING(( "bad magic" ));
     198           0 :     return NULL;
     199           0 :   }
     200             : 
     201           0 :   return selector;
     202           0 : }
     203             : 
     204             : void *
     205           0 : fd_sspeer_selector_leave( fd_sspeer_selector_t * selector ) {
     206           0 :   if( FD_UNLIKELY( !selector ) ) {
     207           0 :     FD_LOG_WARNING(( "NULL selector" ));
     208           0 :     return NULL;
     209           0 :   }
     210             : 
     211           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)selector, fd_sspeer_selector_align() ) ) ) {
     212           0 :     FD_LOG_WARNING(( "misaligned selector" ));
     213           0 :     return NULL;
     214           0 :   }
     215             : 
     216           0 :   if( FD_UNLIKELY( selector->magic!=FD_SSPEER_SELECTOR_MAGIC ) ) {
     217           0 :     FD_LOG_WARNING(( "bad magic" ));
     218           0 :     return NULL;
     219           0 :   }
     220             : 
     221           0 :   selector->pool               = peer_pool_leave( selector->pool );
     222           0 :   selector->map_by_key         = peer_map_by_key_leave( selector->map_by_key );
     223           0 :   selector->map_by_addr        = peer_map_by_addr_leave( selector->map_by_addr );
     224           0 :   selector->score_treap        = score_treap_leave( selector->score_treap );
     225           0 :   selector->shadow_score_treap = score_treap_leave( selector->shadow_score_treap );
     226             : 
     227           0 :   return (void *)selector;
     228           0 : }
     229             : 
     230             : void *
     231           0 : fd_sspeer_selector_delete( void * shselector ) {
     232           0 :   if( FD_UNLIKELY( !shselector ) ) {
     233           0 :     FD_LOG_WARNING(( "NULL shselector" ));
     234           0 :     return NULL;
     235           0 :   }
     236             : 
     237           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shselector, fd_sspeer_selector_align() ) ) ) {
     238           0 :     FD_LOG_WARNING(( "misaligned shselector" ));
     239           0 :     return NULL;
     240           0 :   }
     241             : 
     242           0 :   fd_sspeer_selector_t * selector = (fd_sspeer_selector_t *)shselector;
     243             : 
     244           0 :   if( FD_UNLIKELY( selector->magic!=FD_SSPEER_SELECTOR_MAGIC ) ) {
     245           0 :     FD_LOG_WARNING(( "bad magic" ));
     246           0 :     return NULL;
     247           0 :   }
     248             : 
     249           0 :   selector->pool               = peer_pool_delete( selector->pool );
     250           0 :   selector->map_by_key         = peer_map_by_key_delete( selector->map_by_key );
     251           0 :   selector->map_by_addr        = peer_map_by_addr_delete( selector->map_by_addr );
     252           0 :   selector->score_treap        = score_treap_delete( selector->score_treap );
     253           0 :   selector->shadow_score_treap = score_treap_delete( selector->shadow_score_treap );
     254             : 
     255           0 :   FD_COMPILER_MFENCE();
     256           0 :   FD_VOLATILE( selector->magic ) = 0UL;
     257           0 :   FD_COMPILER_MFENCE();
     258             : 
     259           0 :   return (void *)selector;
     260           0 : }
     261             : 
     262             : /* Calculates a score for a peer given its latency and its resolved
     263             :    full and incremental slots */
     264             : static ulong
     265             : fd_sspeer_selector_score( fd_sspeer_selector_t const * selector,
     266             :                           ulong                        peer_latency,
     267             :                           ulong                        full_slot,
     268           0 :                           ulong                        incr_slot ) {
     269           0 :   peer_latency = peer_latency!=FD_SSPEER_LATENCY_UNKNOWN ? peer_latency : DEFAULT_PEER_LATENCY;
     270             : 
     271           0 :   ulong slots_behind = DEFAULT_SLOTS_BEHIND;
     272             : 
     273           0 :   if( FD_LIKELY( full_slot!=FD_SSPEER_SLOT_UNKNOWN ) ) {
     274           0 :     if( FD_LIKELY( incr_slot!=FD_SSPEER_SLOT_UNKNOWN &&
     275           0 :                    selector->cluster_slot.incremental!=FD_SSPEER_SLOT_UNKNOWN ) ) {
     276           0 :       slots_behind = selector->cluster_slot.incremental>incr_slot ? selector->cluster_slot.incremental - incr_slot : 0UL;
     277           0 :     } else {
     278             :       /* Either the peer has no incremental or the cluster has no
     279             :          incremental reference yet.  Fall back to comparing full_slot
     280             :          against the cluster full slot. */
     281           0 :       slots_behind = selector->cluster_slot.full>full_slot ? selector->cluster_slot.full - full_slot : 0UL;
     282           0 :     }
     283           0 :     slots_behind = fd_ulong_min( slots_behind, MAX_SLOTS_BEHIND_CAP );
     284           0 :   }
     285             : 
     286             :   /* Using saturating arithmetic to avoid overflow and cap at
     287             :      FD_SSPEER_SCORE_MAX. */
     288           0 :   ulong penalty = fd_ulong_sat_mul( DEFAULT_SLOTS_BEHIND_PENALTY,
     289           0 :                   fd_ulong_sat_mul( slots_behind, slots_behind ) );
     290           0 :   ulong score   = fd_ulong_sat_add( peer_latency, penalty );
     291           0 :   return fd_ulong_min( score, FD_SSPEER_SCORE_MAX );
     292           0 : }
     293             : 
     294             : /* Validates slot arguments for both new and existing peers.  Returns
     295             :    0 on success, -1 on failure due to incr_slot<full_slot, and -2 on
     296             :    failure due to full_slot==UNKNOWN with incr_slot!=UNKNOWN.  The
     297             :    caller passes in the effective (already-resolved) full_slot and
     298             :    incr_slot values.  No log on failure (the caller is responsible
     299             :    for logging whenever needed).
     300             : 
     301             :    Two invariants are enforced:
     302             :    1. When both slots are known, incr_slot must be >= full_slot.
     303             :    2. An incremental slot requires a known full slot. */
     304             : static int
     305             : fd_sspeer_validate_slot_args( ulong full_slot,
     306           0 :                               ulong incr_slot ) {
     307           0 :   if( FD_UNLIKELY( incr_slot!=FD_SSPEER_SLOT_UNKNOWN &&
     308           0 :                    full_slot!=FD_SSPEER_SLOT_UNKNOWN &&
     309           0 :                    incr_slot<full_slot ) ) {
     310           0 :     return -1;
     311           0 :   }
     312             : 
     313           0 :   if( FD_UNLIKELY( full_slot==FD_SSPEER_SLOT_UNKNOWN &&
     314           0 :                    incr_slot!=FD_SSPEER_SLOT_UNKNOWN ) ) {
     315           0 :     return -2;
     316           0 :   }
     317             : 
     318           0 :   return 0;
     319           0 : }
     320             : 
     321             : /* Updates a peer's score with new values for latency and/or resolved
     322             :    full/incremental slots.  Returns FD_SSPEER_UPDATE_SUCCESS on
     323             :    success, or the specific fd_sspeer_validate_slot_args error code
     324             :    on failure without modifying the peer or any data structure.
     325             : 
     326             :    Slot-based incremental clearing: when the caller provides
     327             :    incr_slot==UNKNOWN and full_slot!=UNKNOWN, the peer's existing
     328             :    incremental data is cleared if it is stale (peer->incr_slot <
     329             :    full_slot).  Otherwise, the existing incremental data is preserved. */
     330             : static int
     331             : fd_sspeer_selector_update( fd_sspeer_selector_t * selector,
     332             :                            fd_sspeer_private_t *  peer,
     333             :                            ulong                  latency,
     334             :                            ulong                  full_slot,
     335             :                            ulong                  incr_slot,
     336             :                            uchar const            full_hash[ FD_HASH_FOOTPRINT ],
     337           0 :                            uchar const            incr_hash[ FD_HASH_FOOTPRINT ] ) {
     338           0 :   ulong peer_latency   = latency!=FD_SSPEER_LATENCY_UNKNOWN ? latency : peer->latency;
     339           0 :   ulong peer_full_slot = full_slot!=FD_SSPEER_SLOT_UNKNOWN ? full_slot : peer->full_slot;
     340             : 
     341           0 :   ulong peer_incr_slot;
     342           0 :   int   clear_incr = 0;
     343           0 :   if( incr_slot!=FD_SSPEER_SLOT_UNKNOWN ) {
     344           0 :     peer_incr_slot = incr_slot;
     345           0 :   } else if( full_slot!=FD_SSPEER_SLOT_UNKNOWN &&
     346           0 :              peer->incr_slot!=FD_SSPEER_SLOT_UNKNOWN &&
     347           0 :              peer->incr_slot<full_slot ) {
     348             :     /* The caller is providing a new full_slot that has advanced past
     349             :        the peer's existing incremental, the incremental is stale. */
     350           0 :     peer_incr_slot = FD_SSPEER_SLOT_UNKNOWN;
     351           0 :     clear_incr     = 1;
     352           0 :   } else {
     353           0 :     peer_incr_slot = peer->incr_slot;
     354           0 :   }
     355             : 
     356           0 :   int validate_err = fd_sspeer_validate_slot_args( peer_full_slot, peer_incr_slot );
     357           0 :   if( FD_UNLIKELY( validate_err ) ) return validate_err;
     358             : 
     359           0 :   score_treap_ele_remove( selector->score_treap, peer, selector->pool );
     360             : 
     361           0 :   peer->score = fd_sspeer_selector_score( selector, peer_latency, peer_full_slot, peer_incr_slot );
     362             : 
     363           0 :   peer->latency   = peer_latency;
     364           0 :   peer->full_slot = peer_full_slot;
     365           0 :   peer->incr_slot = peer_incr_slot;
     366           0 :   if( FD_LIKELY( full_hash ) ) {
     367           0 :     fd_memcpy( peer->full_hash, full_hash, FD_HASH_FOOTPRINT );
     368           0 :   }
     369           0 :   if( FD_UNLIKELY( clear_incr ) ) {
     370           0 :     fd_memset( peer->incr_hash, 0, FD_HASH_FOOTPRINT );
     371           0 :   } else if( FD_LIKELY( incr_hash ) ) {
     372           0 :     fd_memcpy( peer->incr_hash, incr_hash, FD_HASH_FOOTPRINT );
     373           0 :   }
     374             : 
     375           0 :   score_treap_ele_insert( selector->score_treap, peer, selector->pool );
     376           0 :   return FD_SSPEER_UPDATE_SUCCESS;
     377           0 : }
     378             : 
     379             : ulong
     380             : fd_sspeer_selector_update_on_ping( fd_sspeer_selector_t * selector,
     381             :                                    fd_ip4_port_t          addr,
     382           0 :                                    ulong                  latency ) {
     383           0 :   ulong ele_idx = peer_map_by_addr_idx_query_const( selector->map_by_addr, &addr, ULONG_MAX, selector->pool );
     384           0 :   ulong cnt = 0UL;
     385           0 :   for(;;) {
     386           0 :     if( FD_UNLIKELY( ele_idx==ULONG_MAX ) ) break;
     387           0 :     fd_sspeer_private_t * peer = selector->pool + ele_idx;
     388             :     /* Update cannot fail here: slots are FD_SSPEER_SLOT_UNKNOWN and
     389             :        hashes are NULL, so fd_sspeer_validate_slot_args always
     390             :        returns FD_SSPEER_UPDATE_SUCCESS (no clear_incr trigger,
     391             :        no incr<full violation). */
     392           0 :     int update_status = fd_sspeer_selector_update( selector, peer, latency,
     393           0 :                                                    FD_SSPEER_SLOT_UNKNOWN, FD_SSPEER_SLOT_UNKNOWN,
     394           0 :                                                    NULL, NULL );
     395           0 :     if( FD_UNLIKELY( update_status!=FD_SSPEER_UPDATE_SUCCESS ) ) {
     396             :       /* A warning is a tradeoff between crashing with FD_LOG_CRIT and
     397             :          potentially missing the log altogether with FD_LOG_DEBUG. */
     398           0 :       if( peer->key.is_url ) {
     399           0 :         FD_LOG_WARNING(( "unexpected selector update returned %d for peer %s " FD_IP4_ADDR_FMT ":%hu",
     400           0 :                          update_status, peer->key.url.hostname,
     401           0 :                          FD_IP4_ADDR_FMT_ARGS( peer->key.url.resolved_addr.addr ), fd_ushort_bswap( peer->key.url.resolved_addr.port ) ));
     402           0 :       } else {
     403           0 :         FD_BASE58_ENCODE_32_BYTES( peer->key.pubkey->uc, peer_pubkey_b58 );
     404           0 :         FD_LOG_WARNING(( "unexpected selector update returned %d for peer %s " FD_IP4_ADDR_FMT ":%hu",
     405           0 :                          update_status, peer_pubkey_b58,
     406           0 :                          FD_IP4_ADDR_FMT_ARGS( peer->addr.addr ), fd_ushort_bswap( peer->addr.port ) ));
     407           0 :       }
     408           0 :     }
     409           0 :     ele_idx = peer_map_by_addr_idx_next_const( ele_idx, ULONG_MAX, selector->pool );
     410           0 :     cnt++;
     411           0 :   }
     412           0 :   return cnt;
     413           0 : }
     414             : 
     415             : ulong
     416             : fd_sspeer_selector_add( fd_sspeer_selector_t * selector,
     417             :                         fd_sspeer_key_t const * key,
     418             :                         fd_ip4_port_t          addr,
     419             :                         ulong                  latency,
     420             :                         ulong                  full_slot,
     421             :                         ulong                  incr_slot,
     422             :                         uchar const            full_hash[ FD_HASH_FOOTPRINT ],
     423           0 :                         uchar const            incr_hash[ FD_HASH_FOOTPRINT ] ) {
     424           0 :   if( FD_UNLIKELY( key==NULL ) ) return FD_SSPEER_SCORE_INVALID;
     425             :   /* A peer without a valid address cannot be added to the selector.
     426             :      For an existing peer changing from a valid address to 0, it is
     427             :      the caller's responsibility to remove them. */
     428           0 :   if( FD_UNLIKELY( !addr.l ) ) return FD_SSPEER_SCORE_INVALID;
     429             : 
     430             :   /* Reject implausible slot values.  FD_SSPEER_SLOT_UNKNOWN is allowed. */
     431           0 :   if( FD_UNLIKELY( full_slot!=FD_SSPEER_SLOT_UNKNOWN && full_slot>=FD_SSPEER_PLAUSIBLE_MAX_SLOT ) ) return FD_SSPEER_SCORE_INVALID;
     432           0 :   if( FD_UNLIKELY( incr_slot!=FD_SSPEER_SLOT_UNKNOWN && incr_slot>=FD_SSPEER_PLAUSIBLE_MAX_SLOT ) ) return FD_SSPEER_SCORE_INVALID;
     433             : 
     434           0 :   fd_sspeer_private_t * peer = peer_map_by_key_ele_query( selector->map_by_key, key, NULL, selector->pool );
     435           0 :   if( FD_LIKELY( peer ) ) {
     436           0 :     ulong old_full = peer->full_slot;
     437           0 :     ulong old_incr = peer->incr_slot;
     438           0 :     int update_status = fd_sspeer_selector_update( selector, peer, latency, full_slot, incr_slot, full_hash, incr_hash );
     439           0 :     if( FD_UNLIKELY( update_status!=FD_SSPEER_UPDATE_SUCCESS ) ) return FD_SSPEER_SCORE_INVALID;
     440           0 :     if( FD_UNLIKELY( peer->full_slot!=old_full || peer->incr_slot!=old_incr ) ) selector->cluster_slot_dirty = 1;
     441             :     /* Update the addr map after the selector update so that the peer
     442             :        is not mutated when the update fails. */
     443           0 :     if( FD_UNLIKELY( peer->addr.l!=addr.l ) ) {
     444           0 :       peer_map_by_addr_ele_remove_fast( selector->map_by_addr, peer, selector->pool );
     445           0 :       peer->addr = addr;
     446           0 :       peer_map_by_addr_ele_insert( selector->map_by_addr, peer, selector->pool );
     447           0 :     }
     448           0 :   } else {
     449           0 :     if( FD_UNLIKELY( !peer_pool_free( selector->pool ) ) ) {
     450           0 :       FD_LOG_WARNING(( "peer selector pool exhausted" ));
     451           0 :       return FD_SSPEER_SCORE_INVALID;
     452           0 :     }
     453           0 :     if( FD_UNLIKELY( score_treap_ele_cnt(selector->score_treap)>=selector->max_peers ) ) {
     454           0 :       FD_LOG_WARNING(( "peer selector at max capacity" ));
     455           0 :       return FD_SSPEER_SCORE_INVALID;
     456           0 :     }
     457             : 
     458           0 :     if( FD_UNLIKELY( fd_sspeer_validate_slot_args( full_slot, incr_slot ) ) ) {
     459           0 :       return FD_SSPEER_SCORE_INVALID;
     460           0 :     }
     461             : 
     462           0 :     peer = peer_pool_ele_acquire( selector->pool );
     463           0 :     peer->key       = *key;
     464           0 :     peer->addr      = addr;
     465           0 :     peer->latency   = latency;
     466           0 :     peer->score     = fd_sspeer_selector_score( selector, latency, full_slot, incr_slot );
     467           0 :     peer->full_slot = full_slot;
     468           0 :     peer->incr_slot = incr_slot;
     469           0 :     if( FD_LIKELY( full_hash ) ) fd_memcpy( peer->full_hash, full_hash, FD_HASH_FOOTPRINT );
     470           0 :     else                         fd_memset( peer->full_hash, 0, FD_HASH_FOOTPRINT );
     471             :     /* full_hash and incr_hash are treated independently here. */
     472           0 :     if( FD_LIKELY( incr_hash ) ) fd_memcpy( peer->incr_hash, incr_hash, FD_HASH_FOOTPRINT );
     473           0 :     else                         fd_memset( peer->incr_hash, 0, FD_HASH_FOOTPRINT );
     474           0 :     peer_map_by_key_ele_insert( selector->map_by_key, peer, selector->pool );
     475           0 :     peer_map_by_addr_ele_insert( selector->map_by_addr, peer, selector->pool );
     476           0 :     score_treap_ele_insert( selector->score_treap, peer, selector->pool );
     477           0 :     if( FD_LIKELY( full_slot!=FD_SSPEER_SLOT_UNKNOWN || incr_slot!=FD_SSPEER_SLOT_UNKNOWN ) ) selector->cluster_slot_dirty = 1;
     478           0 :   }
     479           0 :   peer->valid = peer->full_slot!=FD_SSPEER_SLOT_UNKNOWN;
     480           0 :   return peer->score;
     481           0 : }
     482             : 
     483             : void
     484             : fd_sspeer_selector_remove( fd_sspeer_selector_t * selector,
     485           0 :                            fd_sspeer_key_t const * key ) {
     486           0 :   if( FD_UNLIKELY( key==NULL ) ) return;
     487           0 :   fd_sspeer_private_t * peer = peer_map_by_key_ele_query( selector->map_by_key, key, NULL, selector->pool );
     488           0 :   if( FD_UNLIKELY( peer==NULL ) ) return;
     489           0 :   score_treap_ele_remove( selector->score_treap, peer, selector->pool );
     490           0 :   peer_map_by_key_ele_remove_fast( selector->map_by_key, peer, selector->pool );
     491           0 :   peer_map_by_addr_ele_remove_fast( selector->map_by_addr, peer, selector->pool );
     492           0 :   peer_pool_ele_release( selector->pool, peer );
     493           0 :   selector->cluster_slot_dirty = 1;
     494           0 : }
     495             : 
     496             : void
     497             : fd_sspeer_selector_remove_by_addr( fd_sspeer_selector_t * selector,
     498           0 :                                    fd_ip4_port_t          addr ) {
     499           0 :   for(;;) {
     500           0 :     fd_sspeer_private_t * peer = peer_map_by_addr_ele_remove( selector->map_by_addr, &addr, NULL, selector->pool );
     501           0 :     if( FD_UNLIKELY( peer==NULL ) ) break;
     502           0 :     score_treap_ele_remove( selector->score_treap, peer, selector->pool );
     503           0 :     peer_map_by_key_ele_remove_fast( selector->map_by_key, peer, selector->pool );
     504           0 :     peer_pool_ele_release( selector->pool, peer );
     505           0 :     selector->cluster_slot_dirty = 1;
     506           0 :   }
     507           0 : }
     508             : 
     509             : fd_sspeer_t
     510             : fd_sspeer_selector_best( fd_sspeer_selector_t * selector,
     511             :                          int                    incremental,
     512             :                          ulong                  base_slot,
     513           0 :                          ulong                  min_reachable_slot ) {
     514           0 :   if( FD_UNLIKELY( incremental && base_slot==FD_SSPEER_SLOT_UNKNOWN ) ) {
     515           0 :     FD_LOG_WARNING(( "incremental selection requires a valid base_slot" ));
     516           0 :     return (fd_sspeer_t){
     517           0 :       .key       = { .is_url = 0 },
     518           0 :       .addr      = { .l=0UL },
     519           0 :       .full_slot = FD_SSPEER_SLOT_UNKNOWN,
     520           0 :       .incr_slot = FD_SSPEER_SLOT_UNKNOWN,
     521           0 :       .score     = FD_SSPEER_SCORE_INVALID,
     522           0 :       .full_hash = {0},
     523           0 :       .incr_hash = {0},
     524           0 :     };
     525           0 :   }
     526             : 
     527           0 :   for( score_treap_fwd_iter_t iter = score_treap_fwd_iter_init( selector->score_treap, selector->pool );
     528           0 :        !score_treap_fwd_iter_done( iter );
     529           0 :        iter = score_treap_fwd_iter_next( iter, selector->pool ) ) {
     530           0 :     fd_sspeer_private_t const * peer = score_treap_fwd_iter_ele_const( iter, selector->pool );
     531             :     /* For full selection (!incremental), any valid peer is eligible.
     532             :        For incremental selection, the peer must serve the same base full
     533             :        snapshot and must actually offer an incremental snapshot. */
     534           0 :     ulong reachable_slot = fd_ulong_if( peer->incr_slot!=FD_SSPEER_SLOT_UNKNOWN, peer->incr_slot, peer->full_slot );
     535           0 :     if( FD_LIKELY( peer->valid &&
     536           0 :                    reachable_slot>=min_reachable_slot &&
     537           0 :                    (!incremental ||
     538           0 :                    (peer->full_slot==base_slot && peer->incr_slot!=FD_SSPEER_SLOT_UNKNOWN) ) ) ) {
     539           0 :       fd_sspeer_t best = {
     540           0 :         .key       = peer->key,
     541           0 :         .addr      = peer->addr,
     542           0 :         .full_slot = peer->full_slot,
     543           0 :         .incr_slot = peer->incr_slot,
     544           0 :         .score     = peer->score,
     545           0 :       };
     546           0 :       fd_memcpy( best.full_hash, peer->full_hash, FD_HASH_FOOTPRINT );
     547           0 :       fd_memcpy( best.incr_hash, peer->incr_hash, FD_HASH_FOOTPRINT );
     548           0 :       return best;
     549           0 :     }
     550           0 :   }
     551             : 
     552           0 :   return (fd_sspeer_t){
     553           0 :     .key       = { .is_url = 0 },
     554           0 :     .addr      = { .l=0UL },
     555           0 :     .full_slot = FD_SSPEER_SLOT_UNKNOWN,
     556           0 :     .incr_slot = FD_SSPEER_SLOT_UNKNOWN,
     557           0 :     .score     = FD_SSPEER_SCORE_INVALID,
     558           0 :     .full_hash = {0},
     559           0 :     .incr_hash = {0},
     560           0 :   };
     561           0 : }
     562             : 
     563             : void
     564           0 : fd_sspeer_selector_process_cluster_slot( fd_sspeer_selector_t * selector ) {
     565           0 :   if( FD_LIKELY( !selector->cluster_slot_dirty ) ) return;
     566           0 :   selector->cluster_slot_dirty = 0;
     567             : 
     568             :   /* Compute max full_slot and incr_slot across all tracked peers. */
     569           0 :   ulong max_full = 0UL;
     570           0 :   ulong max_incr = 0UL;
     571           0 :   ulong full_cnt = 0UL;
     572           0 :   ulong incr_cnt = 0UL;
     573           0 :   for( score_treap_fwd_iter_t iter = score_treap_fwd_iter_init( selector->score_treap, selector->pool );
     574           0 :        !score_treap_fwd_iter_done( iter );
     575           0 :        iter = score_treap_fwd_iter_next( iter, selector->pool ) ) {
     576           0 :     fd_sspeer_private_t const * peer = score_treap_fwd_iter_ele_const( iter, selector->pool );
     577           0 :     if( FD_LIKELY( peer->full_slot!=FD_SSPEER_SLOT_UNKNOWN ) ) {
     578           0 :       max_full = fd_ulong_max( max_full, peer->full_slot );
     579           0 :       full_cnt++;
     580           0 :     }
     581           0 :     if( FD_LIKELY( peer->incr_slot!=FD_SSPEER_SLOT_UNKNOWN ) ) {
     582           0 :       max_incr = fd_ulong_max( max_incr, peer->incr_slot );
     583           0 :       incr_cnt++;
     584           0 :     }
     585           0 :   }
     586             : 
     587             :   /* If no known incr_slot among tracked peers, treat as unknown. */
     588           0 :   if( FD_UNLIKELY( incr_cnt==0UL ) ) {
     589           0 :     max_incr = FD_SSPEER_SLOT_UNKNOWN;
     590           0 :   }
     591             : 
     592             :   /* If no known full_slot among tracked peers, reset so that a
     593             :      subsequent add() is not scored against a stale cluster slot. */
     594           0 :   if( FD_UNLIKELY( full_cnt==0UL ) ) {
     595           0 :     selector->cluster_slot.full        = 0UL;
     596           0 :     selector->cluster_slot.incremental = FD_SSPEER_SLOT_UNKNOWN;
     597           0 :     return;
     598           0 :   }
     599             : 
     600             :   /* The full and incremental maxima are computed from different peer
     601             :      subsets, so max_incr < max_full is possible.  Clamp to maintain
     602             :      the invariant that incremental >= full. */
     603           0 :   if( FD_UNLIKELY( max_incr!=FD_SSPEER_SLOT_UNKNOWN &&
     604           0 :                    max_incr<max_full ) ) {
     605           0 :     max_incr = max_full;
     606           0 :   }
     607             : 
     608             :   /* If neither max changed, no rescore needed. */
     609           0 :   if( FD_LIKELY( max_full==selector->cluster_slot.full &&
     610           0 :                  max_incr==selector->cluster_slot.incremental ) ) return;
     611             : 
     612           0 :   selector->cluster_slot.full        = max_full;
     613           0 :   selector->cluster_slot.incremental = max_incr;
     614             : 
     615             :   /* Rescore all peers using the shadow treap swap mechanism. */
     616           0 :   ulong idx = 0UL;
     617           0 :   for( score_treap_fwd_iter_t iter = score_treap_fwd_iter_init( selector->score_treap, selector->pool );
     618           0 :         !score_treap_fwd_iter_done( iter );
     619           0 :         iter = score_treap_fwd_iter_next( iter, selector->pool ) ) {
     620           0 :     fd_sspeer_private_t * peer = score_treap_fwd_iter_ele( iter, selector->pool );
     621           0 :     fd_sspeer_private_t * shadow_peer = peer_pool_ele_acquire( selector->pool );
     622           0 :     shadow_peer->latency   = peer->latency;
     623           0 :     shadow_peer->full_slot = peer->full_slot;
     624           0 :     shadow_peer->incr_slot = peer->incr_slot;
     625           0 :     shadow_peer->addr      = peer->addr;
     626           0 :     shadow_peer->key       = peer->key;
     627           0 :     shadow_peer->score     = fd_sspeer_selector_score( selector, shadow_peer->latency, shadow_peer->full_slot, shadow_peer->incr_slot );
     628           0 :     shadow_peer->valid     = peer->valid;
     629           0 :     fd_memcpy( shadow_peer->full_hash, peer->full_hash, FD_HASH_FOOTPRINT );
     630           0 :     fd_memcpy( shadow_peer->incr_hash, peer->incr_hash, FD_HASH_FOOTPRINT );
     631           0 :     score_treap_ele_insert( selector->shadow_score_treap, shadow_peer, selector->pool );
     632           0 :     selector->peer_idx_list[ idx++ ] = peer_pool_idx( selector->pool, peer );
     633           0 :     peer_map_by_key_ele_remove_fast( selector->map_by_key, peer, selector->pool );
     634           0 :     peer_map_by_addr_ele_remove_fast( selector->map_by_addr, peer, selector->pool );
     635           0 :     peer_map_by_key_ele_insert( selector->map_by_key, shadow_peer, selector->pool );
     636           0 :     peer_map_by_addr_ele_insert( selector->map_by_addr, shadow_peer, selector->pool );
     637           0 :   }
     638             : 
     639             :   /* clear score treap */
     640           0 :   for( ulong i=0UL; i<idx; i++ ) {
     641           0 :     fd_sspeer_private_t * peer = peer_pool_ele( selector->pool, selector->peer_idx_list[ i ] );
     642           0 :     score_treap_ele_remove( selector->score_treap, peer, selector->pool );
     643           0 :     peer_pool_ele_release( selector->pool, peer );
     644           0 :   }
     645             : 
     646           0 :   score_treap_t * tmp          = selector->score_treap;
     647           0 :   selector->score_treap        = selector->shadow_score_treap;
     648           0 :   selector->shadow_score_treap = tmp;
     649             : 
     650             : #if FD_SSPEER_SELECTOR_DEBUG
     651             :   FD_TEST( score_treap_verify( selector->score_treap, selector->pool )==0 );
     652             : #endif
     653           0 : }
     654             : 
     655             : fd_sscluster_slot_t
     656           0 : fd_sspeer_selector_cluster_slot( fd_sspeer_selector_t * selector ) {
     657           0 :   return selector->cluster_slot;
     658           0 : }
     659             : 
     660             : ulong
     661           0 : fd_sspeer_selector_peer_map_by_key_ele_cnt( fd_sspeer_selector_t * selector ) {
     662           0 :   ulong cnt = 0UL;
     663           0 :   for( peer_map_by_key_iter_t iter = peer_map_by_key_iter_init( selector->map_by_key, selector->pool );
     664           0 :       !peer_map_by_key_iter_done( iter, selector->map_by_key, selector->pool );
     665           0 :       iter = peer_map_by_key_iter_next( iter, selector->map_by_key, selector->pool ) ) {
     666           0 :     cnt++;
     667           0 :   }
     668           0 :   return cnt;
     669           0 : }
     670             : 
     671             : ulong
     672           0 : fd_sspeer_selector_peer_map_by_addr_ele_cnt( fd_sspeer_selector_t * selector ) {
     673           0 :   ulong cnt = 0UL;
     674           0 :   for( peer_map_by_addr_iter_t iter = peer_map_by_addr_iter_init( selector->map_by_addr, selector->pool );
     675           0 :       !peer_map_by_addr_iter_done( iter, selector->map_by_addr, selector->pool );
     676           0 :       iter = peer_map_by_addr_iter_next( iter, selector->map_by_addr, selector->pool ) ) {
     677           0 :     cnt++;
     678           0 :   }
     679           0 :   return cnt;
     680           0 : }

Generated by: LCOV version 1.14