| … | … | @@ -0,0 +1,180 @@ |
| 1 | // Gate: against a REAL testnet, a listening peer records the nodes that took its |
| 2 | // announce and pings them. |
| 3 | // |
| 4 | // test_self_announce_live <host:port[,host:port...]> |
| 5 | // driven by test/run_self_announce_live.js, which stands up the testnet |
| 6 | // |
| 7 | // ⛔ WHY THIS EXISTS. test_self_announce.cpp is 10/10 and proves less than it |
| 8 | // looks like it does: it has no DHT, so nothing ever answers an announce, |
| 9 | // self_holder_count() is 0 throughout, and the recording, the ping round and the |
| 10 | // verdict are all exercised only in their EMPTY form. Every claim about the |
| 11 | // POPULATED path was untested. See docs/PARITY-hyperswarm.md §16. |
| 12 | // |
| 13 | // What this asserts, all of which requires real responders: |
| 14 | // - holders are recorded from real commit replies (§15: they used to be |
| 15 | // guessed from table().closest() with a zeroed token) |
| 16 | // - the recorded token is NOT zero |
| 17 | // - the ping round actually runs against them |
| 18 | // - the holders answer, so the re-announce is NOT triggered spuriously |
| 19 | |
| 20 | #include <cstdio> |
| 21 | #include <cstdlib> |
| 22 | #include <cstring> |
| 23 | #include <string> |
| 24 | #include <vector> |
| 25 | |
| 26 | #include <sodium.h> |
| 27 | |
| 28 | #include "dht_rpc.h" |
| 29 | #include "hyperdht.h" |
| 30 | |
| 31 | extern "C" { |
| 32 | #include <udx.h> |
| 33 | #include <uv.h> |
| 34 | } |
| 35 | |
| 36 | struct Ctx { |
| 37 | uv_loop_t loop; |
| 38 | udx_t udx; |
| 39 | udx_socket_t socket; |
| 40 | uv_timer_t timer; |
| 41 | uv_timer_t deadline; |
| 42 | DhtRpc rpc; |
| 43 | HyperDht dht; |
| 44 | bool listened = false; |
| 45 | int holders_seen = 0; |
| 46 | bool token_nonzero = false; |
| 47 | uint64_t rounds_at_first_holder = 0; |
| 48 | uint64_t announces_at_first_holder = 0; |
| 49 | uint64_t stop_at = 0; |
| 50 | }; |
| 51 | |
| 52 | static void on_socket_recv(udx_socket_t *sock, ssize_t read_len, const uv_buf_t *buf, |
| 53 | const struct sockaddr *from) { |
| 54 | Ctx *ctx = static_cast<Ctx *>(sock->data); |
| 55 | if (!ctx || read_len <= 0) return; |
| 56 | const uint8_t *data = (const uint8_t *)buf->base; |
| 57 | if ((size_t)read_len >= 4 && (data[0] == DHT_REQUEST_ID || data[0] == DHT_RESPONSE_ID)) { |
| 58 | ctx->rpc.on_message(data, (size_t)read_len, (const struct sockaddr_in *)from); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | static void on_tick(uv_timer_t *h) { |
| 63 | Ctx *ctx = static_cast<Ctx *>(h->data); |
| 64 | const uint64_t now = uv_now(&ctx->loop); |
| 65 | ctx->rpc.tick(now); |
| 66 | ctx->dht.tick(now); |
| 67 | |
| 68 | if (!ctx->listened && ctx->rpc.is_bootstrapped()) { |
| 69 | ctx->listened = true; |
| 70 | printf(" bootstrapped, announcing self\n"); |
| 71 | ctx->dht.listen(); |
| 72 | } |
| 73 | |
| 74 | const int holders = ctx->dht.self_holder_count(); |
| 75 | if (holders > ctx->holders_seen) { |
| 76 | if (ctx->holders_seen == 0) { |
| 77 | ctx->rounds_at_first_holder = ctx->dht.self_ping_rounds(); |
| 78 | ctx->announces_at_first_holder = ctx->dht.self_announce_count(); |
| 79 | // Give the ping loop several rounds with holders present. |
| 80 | ctx->stop_at = now + 12000; |
| 81 | printf(" first holder recorded at %llums\n", (unsigned long long)now); |
| 82 | } |
| 83 | ctx->holders_seen = holders; |
| 84 | } |
| 85 | |
| 86 | if (ctx->stop_at && now >= ctx->stop_at) uv_stop(&ctx->loop); |
| 87 | } |
| 88 | |
| 89 | static void on_deadline(uv_timer_t *h) { |
| 90 | uv_stop(&static_cast<Ctx *>(h->data)->loop); |
| 91 | } |
| 92 | |
| 93 | int main(int argc, char **argv) { |
| 94 | if (sodium_init() < 0) return 1; |
| 95 | if (argc < 2) { printf("usage: %s <host:port,...>\n", argv[0]); return 2; } |
| 96 | |
| 97 | std::vector<std::string> boots; |
| 98 | { |
| 99 | std::string all(argv[1]); |
| 100 | size_t p = 0; |
| 101 | while (p <= all.size()) { |
| 102 | size_t c = all.find(',', p); |
| 103 | if (c == std::string::npos) c = all.size(); |
| 104 | if (c > p) boots.push_back(all.substr(p, c - p)); |
| 105 | p = c + 1; |
| 106 | } |
| 107 | } |
| 108 | std::vector<const char *> bootv; |
| 109 | for (auto &b : boots) bootv.push_back(b.c_str()); |
| 110 | printf(" bootstrap: %s\n", argv[1]); |
| 111 | |
| 112 | Ctx *ctx = new Ctx(); |
| 113 | uv_loop_init(&ctx->loop); |
| 114 | udx_init(&ctx->loop, &ctx->udx, nullptr); |
| 115 | udx_socket_init(&ctx->udx, &ctx->socket, nullptr); |
| 116 | ctx->socket.data = ctx; |
| 117 | |
| 118 | struct sockaddr_in bind_addr; |
| 119 | uv_ip4_addr("0.0.0.0", 0, &bind_addr); |
| 120 | udx_socket_bind(&ctx->socket, (struct sockaddr *)&bind_addr, 0); |
| 121 | udx_socket_recv_start(&ctx->socket, on_socket_recv); |
| 122 | |
| 123 | ctx->rpc.init(&ctx->socket, &ctx->loop); |
| 124 | ctx->rpc.set_bootstrap(bootv.data(), (int)bootv.size()); |
| 125 | |
| 126 | struct sockaddr_in local_addr; |
| 127 | int alen = sizeof(local_addr); |
| 128 | udx_socket_getsockname(&ctx->socket, (struct sockaddr *)&local_addr, &alen); |
| 129 | IPv4Address local; |
| 130 | memcpy(local.ip, &local_addr.sin_addr.s_addr, 4); |
| 131 | local.port = ntohs(local_addr.sin_port); |
| 132 | ctx->rpc.set_local_address(local); |
| 133 | |
| 134 | Ed25519KeyPair kp; |
| 135 | crypto_sign_keypair(kp.public_key, kp.secret_key); |
| 136 | ctx->dht.init(&ctx->rpc, kp); |
| 137 | |
| 138 | ctx->rpc.bootstrap(); |
| 139 | |
| 140 | uv_timer_init(&ctx->loop, &ctx->timer); |
| 141 | ctx->timer.data = ctx; |
| 142 | uv_timer_start(&ctx->timer, on_tick, 0, 20); |
| 143 | |
| 144 | uv_timer_init(&ctx->loop, &ctx->deadline); |
| 145 | ctx->deadline.data = ctx; |
| 146 | uv_timer_start(&ctx->deadline, on_deadline, 45000, 0); |
| 147 | |
| 148 | uv_run(&ctx->loop, UV_RUN_DEFAULT); |
| 149 | |
| 150 | int checks = 0, failures = 0; |
| 151 | auto check = [&](bool c, const char *what) { |
| 152 | checks++; |
| 153 | if (c) { printf(" PASS: %s\n", what); return; } |
| 154 | failures++; |
| 155 | printf(" FAIL: %s\n", what); |
| 156 | }; |
| 157 | |
| 158 | printf("\n"); |
| 159 | check(ctx->listened, "reached the testnet and announced"); |
| 160 | check(ctx->holders_seen > 0, |
| 161 | "recorded at least one node that ACCEPTED the announce"); |
| 162 | // ⛔ THE ONE THAT DISTINGUISHES THE FIX. The routing-table guess this |
| 163 | // replaced also populated the node list — from table().closest() — so |
| 164 | // "holders > 0" passes either way. It wrote `memset(token, 0, 32)`, so a |
| 165 | // real token is the only externally visible difference. |
| 166 | check(ctx->dht.self_holder_tokens() == ctx->holders_seen, |
| 167 | "every recorded holder carries a REAL token, not a zeroed one"); |
| 168 | check(ctx->dht.self_ping_rounds() > ctx->rounds_at_first_holder, |
| 169 | "ping rounds ran against the recorded holders"); |
| 170 | // Holders on a healthy testnet answer, so the failure path must NOT fire. |
| 171 | check(ctx->dht.self_announce_count() <= ctx->announces_at_first_holder + 1, |
| 172 | "live holders answered, so no spurious re-announce storm"); |
| 173 | |
| 174 | printf("\n holders=%d tokens=%d ping_rounds=%llu announces=%llu\n", |
| 175 | ctx->holders_seen, ctx->dht.self_holder_tokens(), |
| 176 | (unsigned long long)ctx->dht.self_ping_rounds(), |
| 177 | (unsigned long long)ctx->dht.self_announce_count()); |
| 178 | printf("\n%d/%d passed\n", checks - failures, checks); |
| 179 | return failures ? 1 : 0; |
| 180 | } |