Repository: firedancer-io/firedancer (C)

Issue:
[gossip tile] entrypoints config syntax
The gossip tile has confusing config syntax

When specifying gossip entrypoints `10.0.0.1:1001` and `10.0.0.2:1002`, the config looks like this:

```
[tiles.gossip]
entrypoints = ["10.0.0.1", "10.0.0.2"]
peer_ports = [1001, 1002]
```

Should instead be

```
[tiles.gossip]
entrypoints = ["10.0.0.1:1001", "10.0.0.2:1002"]
```



Fix (patch):
diff --git a/src/app/fdctl/config/default-firedancer.toml b/src/app/fdctl/config/default-firedancer.toml
index 7b3e008334f..ac3386bdb50 100644
--- a/src/app/fdctl/config/default-firedancer.toml
+++ b/src/app/fdctl/config/default-firedancer.toml
@@ -8,10 +8,6 @@
     port = 8700
 
 [tiles]
-    [tiles.gossip]
-        peer_ports = []
-        gossip_listen_port = 8700
-
     [tiles.repair]
         repair_intake_listen_port = 8701
         repair_serve_listen_port = 8702
diff --git a/src/app/fdctl/topos/fd_firedancer.c b/src/app/fdctl/topos/fd_firedancer.c
index 466c5f720f6..c725059851e 100644
--- a/src/app/fdctl/topos/fd_firedancer.c
+++ b/src/app/fdctl/topos/fd_firedancer.c
@@ -13,9 +13,11 @@
 #include "../../../flamenco/runtime/fd_runtime.h"
 #include "../../../flamenco/runtime/fd_txncache.h"
 #include "../../../util/tile/fd_tile_private.h"
-#include "../../../util/shmem/fd_shmem_private.h"
 
 #include <sys/random.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
 
 static fd_topo_obj_t *
 setup_topo_blockstore( fd_topo_t *  topo,
@@ -73,8 +75,74 @@ setup_topo_txncache( fd_topo_t *  topo,
   return obj;
 }
 
+static int
+resolve_gossip_entrypoint( char const *    host_port,
+                           fd_ip4_port_t * ip4_port ) {
+
+  /* Split host:port */
+
+  char const * colon = strrchr( host_port, ':' );
+  if( FD_UNLIKELY( !colon ) ) {
+    FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": no port number", host_port ));
+  }
+
+  char fqdn[ 255 ];
+  ulong fqdn_len = (ulong)( colon-host_port );
+  if( FD_UNLIKELY( fqdn_len>254 ) ) {
+    FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": hostname too long", host_port ));
+  }
+  fd_memcpy( fqdn, host_port, fqdn_len );
+  fqdn[ fqdn_len ] = '\0';
+
+  /* Parse port number */
+
+  char const * port_str = colon+1;
+  char const * endptr   = NULL;
+  ulong port = strtoul( port_str, (char **)&endptr, 10 );
+  if( FD_UNLIKELY( !endptr || !port || port>USHORT_MAX || *endptr!='\0' ) ) {
+    FD_LOG_ERR(( "invalid [gossip.entrypoints] entry \"%s\": invalid port number", host_port ));
+  }
+  ip4_port->port = (ushort)fd_ushort_bswap( (ushort)port );
+
+  /* Resolve hostname */
+
+  struct addrinfo hints = { .ai_family = AF_INET };
+  struct addrinfo * res;
+  int err = getaddrinfo( fqdn, NULL, &hints, &res );
+  if( FD_UNLIKELY( err ) ) {
+    FD_LOG_WARNING(( "cannot resolve [gossip.entrypoints] entry \"%s\": %i-%s", fqdn, err, gai_strerror( err ) ));
+    return 0;
+  }
+
+  int resolved = 0;
+  for( struct addrinfo * cur=res; cur; cur=cur->ai_next ) {
+    if( FD_UNLIKELY( cur->ai_addr->sa_family!=AF_INET ) ) continue;
+    struct sockaddr_in const * addr = (struct sockaddr_in const *)cur->ai_addr;
+    ip4_port->addr = addr->sin_addr.s_addr;
+    resolved = 1;
+    break;
+  }
+
+  freeaddrinfo( res );
+  return resolved;
+}
+
+static void
+resolve_gossip_entrypoints( config_t * config ) {
+  ulong entrypoint_cnt = config->gossip.entrypoints_cnt;
+  ulong resolved_entrypoints = 0UL;
+  for( ulong j=0UL; j<entrypoint_cnt; j++ ) {
+    if( resolve_gossip_entrypoint( config->gossip.entrypoints[j], &config->gossip.resolved_entrypoints[resolved_entrypoints] ) ) {
+      resolved_entrypoints++;
+    }
+  }
+  config->gossip.resolved_entrypoints_cnt = resolved_entrypoints;
+}
+
 void
 fd_topo_initialize( config_t * config ) {
+  resolve_gossip_entrypoints( config );
+
   ulong net_tile_cnt    = config->layout.net_tile_count;
   ulong shred_tile_cnt  = config->layout.shred_tile_count;
   ulong quic_tile_cnt   = config->layout.quic_tile_count;
@@ -602,7 +670,6 @@ fd_topo_initialize( config_t * config ) {
       tile->gossip.ip_addr = config->tiles.net.ip_addr;
       strncpy( tile->gossip.identity_key_path, config->consensus.identity_path, sizeof(tile->gossip.identity_key_path) );
       tile->gossip.gossip_listen_port =  config->gossip.port;
-      FD_TEST( config->gossip.port == config->tiles.gossip.gossip_listen_port );
       tile->gossip.tvu_port = config->tiles.shred.shred_listen_port;
       if( FD_UNLIKELY( tile->gossip.tvu_port>(ushort)(USHORT_MAX-6) ) )
         FD_LOG_ERR(( "shred_listen_port in the config must not be greater than %hu", (ushort)(USHORT_MAX-6) ));
@@ -611,15 +678,8 @@ fd_topo_initialize( config_t * config ) {
       tile->gossip.tpu_quic_port        = config->tiles.quic.quic_transaction_listen_port;
       tile->gossip.tpu_vote_port        = config->tiles.quic.regular_transaction_listen_port; /* TODO: support separate port for tpu vote */
       tile->gossip.repair_serve_port    = config->tiles.repair.repair_serve_listen_port;
-      FD_TEST( config->tiles.gossip.entrypoints_cnt == config->tiles.gossip.peer_ports_cnt );
-      tile->gossip.entrypoints_cnt = config->tiles.gossip.peer_ports_cnt;
-      for (ulong i=0UL; i<config->tiles.gossip.entrypoints_cnt; i++) {
-        if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->tiles.gossip.entrypoints[i], &tile->gossip.entrypoints[i] ) ) ) {
-          FD_LOG_ERR(( "configuration specifies invalid gossip peer IP address `%s`", config->tiles.gossip.entrypoints[i] ));
-        }
-        tile->gossip.peer_ports[i] = (ushort)config->tiles.gossip.peer_ports[i];
-      }
-      tile->gossip.plugins_enabled = plugins_enabled;
+      tile->gossip.entrypoints_cnt      = fd_ulong_min( config->gossip.resolved_entrypoints_cnt, FD_TOPO_GOSSIP_ENTRYPOINTS_MAX );
+      fd_memcpy( tile->gossip.entrypoints, config->gossip.resolved_entrypoints, tile->gossip.entrypoints_cnt * sizeof(fd_ip4_port_t) );
 
     } else if( FD_UNLIKELY( !strcmp( tile->name, "repair" ) ) ) {
       tile->repair.repair_intake_listen_port =  config->tiles.repair.repair_intake_listen_port;
diff --git a/src/app/shared/Local.mk b/src/app/shared/Local.mk
index 3f8797ce23d..2c0b24f4a3b 100644
--- a/src/app/shared/Local.mk
+++ b/src/app/shared/Local.mk
@@ -11,6 +11,7 @@ $(call add-hdrs,fd_file_util.h)
 
 $(call add-objs,fd_config fd_config_parse,fdctl_shared)
 $(call make-unit-test,test_config_parse,test_config_parse,fd_fdctl fdctl_shared fd_ballet fd_util)
+$(call run-unit-test,test_config_parse)
 $(call make-fuzz-test,fuzz_fdctl_config,fuzz_fdctl_config,fd_fdctl fdctl_shared fd_ballet fd_util)
 
 $(call add-objs,commands/help,fdctl_shared)
diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h
index de56bfc1fff..e37eb2c63be 100644
--- a/src/app/shared/fd_config.h
+++ b/src/app/shared/fd_config.h
@@ -3,6 +3,7 @@
 
 #include "fd_cap_chk.h"
 #include "../../disco/topo/fd_topo.h"
+#include "../../util/net/fd_net_headers.h" /* fd_ip4_port_t */
 
 #include <net/if.h>
 #include <linux/limits.h>
@@ -161,8 +162,11 @@ struct fd_config {
   } ledger;
 
   struct {
+#   define FD_CONFIG_GOSSIP_ENTRYPOINTS_MAX 16
     ulong  entrypoints_cnt;
-    char   entrypoints[ 16 ][ 256 ];
+    char   entrypoints[ FD_CONFIG_GOSSIP_ENTRYPOINTS_MAX ][ 262 ];
+    ulong         resolved_entrypoints_cnt;
+    fd_ip4_port_t resolved_entrypoints[ FD_CONFIG_GOSSIP_ENTRYPOINTS_MAX ];
     int    port_check;
     ushort port;
     char   host[ 256 ];
@@ -382,14 +386,6 @@ struct fd_config {
 
     /* Firedancer-only tile configs */
 
-    struct {
-      ulong  entrypoints_cnt;
-      char   entrypoints[16][256];
-      ushort gossip_listen_port;
-      ulong  peer_ports_cnt;
-      ushort peer_ports[16];
-    } gossip;
-
     struct {
       ushort repair_intake_listen_port;
       ushort repair_serve_listen_port;
diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c
index 8614164f977..027e794e129 100644
--- a/src/app/shared/fd_config_parse.c
+++ b/src/app/shared/fd_config_parse.c
@@ -391,10 +391,6 @@ fdctl_pod_to_cfg( config_t * config,
 
   CFG_POP      ( bool,   consensus.vote                                   );
 
-  CFG_POP_ARRAY( cstr,   tiles.gossip.entrypoints                         );
-  CFG_POP      ( ushort, tiles.gossip.gossip_listen_port                  );
-  CFG_POP_ARRAY( ushort, tiles.gossip.peer_ports                          );
-
   CFG_POP      ( ushort, tiles.repair.repair_intake_listen_port           );
   CFG_POP      ( ushort, tiles.repair.repair_serve_listen_port            );
   CFG_POP      ( cstr,   tiles.repair.good_peer_cache_file                );
diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h
index 20e31eabe6a..4d8853c37a6 100644
--- a/src/disco/topo/fd_topo.h
+++ b/src/disco/topo/fd_topo.h
@@ -5,6 +5,7 @@
 #include "../../tango/fd_tango.h"
 #include "../../waltz/xdp/fd_xdp1.h"
 #include "../../ballet/base58/fd_base58.h"
+#include "../../util/net/fd_net_headers.h"
 
 /* Maximum number of workspaces that may be present in a topology. */
 #define FD_TOPO_MAX_WKSPS         (256UL)
@@ -321,15 +322,11 @@ typedef struct {
       float cu_price_spread;
     } benchg;
 
-    /* Firedancer-only tile configs */
-
     struct {
       ushort  gossip_listen_port;
+#     define FD_TOPO_GOSSIP_ENTRYPOINTS_MAX 16
       ulong   entrypoints_cnt;
-      uint    entrypoints[16];
-      ulong   peer_ports_cnt;
-      ushort  peer_ports[16];
-
+      fd_ip4_port_t entrypoints[ FD_TOPO_GOSSIP_ENTRYPOINTS_MAX ];
       uint    ip_addr;
       char    identity_key_path[ PATH_MAX ];
       ushort  tvu_port;
diff --git a/src/discof/gossip/fd_gossip_tile.c b/src/discof/gossip/fd_gossip_tile.c
index 3da6e40b916..ccfdbbb8c7b 100644
--- a/src/discof/gossip/fd_gossip_tile.c
+++ b/src/discof/gossip/fd_gossip_tile.c
@@ -951,7 +951,7 @@ unprivileged_init( fd_topo_t *      topo,
     FD_LOG_ERR( ( "error setting gossip config" ) );
   }
 
-  fd_gossip_set_entrypoints( ctx->gossip, tile->gossip.entrypoints, tile->gossip.entrypoints_cnt, tile->gossip.peer_ports );
+  fd_gossip_set_entrypoints( ctx->gossip, tile->gossip.entrypoints, tile->gossip.entrypoints_cnt );
 
   fd_gossip_update_addr( ctx->gossip, &ctx->gossip_config.my_addr );
 
diff --git a/src/flamenco/gossip/fd_gossip.c b/src/flamenco/gossip/fd_gossip.c
index f0a71441737..7f712eec1be 100644
--- a/src/flamenco/gossip/fd_gossip.c
+++ b/src/flamenco/gossip/fd_gossip.c
@@ -1971,17 +1971,18 @@ fd_gossip_recv(fd_gossip_t * glob, const fd_gossip_peer_addr_t * from, fd_gossip
 
 /* Initiate connection to a peer */
 int
-fd_gossip_add_active_peer( fd_gossip_t * glob, fd_gossip_peer_addr_t * addr ) {
+fd_gossip_add_active_peer( fd_gossip_t *                 glob,
+                           fd_gossip_peer_addr_t const * addr ) {
   fd_gossip_lock( glob );
-  fd_active_elem_t * val = fd_active_table_query(glob->actives, addr, NULL);
-  if (val == NULL) {
-    if (fd_active_table_is_full(glob->actives)) {
-      FD_LOG_WARNING(("too many actives"));
+  fd_active_elem_t * val = fd_active_table_query( glob->actives, addr, NULL );
+  if( val==NULL ) {
+    if( fd_active_table_is_full( glob->actives )) {
+      FD_LOG_WARNING(( "too many actives" ));
       fd_gossip_unlock( glob );
       return -1;
     }
-    val = fd_active_table_insert(glob->actives, addr);
-    fd_active_new_value(val);
+    val = fd_active_table_insert( glob->actives, addr );
+    fd_active_new_value( val );
     val->pingcount = 0; /* Incremented in fd_gossip_make_ping */
   }
   fd_gossip_unlock( glob );
@@ -2516,19 +2517,15 @@ fd_gossip_set_stake_weights( fd_gossip_t * gossip,
 }
 
 void
-fd_gossip_set_entrypoints( fd_gossip_t * gossip,
-                           uint const * entrypoints,
-                           ulong entrypoints_cnt,
-                           ushort const * ports ) {
+fd_gossip_set_entrypoints( fd_gossip_t *         gossip,
+                           fd_ip4_port_t const * entrypoints,
+                           ulong                 entrypoints_cnt ) {
   gossip->entrypoints_cnt = entrypoints_cnt;
-  for( ulong i = 0UL; i<entrypoints_cnt; i++) {
-    fd_gossip_peer_addr_t addr;
-    addr.addr = entrypoints[i];
-    addr.port = fd_ushort_bswap( ports[i] );
+  for( ulong i=0UL; i<entrypoints_cnt; i++ ) {
     FD_LOG_NOTICE(( "gossip initial peer - addr: " FD_IP4_ADDR_FMT ":%u",
-      FD_IP4_ADDR_FMT_ARGS( addr.addr ), fd_ushort_bswap( addr.port ) ));
-    fd_gossip_add_active_peer( gossip, &addr );
-    gossip->entrypoints[i] = addr;
+      FD_IP4_ADDR_FMT_ARGS( entrypoints[i].addr ), fd_ushort_bswap( entrypoints[i].port ) ));
+    fd_gossip_add_active_peer( gossip, &entrypoints[i] );
+    gossip->entrypoints[i] = entrypoints[i];
   }
 }
 
diff --git a/src/flamenco/gossip/fd_gossip.h b/src/flamenco/gossip/fd_gossip.h
index a9105aac0ea..edb2e77ebf4 100644
--- a/src/flamenco/gossip/fd_gossip.h
+++ b/src/flamenco/gossip/fd_gossip.h
@@ -4,6 +4,7 @@
 #include "../types/fd_types.h"
 #include "../../util/valloc/fd_valloc.h"
 #include "../../disco/metrics/generated/fd_metrics_gossip.h"
+#include "../../util/net/fd_net_headers.h" /* fd_ip4_port_t */
 
 /* Max number of validators that can be known */
 #define FD_PEER_KEY_MAX (1<<14)
@@ -47,16 +48,7 @@ fd_gossip_t * fd_gossip_join     ( void * shmap );
 void *        fd_gossip_leave    ( fd_gossip_t * join );
 void *        fd_gossip_delete   ( void * shmap );
 
-
-union fd_gossip_peer_addr {
-    struct {
-        uint   addr;  /* IPv4 address, network byte order (big endian) */
-        ushort port;  /* port number, network byte order (big endian) */
-        ushort pad;   /* Must be zero */
-    };
-    ulong l;          /* Combined port and address */
-};
-typedef union fd_gossip_peer_addr fd_gossip_peer_addr_t;
+typedef union fd_ip4_port fd_gossip_peer_addr_t;
 
 int
 fd_gossip_from_soladdr(fd_gossip_peer_addr_t * dst, fd_gossip_socket_addr_t const * src );
@@ -125,7 +117,7 @@ int fd_gossip_update_tpu_vote_addr( fd_gossip_t * glob, const fd_gossip_peer_add
 void fd_gossip_set_shred_version( fd_gossip_t * glob, ushort shred_version );
 
 /* Add a peer to talk to */
-int fd_gossip_add_active_peer( fd_gossip_t * glob, fd_gossip_peer_addr_t * addr );
+int fd_gossip_add_active_peer( fd_gossip_t * glob, fd_gossip_peer_addr_t const * addr );
 
 /* Publish an outgoing value. The source id and wallclock are set by this function. The gossip key for the value is optionally returned. */
 int fd_gossip_push_value( fd_gossip_t * glob, fd_crds_data_t* data, fd_hash_t * key_opt );
@@ -155,10 +147,10 @@ void fd_gossip_set_stake_weights( fd_gossip_t * gossip, fd_stake_weight_t const
 /* fd_gossip_set_entrypoints sets ip and ports for initial known
    validators to gossip to.  These values are set by the operator
    at startup.  This function should only be called at startup. */
-void fd_gossip_set_entrypoints( fd_gossip_t * gossip,
-                                uint const * allowed_entrypoints, /* big endian ipv4 addresses (allowed_entrypoints_cnt many) */
-                                ulong allowed_entrypoints_cnt,    /* number of allowed entrypoints, assumed to be in [1, FD_ACTIVE_KEY_MAX] */
-                                ushort const * ports );           /* gossip ports of the peers (allowed_entrypoints_cnt many) */
+void
+fd_gossip_set_entrypoints( fd_gossip_t *         gossip,
+                           fd_ip4_port_t const * entrypoints,
+                           ulong                 entrypoint_cnt );
 
 uint fd_gossip_is_allowed_entrypoint( fd_gossip_t * gossip, fd_gossip_peer_addr_t * addr );
 
diff --git a/src/util/net/fd_net_headers.h b/src/util/net/fd_net_headers.h
index ec2d97fdd7d..d2ad043813f 100644
--- a/src/util/net/fd_net_headers.h
+++ b/src/util/net/fd_net_headers.h
@@ -56,4 +56,14 @@ fd_ip4_udp_hdr_init( fd_ip4_udp_hdrs_t * hdrs,
 
 FD_PROTOTYPES_END
 
+union fd_ip4_port {
+  struct {
+    uint   addr;  /* net order */
+    ushort port;  /* net order */
+  };
+  ulong l;
+};
+
+typedef union fd_ip4_port fd_ip4_port_t;
+
 #endif /* HEADER_fd_src_util_net_headers_h */