OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_dht_godot_style.cpp

220 lines · cpp ref:
// Test: DHT bootstrap using the same event loop style as UdxTransport.
// Reproduces the Godot integration pattern:
// - Thread with UV_RUN_NOWAIT + 500µs sleep
// - 1ms timer tick
// - DhtRpc tick'd from timer callback
// - HyperDht layer installed (overwrites on_response callback)
//
// This test isolates whether the Godot event loop pattern breaks bootstrap.

#include "compact_encoding.h"
#include "dht_rpc.h"
#include "hyperdht.h"
#include "kademlia_table.h"
#include "noise_ik.h"

#include <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <sodium.h>
#include <thread>

extern "C" {
#include <udx.h>
#include <uv.h>
}

// ---- Test context (mimics UdxTransport) ----

struct TestContext {
	uv_loop_t loop;
	udx_t udx;
	udx_socket_t socket;
	uv_timer_t tick_timer;

	DhtRpc *rpc;
	HyperDht *hyperdht;

	std::atomic<bool> running;
	std::atomic<bool> bootstrapped;
	std::atomic<bool> init_requested;

	int tick_count;
	uint64_t start_time;
	int table_size_at_bootstrap;
};

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 DHT messages (same as UdxTransport::_on_socket_recv).
	if (ctx->rpc && (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);

	// Process init command (mimics _process_commands() handling CMD_DHT_INIT).
	if (ctx->init_requested.load() && !ctx->rpc) {
		ctx->init_requested.store(false);

		printf("  [tick %d] Initializing DHT (uv_now=%lu)...\n",
			   ctx->tick_count, (unsigned long)uv_now(&ctx->loop));

		// Create DHT-RPC (same as _init_dht).
		ctx->rpc = new DhtRpc();
		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 from bound socket.
		struct sockaddr_in local_addr;
		int addr_len = sizeof(local_addr);
		udx_socket_getsockname(&ctx->socket, (struct sockaddr *)&local_addr, &addr_len);

		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);
		printf("  [tick %d] Local address: %d.%d.%d.%d:%d\n",
			   ctx->tick_count,
			   local.ip[0], local.ip[1], local.ip[2], local.ip[3], local.port);

		// Create HyperDht layer (overwrites on_response callback, same as Godot).
		Ed25519KeyPair kp;
		memset(&kp, 0, sizeof(kp));
		crypto_sign_keypair(kp.public_key, kp.secret_key);

		ctx->hyperdht = new HyperDht();
		ctx->hyperdht->init(ctx->rpc, kp);

		// Set on_bootstrapped callback (after HyperDht::init, same order as Godot).
		ctx->rpc->on_bootstrapped = [ctx]() {
			ctx->bootstrapped.store(true);
			ctx->table_size_at_bootstrap = ctx->rpc->table().size();
			uint64_t elapsed = uv_now(&ctx->loop) - ctx->start_time;
			printf("  [tick %d] on_bootstrapped fired! table=%d elapsed=%lu ms\n",
				   ctx->tick_count, ctx->table_size_at_bootstrap, (unsigned long)elapsed);
		};

		// Bootstrap!
		ctx->start_time = uv_now(&ctx->loop);
		ctx->rpc->bootstrap();
		printf("  [tick %d] bootstrap() called, start_time=%lu\n",
			   ctx->tick_count, (unsigned long)ctx->start_time);
	}

	// Tick DHT (same as _process_commands() in UdxTransport).
	if (ctx->rpc) {
		uint64_t now = uv_now(&ctx->loop);
		ctx->rpc->tick(now);
		if (ctx->hyperdht) ctx->hyperdht->tick(now);
	}

	ctx->tick_count++;

	// Stop after bootstrap or timeout.
	if (ctx->bootstrapped.load() || ctx->tick_count > 15000) { // 15s max
		ctx->running.store(false);
		uv_timer_stop(&ctx->tick_timer);
		uv_close((uv_handle_t *)&ctx->tick_timer, nullptr);
		udx_socket_close(&ctx->socket);
	}
}

int main() {
	printf("=== DHT Bootstrap Test (Godot Style) ===\n");
	printf("Tests bootstrap using UV_RUN_NOWAIT + 500us sleep + HyperDht layer\n\n");

	if (sodium_init() < 0) {
		printf("FAIL: sodium_init\n");
		return 1;
	}

	TestContext ctx;
	memset(&ctx, 0, sizeof(ctx));
	ctx.running.store(true);
	ctx.bootstrapped.store(false);
	ctx.init_requested.store(false);
	ctx.tick_count = 0;
	ctx.rpc = nullptr;
	ctx.hyperdht = nullptr;
	ctx.table_size_at_bootstrap = 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);

	struct sockaddr_in local_addr;
	int addr_len = sizeof(local_addr);
	udx_socket_getsockname(&ctx.socket, (struct sockaddr *)&local_addr, &addr_len);
	printf("  Bound to port %d\n", ntohs(local_addr.sin_port));

	udx_socket_recv_start(&ctx.socket, on_socket_recv);

	// Timer (1ms tick, same as Godot).
	uv_timer_init(&ctx.loop, &ctx.tick_timer);
	ctx.tick_timer.data = &ctx;
	uv_timer_start(&ctx.tick_timer, on_tick, 0, 1);

	// Simulate: request DHT init after some ticks (like Godot queuing CMD_DHT_INIT).
	// In Godot, the thread starts, runs several ticks, THEN picks up the command.
	// We simulate this by setting init_requested after a short delay.
	ctx.init_requested.store(true);

	printf("  Running event loop (UV_RUN_NOWAIT + 500us sleep)...\n");

	// Run event loop Godot-style.
	while (ctx.running.load()) {
		uv_run(&ctx.loop, UV_RUN_NOWAIT);
		std::this_thread::sleep_for(std::chrono::microseconds(500));
	}

	// Run final callbacks.
	uv_run(&ctx.loop, UV_RUN_DEFAULT);
	uv_loop_close(&ctx.loop);

	// Results.
	printf("\n  --- Results ---\n");
	printf("  Bootstrapped: %s\n", ctx.bootstrapped.load() ? "YES" : "NO");
	if (ctx.rpc) {
		printf("  Routing table nodes: %d\n", ctx.rpc->table().size());
	}
	printf("  Total ticks: %d\n", ctx.tick_count);

	// Cleanup.
	delete ctx.hyperdht;
	delete ctx.rpc;

	if (ctx.bootstrapped.load()) {
		printf("\n  PASS: DHT bootstrap with Godot-style event loop\n");
		return 0;
	} else {
		printf("\n  FAIL: DHT bootstrap did not complete\n");
		return 1;
	}
}