// Local regression test for the pool's rendezvous accept path. // // Reproduces the exact failure that took remote miners off the pool: since // upstream #30, RvServiceRegister returns as soon as the relay lists the // service and no longer consumes the relay's 0x01 pairing byte. A service that // goes straight from RvServiceRegister to BtfServiceWrap therefore reads that // 0x01 as the first byte of the client's 32-byte ephemeral key, shifting the // whole stream by one. X25519 has no confirmation step, so the handshake // "succeeds" against a garbage shared secret and the corruption only shows up // as a tunnel that never carries a valid byte. // // Build: g++ -std=gnu++14 -O2 -pthread test_rvaccept.cpp btftunnel.cpp btfrv.cpp \ // btfchan.cpp -lsodium -o test_rvaccept // // Build: g++ -std=gnu++14 -O2 -pthread test_rvaccept.cpp btftunnel.cpp \ // btfrv.cpp btfchan.cpp -lsodium -o test_rvaccept // // Run against a bitflashd relay already listening on 127.0.0.1:. // test_rvaccept broken -- the pre-fix pool code path, expected to FAIL // test_rvaccept fixed -- with RvServiceWaitPaired, expected to PASS #include "btftunnel.h" #include "btfrv.h" #include "btfchan.h" #include #include #include #include #include #include #include #include #include #include using namespace btf; static const char* HOST = "127.0.0.1"; static unsigned short PORT = 18446; static const char* MSG = "mining.subscribe stand-in payload"; static void SetTimeout(int fd, int secs) { struct timeval tv; tv.tv_sec = secs; tv.tv_usec = 0; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); } static bool RecvN(int fd, void* buf, int n) { char* p = (char*)buf; int off = 0; while (off < n) { int r = recv(fd, p + off, n - off, 0); if (r <= 0) return false; off += r; } return true; } static bool SendN(int fd, const void* buf, int n) { const char* p = (const char*)buf; int off = 0; while (off < n) { int r = send(fd, p + off, n - off, 0); if (r <= 0) return false; off += r; } return true; } static bool g_serviceEcho = false; static std::string g_serviceSaw; // The pool's AcceptOneFn, in both shapes. static void ServiceThread(bool fWaitPaired, std::vector id, std::vector enc_sk, int len) { RvSocket rv = RvServiceRegister(HOST, PORT, id.data()); if (rv == RV_INVALID) { printf(" service: RvServiceRegister failed\n"); return; } if (fWaitPaired) { if (!RvServiceWaitPaired(rv)) { printf(" service: RvServiceWaitPaired failed\n"); RvClose(rv); return; } printf(" service: paired (0x01 consumed by RvServiceWaitPaired)\n"); } else { printf(" service: skipped RvServiceWaitPaired (pre-fix behaviour)\n"); } btf_socket_t app = BtfServiceWrap(rv, enc_sk.data()); if (app == INVALID_SOCKET) { printf(" service: BtfServiceWrap failed\n"); return; } printf(" service: BtfServiceWrap returned a tunnel\n"); SetTimeout(app, 5); std::vector buf(len); if (!RecvN(app, buf.data(), len)) { printf(" service: never received the client's plaintext\n"); close(app); return; } g_serviceSaw.assign(buf.data(), len); g_serviceEcho = SendN(app, buf.data(), len); close(app); } int main(int argc, char* argv[]) { bool fWaitPaired = (argc >= 2 && strcmp(argv[1], "fixed") == 0); if (argc >= 3) PORT = (unsigned short)atoi(argv[2]); if (!TunnelInit()) { printf("TunnelInit failed\n"); return 2; } unsigned char enc_pk[32], enc_sk[32]; ChanKeypair(enc_pk, enc_sk); std::vector id(32); for (int i = 0; i < 32; i++) id[i] = (unsigned char)(i * 7 + 13); const std::string msg(MSG); const int len = (int)msg.size(); printf(" mode: %s relay: %s:%u\n", fWaitPaired ? "fixed" : "broken", HOST, PORT); std::thread svc(ServiceThread, fWaitPaired, id, std::vector(enc_sk, enc_sk + 32), len); std::this_thread::sleep_for(std::chrono::milliseconds(500)); int nFail = 0; btf_socket_t app = BtfClientTunnel(HOST, PORT, id.data(), enc_pk); if (app == INVALID_SOCKET) { printf(" client: BtfClientTunnel failed\n"); nFail++; } else { printf(" client: tunnel established\n"); SetTimeout(app, 5); if (!SendN(app, msg.data(), len)) { printf(" client: send failed\n"); nFail++; } std::vector echo(len); if (!RecvN(app, echo.data(), len)) { printf(" client: no echo came back\n"); nFail++; } else if (std::string(echo.data(), len) != msg) { printf(" client: echo did not match what was sent\n"); nFail++; } else { printf(" client: round-tripped the payload intact\n"); } close(app); } svc.join(); if (g_serviceSaw != msg) { printf(" service did not see the correct plaintext (saw %d bytes)\n", (int)g_serviceSaw.size()); nFail++; } if (!g_serviceEcho) { printf(" service could not echo\n"); nFail++; } bool fPassed = (nFail == 0); printf(" RESULT: %s\n", fPassed ? "PASS -- tunnel carries plaintext end to end" : "FAIL -- tunnel never carried a valid byte"); // A broken run is the expected outcome pre-fix, so exit 0 when the // observed behaviour matches the expectation for the mode under test. bool fAsExpected = (fWaitPaired ? fPassed : !fPassed); printf(" EXPECTED %s -> %s\n", fWaitPaired ? "PASS" : "FAIL", fAsExpected ? "as expected" : "UNEXPECTED"); return fAsExpected ? 0 : 1; }