Add NodeDiscovery: multi-hop peer crawl with UDP-ping preference
- Implement NodeDiscovery engine: known-peer table (DynArr) with a per-tick seed/ping/query/connect state machine driven by the node maintenance thread; bounded multi-hop crawl (FANOUT peers per node, hop-capped) that connects to reachable peers lowest-ping-first - Add GET_PEERS/PEERS TCP opcodes for peer-list exchange, handled on both inbound and outbound connections - Measure UDP round-trip time and pass it to the on_pong callback (previously the send timestamp was only used for retries) - Advertise each node's listen port in HELLO/ACK_HELLO and store it per-connection, so inbound-only peers and non-default ports are discoverable (length-guarded parse; wire-compatible with old peers) - Wire a udp_node_t + node_discovery_t into net_node_t: init/start in Node_Create, tick in the maintenance loop, teardown in Node_Destroy (stop UDP before destroying discovery to avoid callback races) - Add Node_ConnListenEndpoint / Node_GetPeerEndpoints helpers to derive peers' listen endpoints (outbound: dialed port; inbound: advertised), with IPv4-mapped-IPv6 normalization and IP+port dedup - Match ping pong/timeout callbacks by peer address (the UDP layer owns the nonce), fixing discovered peers stuck UNREACHABLE - Ping the peer's listen port instead of the ephemeral TCP source port (fixes the original stub so pongs actually return) - Add `peers` CLI command to dump the discovery table (endpoint/hop/ state/ping) - Add discovery tunables to constants.h (fanout, max hops, target connections, timeouts, caps)
This commit is contained in:
@@ -13,6 +13,18 @@
|
||||
#define MAX_CONS 32 // Some baseline for now
|
||||
#define LISTEN_PORT 9393
|
||||
#define ECHO_PEERS 1 // If non-zero, automatically attempt to connect back to any inbound peers (helps form bidirectional peering)
|
||||
|
||||
// Node discovery
|
||||
#define DISCOVERY_FANOUT 2 // "A couple" - how many peers to query per round, and how many new peers to accept per PEERS response (keeps the crawl spread out)
|
||||
#define DISCOVERY_MAX_HOPS 3 // How many hops away from us we keep crawling
|
||||
#define DISCOVERY_TARGET_CONNECTIONS 8 // Desired outbound connection count discovery tries to reach (bounded by MAX_CONS)
|
||||
#define DISCOVERY_MAX_KNOWN_PEERS 256 // Cap on the known-peer table size
|
||||
#define DISCOVERY_PEERS_RESPONSE_CAP 8 // Max endpoints we put in a single PEERS response
|
||||
#define DISCOVERY_MAX_PINGS_PER_TICK 8 // Cap on UDP pings sent per discovery tick
|
||||
#define DISCOVERY_PING_TIMEOUT_MS 5000ULL // Backstop: a PINGED peer with no pong for this long is marked unreachable
|
||||
#define DISCOVERY_PING_REFRESH_MS 60000ULL // Re-ping a reachable peer after this long to refresh its latency
|
||||
#define DISCOVERY_QUERY_INTERVAL_MS 15000ULL // Minimum interval between GET_PEERS to the same peer
|
||||
#define DISCOVERY_CONNECT_RETRY_MS 30000ULL // Minimum interval between connect attempts to the same discovered peer
|
||||
#define TCP_THREAD_STACK_SIZE (512 * 1024) // 512 KB. We could get away with like 128 KB since it's mostly just recv bufs, but it's good having some breathing room.
|
||||
// This is also for client threads. The server has the default (~8 MB on POSIX).
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
|
||||
#include <constants.h>
|
||||
#include <packettype.h>
|
||||
#include <udpd/udpnode.h>
|
||||
|
||||
// Forward declaration - the discovery state is defined in nodediscovery.c (opaque here).
|
||||
typedef struct node_discovery node_discovery_t;
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -40,6 +44,9 @@ typedef struct {
|
||||
pthread_t maintenanceThread;
|
||||
volatile int maintenanceRunning;
|
||||
int maintenanceIntervalMs;
|
||||
// UDP ping/pong daemon (latency oracle) and peer discovery state
|
||||
udp_node_t* udpNode;
|
||||
node_discovery_t* discovery;
|
||||
} net_node_t;
|
||||
|
||||
net_node_t* Node_Create();
|
||||
@@ -71,4 +78,15 @@ void Node_Client_OnConnect(tcp_connection_t* client);
|
||||
void Node_Client_OnData(tcp_connection_t* client);
|
||||
void Node_Client_OnDisconnect(tcp_connection_t* client);
|
||||
|
||||
void Node_GetClientList(net_node_t* node, tcp_connection_t** outClients, size_t* outCount);
|
||||
|
||||
// Computes a connection's peer listen endpoint (IP + advertised/dialed listen port) into *out.
|
||||
// Outbound: the dialed peerAddr port already is the listen port. Inbound: uses peerListenPort.
|
||||
// Returns non-zero on success (usable endpoint with a known, non-zero port), zero otherwise.
|
||||
int Node_ConnListenEndpoint(const tcp_connection_t* conn, struct sockaddr_storage* out);
|
||||
|
||||
// Fills outEndpoints with the listen endpoints of all current connections (inbound + outbound),
|
||||
// deduped by IP+port. Returns the number of endpoints written (<= maxOut).
|
||||
size_t Node_GetPeerEndpoints(net_node_t* node, struct sockaddr_storage* outEndpoints, size_t maxOut);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef NODEDISCOVERY_H
|
||||
#define NODEDISCOVERY_H
|
||||
|
||||
#include <nets/net_node.h>
|
||||
#include <udpd/udpnode.h>
|
||||
|
||||
// Create/destroy the peer-discovery state. Owns the known-peer table and its lock.
|
||||
node_discovery_t* NodeDiscovery_Create(net_node_t* node, udp_node_t* udpNode);
|
||||
void NodeDiscovery_Destroy(node_discovery_t* disc);
|
||||
|
||||
// Periodic tick (driven by the node maintenance thread): seed currently-connected peers,
|
||||
// UDP-ping newly-learned ones, query a couple of connected peers for more, and connect to
|
||||
// the reachable peers with the lowest ping until we reach the target connection count.
|
||||
void NodeDiscovery_Iterate(node_discovery_t* disc);
|
||||
|
||||
// UDP latency callbacks (forwarded from the udp node via net_node thunks).
|
||||
void NodeDiscovery_OnPong(node_discovery_t* disc, const struct sockaddr_storage* from, uint64_t nonce, uint64_t rttMs);
|
||||
void NodeDiscovery_OnPingTimeout(node_discovery_t* disc, const struct sockaddr_storage* dest, uint64_t nonce);
|
||||
|
||||
// TCP peer-exchange handlers (called from the net_node packet dispatch).
|
||||
// Build a PEERS response (a sample of our peers, excluding the requester) and send it over fromConn.
|
||||
void NodeDiscovery_OnGetPeers(node_discovery_t* disc, tcp_connection_t* fromConn);
|
||||
// Decode a received PEERS payload and fold a couple of its endpoints into the known-peer table.
|
||||
void NodeDiscovery_OnPeersReceived(node_discovery_t* disc, tcp_connection_t* fromConn, const unsigned char* payload, size_t payloadLen);
|
||||
|
||||
// Dump the known-peer table to stdout (for the CLI `peers` command).
|
||||
void NodeDiscovery_PrintPeers(node_discovery_t* disc);
|
||||
|
||||
#endif
|
||||
@@ -14,7 +14,9 @@ typedef enum {
|
||||
PACKET_TYPE_BROADCAST_TX = 7, // Here's a new transaction I want to share with the network
|
||||
PACKET_TYPE_ACK_TX = 8, // I have received your transaction, here's what I did with it (response to broadcast)
|
||||
PACKET_TYPE_ERROR = 9, // Something went wrong with the packet you sent me, here's an error message (can be response to any packet)
|
||||
PACKET_TYPE_MAX = 10
|
||||
PACKET_TYPE_GET_PEERS = 10, // Who are your peers? Send me a few of them so I can discover more of the network
|
||||
PACKET_TYPE_PEERS = 11, // Here are some of my peers' listen endpoints (response to GET_PEERS)
|
||||
PACKET_TYPE_MAX = 12
|
||||
} packet_type_t;
|
||||
|
||||
static inline int PacketType_IsValid(uint8_t packetType) {
|
||||
|
||||
@@ -26,6 +26,10 @@ struct tcp_connection_t {
|
||||
uint32_t connectionId;
|
||||
tcp_connection_role_t role;
|
||||
|
||||
// Peer's advertised TCP/UDP listen port (learned from HELLO/ACK_HELLO). 0 until known.
|
||||
// For OUTBOUND connections the peerAddr port already is the listen port; this matters for INBOUND peers.
|
||||
uint16_t peerListenPort;
|
||||
|
||||
pthread_t ioThread;
|
||||
pthread_mutex_t sendLock;
|
||||
pthread_mutex_t stateLock;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef UDP_NODE_H
|
||||
#define UDP_NODE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <udpd/udppackettype.h>
|
||||
|
||||
#define UDP_LISTEN_PORT 9393
|
||||
#define UDP_PING_RETRY_INTERVAL_MS 1000
|
||||
#define UDP_PING_MAX_RETRIES 3
|
||||
#define UDP_MAX_PENDING_PINGS 64
|
||||
|
||||
typedef struct {
|
||||
uint64_t nonce;
|
||||
struct sockaddr_storage dest;
|
||||
uint64_t lastSentMs;
|
||||
int retries;
|
||||
bool active;
|
||||
} pending_ping_t;
|
||||
|
||||
typedef struct udp_node {
|
||||
int sockFd; // AF_INET6, IPV6_V6ONLY=1
|
||||
int sockFdV4; // AF_INET
|
||||
|
||||
volatile int isRunning;
|
||||
|
||||
pthread_t recvThreadV6;
|
||||
pthread_t recvThreadV4;
|
||||
pthread_t retryThread;
|
||||
|
||||
pending_ping_t pendingPings[UDP_MAX_PENDING_PINGS];
|
||||
pthread_mutex_t pingsMutex;
|
||||
|
||||
void (*on_pong)(struct udp_node* node,
|
||||
const struct sockaddr_storage* from,
|
||||
uint64_t nonce, int protoVersion, uint64_t rttMs, void* user);
|
||||
void (*on_ping_timeout)(struct udp_node* node,
|
||||
const struct sockaddr_storage* dest,
|
||||
uint64_t nonce, void* user);
|
||||
void* callbackUser;
|
||||
} udp_node_t;
|
||||
|
||||
int UdpNode_Init(udp_node_t* node, uint16_t port);
|
||||
void UdpNode_SetCallbacks(udp_node_t* node,
|
||||
void (*on_pong)(udp_node_t*, const struct sockaddr_storage*, uint64_t, int, uint64_t, void*),
|
||||
void (*on_ping_timeout)(udp_node_t*, const struct sockaddr_storage*, uint64_t, void*),
|
||||
void* user);
|
||||
int UdpNode_Start(udp_node_t* node);
|
||||
void UdpNode_Stop(udp_node_t* node);
|
||||
void UdpNode_Destroy(udp_node_t* node);
|
||||
int UdpNode_SendPing(udp_node_t* node, const struct sockaddr_storage* dest);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef UDP_PACKET_TYPE_H
|
||||
#define UDP_PACKET_TYPE_H
|
||||
|
||||
typedef enum {
|
||||
UDP_PACKET_TYPE_NONE = 0,
|
||||
UDP_PACKET_TYPE_PING = 1,
|
||||
UDP_PACKET_TYPE_PONG = 2,
|
||||
} udp_packet_type_t;
|
||||
|
||||
// Wire sizes in bytes
|
||||
#define UDP_PING_WIRE_SIZE 9 // 1 (type) + 8 (nonce)
|
||||
#define UDP_PONG_WIRE_SIZE 13 // 1 (type) + 8 (nonce) + 4 (proto_version)
|
||||
|
||||
#endif
|
||||
@@ -14,6 +14,8 @@ typedef struct {
|
||||
uint8_t bytes[32];
|
||||
} key32_t;
|
||||
|
||||
#define PROTO_VERSION 1
|
||||
|
||||
static inline uint32_t hash_key32(key32_t k) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (int i = 0; i < 32; i++) {
|
||||
|
||||
Reference in New Issue
Block a user