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