OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_self_announce.cpp

124 lines · cpp ref:
// Gate: a listening peer must keep re-announcing its OWN key.
//
// docs/PARITY-hyperswarm.md §9. listen() announced BLAKE2b(our public key)
// exactly once, and nothing refreshed it — the self-announce is not a
// TopicDiscovery, so _tick_topics never saw it. DHT records expire; when ours
// did, find_peer(our_pk) found nothing and the peer was unreachable until
// restart, while its TOPIC records (which are refreshed) kept it looking
// discoverable. That is the "still discoverable, no longer reachable" symptom.
//
// JS keeps this alive in hyperdht's Announcer, one per SERVER, targeting
// hash(keyPair.publicKey) (server.js:173-175) — a component distinct from
// hyperswarm's topic-level PeerDiscovery, and one we do not have.
//
// RED before the fix: self_announce_count() stays at 1 for ever.

#include <cstdio>
#include <cstring>

#include <sodium.h>

#include "hyperdht.h"

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

static int checks = 0, failures = 0;
static void check(bool cond, const char *what) {
	checks++;
	if (cond) { printf("  PASS: %s\n", what); return; }
	failures++;
	printf("  FAIL: %s\n", what);
}

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

	uv_loop_t loop;
	uv_loop_init(&loop);
	udx_t udx;
	udx_init(&loop, &udx, nullptr);
	udx_socket_t sock;
	udx_socket_init(&udx, &sock, nullptr);
	struct sockaddr_in addr;
	uv_ip4_addr("127.0.0.1", 0, &addr);
	udx_socket_bind(&sock, (struct sockaddr *)&addr, 0);

	DhtRpc rpc;
	rpc.init(&sock, &loop);

	Ed25519KeyPair kp;
	crypto_sign_keypair(kp.public_key, kp.secret_key);

	HyperDht dht;
	dht.init(&rpc, kp);

	uint64_t now = 1000;
	dht.tick(now);   // establish the clock before listening

	check(dht.self_announce_count() == 0, "nothing announced before listen()");

	dht.listen();
	check(dht.self_announce_count() == 1, "listen() announces our own key");
	check(dht.self_announce_due_at() > now, "and schedules the next one");

	// Well short of the interval: nothing more should happen.
	now += SELF_ANNOUNCE_UPDATE_MS / 2;
	dht.tick(now);
	check(dht.self_announce_count() == 1,
		  "no re-announce before the interval elapses");

	// Past it: exactly one more.
	now += SELF_ANNOUNCE_UPDATE_MS;
	dht.tick(now);
	check(dht.self_announce_count() == 2,
		  "re-announced once the interval elapsed");

	// And it keeps going — the defect was that it happened once, ever.
	for (int i = 0; i < 5; i++) {
		now += SELF_ANNOUNCE_UPDATE_MS;
		dht.tick(now);
	}
	check(dht.self_announce_count() == 7,
		  "keeps re-announcing on every interval, not just once");

	// Ticking repeatedly inside one interval must not multiply announces:
	// a record refreshed 100x a second would be a different defect.
	uint64_t before = dht.self_announce_count();
	for (int i = 0; i < 50; i++) dht.tick(now + i);
	check(dht.self_announce_count() == before,
		  "many ticks inside one interval produce no extra announces");

	// ---- holder liveness (§15/§12.1) ----
	//
	// With no holders recorded there is nothing to ping, and the round must be
	// a no-op rather than a division by zero or a spurious re-announce.
	check(dht.self_holder_count() == 0,
		  "no holders recorded without a real DHT to answer the announce");

	uint64_t rounds_before = dht.self_ping_rounds();
	for (int i = 0; i < 10; i++) {
		now += SELF_ANNOUNCE_PING_MS;
		dht.tick(now);
	}
	check(dht.self_ping_rounds() == rounds_before,
		  "no ping rounds while no holder is known");

	uint64_t announces_before = dht.self_announce_count();
	for (int i = 0; i < 10; i++) {
		now += SELF_ANNOUNCE_PING_MS;
		dht.tick(now);
	}
	check(dht.self_announce_count() == announces_before,
		  "an empty holder set does NOT trigger a re-announce storm");

	printf("\n%d/%d passed\n", checks - failures, checks);

	udx_socket_close(&sock);
	uv_run(&loop, UV_RUN_NOWAIT);
	uv_loop_close(&loop);
	return failures ? 1 : 0;
}