// Test: Live DHT bootstrap to the public HyperDHT network.
// Proves Phase 2 (DHT-RPC + Kademlia) is wire-compatible with the real network.
//
// Build:
// cd /storage/projects/code/godot/hyperswarm-peer/test
// g++ -std=c++17 -I../src -I../thirdparty/libudx/include \
// test_dht_bootstrap.cpp ../src/dht_rpc.cpp ../src/noise_ik.cpp \
// ../thirdparty/libudx/src/udx.c ../thirdparty/libudx/src/udx_bbr.c \
// ../thirdparty/libudx/src/udx_rate.c ../thirdparty/libudx/src/cirbuf.c \
// ../thirdparty/libudx/src/endian.c ../thirdparty/libudx/src/io_posix.c \
// ../thirdparty/libudx/src/queue.c ../thirdparty/libudx/src/win_filter.c \
// ../thirdparty/libudx/src/win_filter_f64.c \
// -luv -lsodium -lpthread -o test_dht_bootstrap
//
// Run: ./test_dht_bootstrap
#include "compact_encoding.h"
#include "dht_rpc.h"
#include "kademlia_table.h"
#include <cstdio>
#include <cstring>
#include <sodium.h>
extern "C" {
#include <udx.h>
#include <uv.h>
}
// ---- Test state ----
static int tests_run = 0;
static int tests_passed = 0;
static int tests_failed = 0;
static void check(bool cond, const char *name) {
tests_run++;
if (cond) {
tests_passed++;
printf(" PASS: %s\n", name);
} else {
tests_failed++;
printf(" FAIL: %s\n", name);
}
}
// ---- Live network test ----
struct TestContext {
uv_loop_t loop;
udx_t udx;
udx_socket_t socket;
uv_timer_t timer;
uv_timer_t timeout_timer;
DhtRpc rpc;
bool bootstrapped;
int responses_received;
int pings_replied;
uint64_t start_time;
};
static void on_socket_recv(udx_socket_t *socket, ssize_t read_len,
const uv_buf_t *buf, const struct sockaddr *from) {
if (read_len <= 0) return;
TestContext *ctx = static_cast<TestContext *>(socket->data);
const uint8_t *data = (const uint8_t *)buf->base;
// Dispatch to DHT-RPC.
if ((size_t)read_len >= 4) {
uint8_t type_byte = data[0];
if (type_byte == DHT_REQUEST_ID || type_byte == DHT_RESPONSE_ID) {
const struct sockaddr_in *from_in = (const struct sockaddr_in *)from;
ctx->rpc.on_message(data, (size_t)read_len, from_in);
}
}
}
static void on_tick(uv_timer_t *handle) {
TestContext *ctx = static_cast<TestContext *>(handle->data);
uint64_t now = uv_now(&ctx->loop);
ctx->rpc.tick(now);
// Check if bootstrap completed.
if (ctx->rpc.is_bootstrapped() && !ctx->bootstrapped) {
ctx->bootstrapped = true;
uint64_t elapsed = now - ctx->start_time;
printf("\n Bootstrap completed in %lu ms\n", (unsigned long)elapsed);
}
// After bootstrap, check the routing table.
if (ctx->bootstrapped) {
int table_size = ctx->rpc.table().size();
if (table_size > 0 && ctx->responses_received == 0) {
ctx->responses_received = table_size;
printf(" Routing table has %d nodes\n", table_size);
// Stop the timer — we have what we need.
uv_timer_stop(&ctx->timer);
uv_timer_stop(&ctx->timeout_timer);
uv_close((uv_handle_t *)&ctx->timer, nullptr);
uv_close((uv_handle_t *)&ctx->timeout_timer, nullptr);
udx_socket_close(&ctx->socket);
}
}
}
static void on_timeout(uv_timer_t *handle) {
TestContext *ctx = static_cast<TestContext *>(handle->data);
printf("\n TIMEOUT after 10 seconds\n");
// Stop everything.
uv_timer_stop(&ctx->timer);
uv_timer_stop(&ctx->timeout_timer);
uv_close((uv_handle_t *)&ctx->timer, nullptr);
uv_close((uv_handle_t *)&ctx->timeout_timer, nullptr);
udx_socket_close(&ctx->socket);
}
static void test_live_bootstrap() {
printf("\n=== Test: Live DHT Bootstrap ===\n\n");
if (sodium_init() < 0) {
printf(" SKIP: sodium_init failed\n");
return;
}
TestContext ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.bootstrapped = false;
ctx.responses_received = 0;
ctx.pings_replied = 0;
// Initialize libuv + libudx.
uv_loop_init(&ctx.loop);
udx_init(&ctx.loop, &ctx.udx, nullptr);
udx_socket_init(&ctx.udx, &ctx.socket, nullptr);
ctx.socket.data = &ctx;
struct sockaddr_in bind_addr;
uv_ip4_addr("0.0.0.0", 0, &bind_addr);
udx_socket_bind(&ctx.socket, (struct sockaddr *)&bind_addr, 0);
// Get bound port.
struct sockaddr_in local_addr;
int addr_len = sizeof(local_addr);
udx_socket_getsockname(&ctx.socket, (struct sockaddr *)&local_addr, &addr_len);
int bound_port = ntohs(local_addr.sin_port);
printf(" Bound to port %d\n", bound_port);
// Enable socket recv.
udx_socket_recv_start(&ctx.socket, on_socket_recv);
// Initialize DHT-RPC.
ctx.rpc.init(&ctx.socket, &ctx.loop);
const char *bootstrap[] = {
"88.99.3.86:49737",
"142.93.90.113:49737",
"138.68.147.8:49737",
};
ctx.rpc.set_bootstrap(bootstrap, 3);
// Set local address.
IPv4Address local;
memcpy(local.ip, &local_addr.sin_addr.s_addr, 4);
local.port = ntohs(local_addr.sin_port);
ctx.rpc.set_local_address(local);
// Set up callbacks.
ctx.rpc.on_bootstrapped = [&ctx]() {
printf(" on_bootstrapped callback fired!\n");
};
ctx.rpc.on_response = [&ctx](const DhtResponse &res, uint32_t tag) {
(void)tag;
char ip[32];
sprintf(ip, "%d.%d.%d.%d:%d",
res.from.ip[0], res.from.ip[1], res.from.ip[2], res.from.ip[3],
res.from.port);
printf(" Response from %s (closer_nodes=%d, rtt=%u ms)\n",
ip, res.closer_count, res.rtt_ms);
};
// Timer for ticking.
uv_timer_init(&ctx.loop, &ctx.timer);
ctx.timer.data = &ctx;
uv_timer_start(&ctx.timer, on_tick, 0, 10); // 10ms tick
// Timeout timer.
uv_timer_init(&ctx.loop, &ctx.timeout_timer);
ctx.timeout_timer.data = &ctx;
uv_timer_start(&ctx.timeout_timer, on_timeout, 10000, 0); // 10s timeout
// Bootstrap!
ctx.start_time = uv_now(&ctx.loop);
printf(" Bootstrapping to live DHT...\n");
ctx.rpc.bootstrap();
// Run event loop until done.
uv_run(&ctx.loop, UV_RUN_DEFAULT);
uv_loop_close(&ctx.loop);
// Verify results.
printf("\n --- Results ---\n");
printf(" Bootstrapped: %s\n", ctx.bootstrapped ? "YES" : "NO");
printf(" Routing table nodes: %d\n", ctx.rpc.table().size());
printf(" Responses received: %d\n", ctx.responses_received);
check(ctx.bootstrapped, "DHT bootstrap completed");
check(ctx.rpc.table().size() > 0, "Routing table has nodes");
check(ctx.rpc.table().size() >= 3, "Routing table has >= 3 nodes (found closer nodes)");
}
// ---- Main ----
int main() {
printf("=== DHT Bootstrap Test ===\n");
printf("Tests wire-compatibility with the live HyperDHT network.\n");
test_live_bootstrap();
printf("\n=== Summary: %d/%d passed", tests_passed, tests_run);
if (tests_failed > 0) printf(", %d FAILED", tests_failed);
printf(" ===\n\n");
return tests_failed > 0 ? 1 : 0;
}