OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_live_lookup.cpp

214 lines · cpp ref:
// test_live_lookup.cpp — Live DHT lookup test against Hyperswarm mainnet.
// Tests: bootstrap → announce → lookup → find peer records.
// Uses the SAME topic as the hyper-tps game to verify real peer discovery.
//
// Run: ./test_live_lookup

#include "compact_encoding.h"
#include "dht_messages.h"
#include "dht_rpc.h"
#include "hyperdht.h"

#include <cstdio>
#include <cstring>
#include <sodium.h>

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

// ---- Test state ----

struct TestContext {
	uv_loop_t loop;
	udx_t udx;
	udx_socket_t socket;
	uv_timer_t timer;
	uv_timer_t timeout_timer;

	DhtRpc rpc;
	HyperDht dht;

	bool bootstrapped;
	int peers_found;
	uint64_t start_time;

	uint8_t topic[32];
	Ed25519KeyPair keypair;
};

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;

	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);
	ctx->dht.tick(now);
}

static void on_timeout(uv_timer_t *handle) {
	TestContext *ctx = static_cast<TestContext *>(handle->data);
	printf("\n=== TIMEOUT (30s) ===\n");
	printf("bootstrapped=%d peers_found=%d\n",
		   ctx->bootstrapped, ctx->peers_found);
	printf("routing_table_size=%d\n", ctx->rpc.table().size());

	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);
}

int main() {
	if (sodium_init() < 0) {
		fprintf(stderr, "sodium_init failed\n");
		return 1;
	}

	printf("=== Live DHT Lookup Test ===\n\n");

	TestContext ctx{};
	// Zero only POD members — do NOT memset the whole struct,
	// as DhtRpc/HyperDht contain std::unordered_map/std::vector.
	ctx.bootstrapped = false;
	ctx.peers_found = 0;
	ctx.start_time = 0;
	memset(ctx.topic, 0, 32);
	memset(&ctx.keypair, 0, sizeof(ctx.keypair));

	// Compute topic: SHA256("hyper-game:room:bi34945zeu5sn14yeq1jezjiahi1hdrm647rknrqnj5t75jnhb8o")
	// Pre-computed: 5979947dea24abc1149a34ed96b5745937a26a587423185347b3dfb810734aaa
	const char *room_str = "hyper-game:room:bi34945zeu5sn14yeq1jezjiahi1hdrm647rknrqnj5t75jnhb8o";
	// Use crypto_generichash (BLAKE2b) to match SHA256 behavior? No, game uses SHA256.
	// Actually game GDScript uses: topic_hash = "hyper-game:room:" + room_id then SHA256.
	// Let's compute with crypto_hash_sha256.
	crypto_hash_sha256(ctx.topic, (const unsigned char *)room_str, strlen(room_str));

	printf("Topic: ");
	for (int i = 0; i < 32; i++) printf("%02x", ctx.topic[i]);
	printf("\n");

	// Generate Ed25519 keypair.
	crypto_sign_keypair(ctx.keypair.public_key, ctx.keypair.secret_key);
	printf("Our PK: ");
	for (int i = 0; i < 8; i++) printf("%02x", ctx.keypair.public_key[i]);
	printf("...\n\n");

	// 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);

	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);

	// Initialize HyperDht.
	ctx.dht.init(&ctx.rpc, ctx.keypair);

	// Wire up callbacks.
	ctx.rpc.on_bootstrapped = [&ctx]() {
		ctx.bootstrapped = true;
		uint64_t elapsed = uv_now(&ctx.loop) - ctx.start_time;
		printf("\n--- Bootstrap completed in %lu ms ---\n", (unsigned long)elapsed);
		printf("Routing table: %d nodes\n\n", ctx.rpc.table().size());

		// Step 1: Join topic (announce + lookup).
		printf("--- Joining topic (announce + lookup) ---\n");
		ctx.dht.join_topic(ctx.topic, true, true);
	};

	ctx.dht.on_peer_found = [&ctx](uint32_t query_id, const FoundPeer &peer) {
		ctx.peers_found++;
		printf("\n*** PEER FOUND! qid=%u pk=", query_id);
		for (int i = 0; i < 8; i++) printf("%02x", peer.public_key[i]);
		printf("... relays=%d relay_node=%d.%d.%d.%d:%d ***\n",
			   peer.relay_count,
			   peer.relay_node.ip[0], peer.relay_node.ip[1],
			   peer.relay_node.ip[2], peer.relay_node.ip[3], peer.relay_node.port);

		if (memcmp(peer.public_key, ctx.keypair.public_key, 32) == 0) {
			printf("  (this is our own announce)\n");
		} else {
			printf("  (this is a REMOTE PEER!)\n");
		}
	};

	// Tick timer (50ms interval).
	uv_timer_init(&ctx.loop, &ctx.timer);
	ctx.timer.data = &ctx;
	uv_timer_start(&ctx.timer, on_tick, 0, 50);

	// 30s timeout.
	uv_timer_init(&ctx.loop, &ctx.timeout_timer);
	ctx.timeout_timer.data = &ctx;
	uv_timer_start(&ctx.timeout_timer, on_timeout, 30000, 0);

	// Bootstrap.
	ctx.start_time = uv_now(&ctx.loop);
	ctx.rpc.bootstrap();

	printf("Running... (30s timeout)\n\n");

	uv_run(&ctx.loop, UV_RUN_DEFAULT);

	// Results.
	printf("\n=== Results ===\n");
	printf("Bootstrapped: %s\n", ctx.bootstrapped ? "yes" : "NO");
	printf("Peers found:  %d\n", ctx.peers_found);

	if (ctx.peers_found > 0) {
		printf("\nSUCCESS: DHT lookup found peers!\n");
	} else if (ctx.bootstrapped) {
		printf("\nFAILURE: Bootstrapped but no peers found via lookup.\n");
		printf("Check the DHT_LOG output above for details.\n");
	} else {
		printf("\nFAILURE: Could not bootstrap to DHT network.\n");
	}

	uv_loop_close(&ctx.loop);
	return ctx.peers_found > 0 ? 0 : 1;
}