OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_dht_bootstrap.cpp

232 lines · cpp ref:
// 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;
}