// Gate: a peer discovered on a SECOND topic must record it, even while we are
// already connected to that peer.
//
// docs/PARITY-hyperswarm.md §10. JS _handlePeer (hyperswarm/index.js:449)
// associates the topic BEFORE its already-connected early return:
//
// const peerInfo = this._upsertPeer(...)
// if (peerInfo) peerInfo._topic(topic)
// if (!peerInfo || this._allConnections.has(peer.publicKey)) return
//
// Ours ran the association AFTER `if (info->peer_id != 0) return`, so the second
// topic was never recorded. That is not a cosmetic asymmetry: _should_requeue
// decides reconnection by walking exactly that list, so a peer found on A and B,
// connected via A, and dropped after A was left is requeued by JS and abandoned
// by us.
//
// RED before the fix: the peer is not requeued and the queue stays empty.
#include <cstdio>
#include <cstring>
#include <sodium.h>
#include "dht_connector.h"
#include "hyperdht.h"
#include "swarm_manager.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);
SwarmManager swarm;
swarm.init(&connector, &dht, kp.public_key);
uint8_t topic_a[32], topic_b[32], peer_pk[32];
memset(topic_a, 0xaa, 32);
memset(topic_b, 0xbb, 32);
randombytes_buf(peer_pk, 32);
uint64_t now = 1000;
swarm.tick(now); // establish the clock before anything is timed
swarm.join(topic_a, true, true);
swarm.join(topic_b, true, true);
// Discovered on A, then connected via A.
swarm.on_peer_found(peer_pk, topic_a);
const int32_t peer_id = 4242;
swarm.on_peer_connected(peer_id, peer_pk, true);
check(swarm.get_status().peers_connected >= 1, "connected via topic A");
// ⛔ NOW DISCOVERED ON B, while already connected. JS records this; we did not.
swarm.on_peer_found(peer_pk, topic_b);
// Leave A. B is still joined, so the peer is still wanted.
swarm.leave(topic_a);
// Drop the connection. _should_requeue walks the peer's topics against the
// joined set: if B was recorded it finds B and requeues; if not, it finds
// nothing and the peer is abandoned.
swarm.on_peer_disconnected(peer_id);
// ⚠ A DISCONNECT DOES NOT QUEUE THE PEER DIRECTLY. on_peer_disconnected sets
// info->waiting = _should_requeue(info) && _retry.add(info)
// so the peer enters the RETRY TIMER and only reaches the queue when that
// timer fires on a later tick. The first version of this gate asserted
// queue_size straight after the disconnect and failed against a working
// fix — the assertion was wrong, not the code.
bool requeued = false;
for (int i = 0; i < 200 && !requeued; i++) {
now += 100;
swarm.tick(now);
const SwarmStatus s = swarm.get_status();
if (s.queue_size > 0 || s.connecting > 0) requeued = true;
}
check(requeued,
"the peer is requeued because topic B is still joined");
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;
}