// Gate: against a REAL testnet, a listening peer records the nodes that took its
// announce and pings them.
//
// test_self_announce_live <host:port[,host:port...]>
// driven by test/run_self_announce_live.js, which stands up the testnet
//
// ⛔ WHY THIS EXISTS. test_self_announce.cpp is 10/10 and proves less than it
// looks like it does: it has no DHT, so nothing ever answers an announce,
// self_holder_count() is 0 throughout, and the recording, the ping round and the
// verdict are all exercised only in their EMPTY form. Every claim about the
// POPULATED path was untested. See docs/PARITY-hyperswarm.md §16.
//
// What this asserts, all of which requires real responders:
// - holders are recorded from real commit replies (§15: they used to be
// guessed from table().closest() with a zeroed token)
// - the recorded token is NOT zero
// - the ping round actually runs against them
// - the holders answer, so the re-announce is NOT triggered spuriously
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <sodium.h>
#include "dht_rpc.h"
#include "hyperdht.h"
extern "C" {
#include <udx.h>
#include <uv.h>
}
struct Ctx {
uv_loop_t loop;
udx_t udx;
udx_socket_t socket;
uv_timer_t timer;
uv_timer_t deadline;
DhtRpc rpc;
HyperDht dht;
bool listened = false;
int holders_seen = 0;
bool token_nonzero = false;
uint64_t rounds_at_first_holder = 0;
uint64_t announces_at_first_holder = 0;
uint64_t stop_at = 0;
};
static void on_socket_recv(udx_socket_t *sock, ssize_t read_len, const uv_buf_t *buf,
const struct sockaddr *from) {
Ctx *ctx = static_cast<Ctx *>(sock->data);
if (!ctx || read_len <= 0) return;
const uint8_t *data = (const uint8_t *)buf->base;
if ((size_t)read_len >= 4 && (data[0] == DHT_REQUEST_ID || data[0] == DHT_RESPONSE_ID)) {
ctx->rpc.on_message(data, (size_t)read_len, (const struct sockaddr_in *)from);
}
}
static void on_tick(uv_timer_t *h) {
Ctx *ctx = static_cast<Ctx *>(h->data);
const uint64_t now = uv_now(&ctx->loop);
ctx->rpc.tick(now);
ctx->dht.tick(now);
if (!ctx->listened && ctx->rpc.is_bootstrapped()) {
ctx->listened = true;
printf(" bootstrapped, announcing self\n");
ctx->dht.listen();
}
const int holders = ctx->dht.self_holder_count();
if (holders > ctx->holders_seen) {
if (ctx->holders_seen == 0) {
ctx->rounds_at_first_holder = ctx->dht.self_ping_rounds();
ctx->announces_at_first_holder = ctx->dht.self_announce_count();
// Give the ping loop several rounds with holders present.
ctx->stop_at = now + 12000;
printf(" first holder recorded at %llums\n", (unsigned long long)now);
}
ctx->holders_seen = holders;
}
if (ctx->stop_at && now >= ctx->stop_at) uv_stop(&ctx->loop);
}
static void on_deadline(uv_timer_t *h) {
uv_stop(&static_cast<Ctx *>(h->data)->loop);
}
int main(int argc, char **argv) {
if (sodium_init() < 0) return 1;
if (argc < 2) { printf("usage: %s <host:port,...>\n", argv[0]); return 2; }
std::vector<std::string> boots;
{
std::string all(argv[1]);
size_t p = 0;
while (p <= all.size()) {
size_t c = all.find(',', p);
if (c == std::string::npos) c = all.size();
if (c > p) boots.push_back(all.substr(p, c - p));
p = c + 1;
}
}
std::vector<const char *> bootv;
for (auto &b : boots) bootv.push_back(b.c_str());
printf(" bootstrap: %s\n", argv[1]);
Ctx *ctx = new Ctx();
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);
udx_socket_recv_start(&ctx->socket, on_socket_recv);
ctx->rpc.init(&ctx->socket, &ctx->loop);
ctx->rpc.set_bootstrap(bootv.data(), (int)bootv.size());
struct sockaddr_in local_addr;
int alen = sizeof(local_addr);
udx_socket_getsockname(&ctx->socket, (struct sockaddr *)&local_addr, &alen);
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);
Ed25519KeyPair kp;
crypto_sign_keypair(kp.public_key, kp.secret_key);
ctx->dht.init(&ctx->rpc, kp);
ctx->rpc.bootstrap();
uv_timer_init(&ctx->loop, &ctx->timer);
ctx->timer.data = ctx;
uv_timer_start(&ctx->timer, on_tick, 0, 20);
uv_timer_init(&ctx->loop, &ctx->deadline);
ctx->deadline.data = ctx;
uv_timer_start(&ctx->deadline, on_deadline, 45000, 0);
uv_run(&ctx->loop, UV_RUN_DEFAULT);
int checks = 0, failures = 0;
auto check = [&](bool c, const char *what) {
checks++;
if (c) { printf(" PASS: %s\n", what); return; }
failures++;
printf(" FAIL: %s\n", what);
};
printf("\n");
check(ctx->listened, "reached the testnet and announced");
check(ctx->holders_seen > 0,
"recorded at least one node that ACCEPTED the announce");
// ⛔ THE ONE THAT DISTINGUISHES THE FIX. The routing-table guess this
// replaced also populated the node list — from table().closest() — so
// "holders > 0" passes either way. It wrote `memset(token, 0, 32)`, so a
// real token is the only externally visible difference.
check(ctx->dht.self_holder_tokens() == ctx->holders_seen,
"every recorded holder carries a REAL token, not a zeroed one");
check(ctx->dht.self_ping_rounds() > ctx->rounds_at_first_holder,
"ping rounds ran against the recorded holders");
// Holders on a healthy testnet answer, so the failure path must NOT fire.
check(ctx->dht.self_announce_count() <= ctx->announces_at_first_holder + 1,
"live holders answered, so no spurious re-announce storm");
printf("\n holders=%d tokens=%d ping_rounds=%llu announces=%llu\n",
ctx->holders_seen, ctx->dht.self_holder_tokens(),
(unsigned long long)ctx->dht.self_ping_rounds(),
(unsigned long long)ctx->dht.self_announce_count());
printf("\n%d/%d passed\n", checks - failures, checks);
return failures ? 1 : 0;
}