// Gate: a connect attempt must fail when the find_peer query EXHAUSTS, not
// when a 30-second wall clock expires.
//
// docs/PARITY-hyperswarm.md §11.2. JS has no overall connect timer at all —
// its find_peer stage ends when the iterative query finishes
// (connect.js:381), so a peer that is not there fails as fast as the DHT can
// say so, and the connect slot is released. Ours held the slot for
// DHT_CONNECT_TIMEOUT_MS regardless. With SWARM_MAX_PARALLEL = 3 that is three
// dead peers monopolising every outbound slot for 30s each, and a topic
// carrying stale announce records is precisely a supply of dead peers.
//
// RED before the fix: on_find_peer_complete is a stub, so nothing fails until
// the timeout, and this reports the slot still held.
#include <cstdio>
#include <cstring>
#include <sodium.h>
#include "dht_connector.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);
DhtConnector connector;
connector.init(&dht, &sock, &loop);
int failed_count = 0;
connector.on_failed = [&failed_count](uint32_t, const uint8_t *, const char *err) {
failed_count++;
printf(" (on_failed: %s)\n", err ? err : "?");
};
// A peer that does not exist. find_peer will be issued and will find nobody.
uint8_t remote_pk[32];
randombytes_buf(remote_pk, 32);
uint32_t conn_id = connector.connect(remote_pk);
check(conn_id != 0, "connect() started an attempt");
// The query the connector is waiting on.
const DhtConnection *conn = connector.find_connection(conn_id);
check(conn != nullptr, "the connection exists");
if (!conn) { printf("\n%d/%d passed\n", checks - failures, checks); return 1; }
const uint32_t qid = conn->find_peer_qid;
// Nothing has failed yet: the query is still running.
check(failed_count == 0, "nothing failed while the query was still running");
// ⛔ THE QUERY EXHAUSTS. This is the event JS acts on.
connector.on_find_peer_complete(qid);
check(failed_count == 1,
"the attempt failed as soon as find_peer exhausted");
// And it must not have needed the wall clock. Tick to just under the old
// timeout: if the fix works nothing more happens; if it does not, this is
// where the failure would finally have been reported.
connector.tick(DHT_CONNECT_TIMEOUT_MS - 1000);
check(failed_count == 1,
"no second failure, and the slot was not held to the timeout");
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;
}