From 51d617e70f90a972185917ea0096c1ecef2bfa8b Mon Sep 17 00:00:00 2001 From: Bitflash-sh Date: Fri, 24 Jul 2026 11:18:11 -0300 Subject: Bitflash: fair-launch release Satoshi's bitcoin-0.1.0 reborn with RandomX CPU proof of work, anonymous .btf addressing over Nostr with CGNAT rendezvous, automatic peer discovery, a headless Linux node, and Windows GUI wallet. Fair launch, no premine, 21M cap. --- src/btftunnel.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/btftunnel.h (limited to 'src/btftunnel.h') diff --git a/src/btftunnel.h b/src/btftunnel.h new file mode 100644 index 0000000..1fa4f01 --- /dev/null +++ b/src/btftunnel.h @@ -0,0 +1,49 @@ +// Copyright (c) 2026 Bitflash developers +// Distributed under the MIT/X11 software license. +// +// Transparent anonymous tunnel. Gives the existing CNode a plain local socket +// while, behind it, a pair of pump threads bridge plaintext <-> echan <-> +// rendezvous. CNode reads/writes normally and never knows its bytes travel +// end-to-end encrypted through a meeting node (the Itzal `wrap()` pattern). +// +// Client side: resolve a `.btf` -> meeting node + service enc key (done by the +// caller), then BtfClientTunnel connects through the relay, runs the echan +// handshake, and returns a local SOCKET to hand a new outbound CNode. +// +// Service side: after RvServiceRegister pairs us with a client, BtfServiceWrap +// completes the echan handshake and returns a local SOCKET for an inbound CNode. + +#ifndef BITFLASH_BTFTUNNEL_H +#define BITFLASH_BTFTUNNEL_H + +#include "btfrv.h" + +#ifdef _WIN32 +#include +typedef SOCKET btf_socket_t; +#else +typedef int btf_socket_t; +#define INVALID_SOCKET (-1) +#endif + +namespace btf +{ + +// Initialize (sockets + libsodium). Idempotent. +bool TunnelInit(); + +// Client: dial the service registered under `target_pubkey` at the meeting node +// (host:port), box to its static x25519 `service_enc_pub`, and return a local +// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error. +btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, + const unsigned char target_pubkey[32], + const unsigned char service_enc_pub[32]); + +// Service: wrap an already-paired rendezvous socket (from RvServiceRegister) +// with echan using my static x25519 secret, returning a local plaintext SOCKET +// for an inbound CNode. Consumes `rv` (closed when the tunnel tears down). +btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]); + +} // namespace btf + +#endif -- cgit v1.2.3 From 857f57e962b24f51621ddac188fa6e42d0ac6f41 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Sun, 9 Aug 2026 02:43:44 +0000 Subject: pool: wait to be paired before wrapping the rendezvous socket Upstream #30 split rendezvous registration from waiting for a dial, so RvServiceRegister now returns as soon as the relay lists the service and no longer consumes the relay's 0x01 pairing byte. It updated its own caller in net.cpp and left this one behind. AcceptOneFn went straight from RvServiceRegister to BtfServiceWrap, so BtfServiceWrap read that 0x01 as the first byte of the miner's 32-byte ephemeral key and every byte after it was shifted by one. The X25519 handshake has no confirmation step, so it completed against a garbage shared secret, the tunnel was handed to the Stratum event loop, and nothing failed loudly: the pool logged a miner arriving and then dropped it as "never authorized", while the miner logged a pool that closed on it with gotSubscribeAck=0. Loopback miners were unaffected, which is exactly what made it look like a relay problem. Block in RvServiceWaitPaired before wrapping, and close and re-register if the relay drops us first. test_rvaccept.cpp covers both shapes against a real bitflashd on loopback: the pre-fix order establishes a tunnel that carries no bytes, the fixed order round-trips. Also corrects the btftunnel.h contract comment, which still described the pre-#30 behaviour and is what the old code was written against -- upstream's own test_relayproc.cpp, test_btftunnel.cpp and test_btfrv.cpp are still on that stale contract and fail the same way. (cherry picked from commit 3e3a36b2bf1e6ffb4861507089b743688619377f) --- src/btftunnel.h | 13 ++-- src/rpc.cpp | 28 ++++++-- src/test_rvaccept.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 src/test_rvaccept.cpp (limited to 'src/btftunnel.h') diff --git a/src/btftunnel.h b/src/btftunnel.h index 1fa4f01..24a7509 100644 --- a/src/btftunnel.h +++ b/src/btftunnel.h @@ -10,8 +10,12 @@ // caller), then BtfClientTunnel connects through the relay, runs the echan // handshake, and returns a local SOCKET to hand a new outbound CNode. // -// Service side: after RvServiceRegister pairs us with a client, BtfServiceWrap -// completes the echan handshake and returns a local SOCKET for an inbound CNode. +// Service side: RvServiceRegister only registers -- callers must then block in +// RvServiceWaitPaired until a client is actually paired to them. BtfServiceWrap +// completes the echan handshake on that paired socket and returns a local +// SOCKET for an inbound CNode. Wrapping a registered-but-unpaired socket reads +// the relay's 0x01 pairing byte as the first byte of the client's ephemeral +// key, which silently corrupts the tunnel instead of failing. #ifndef BITFLASH_BTFTUNNEL_H #define BITFLASH_BTFTUNNEL_H @@ -39,8 +43,9 @@ btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, const unsigned char target_pubkey[32], const unsigned char service_enc_pub[32]); -// Service: wrap an already-paired rendezvous socket (from RvServiceRegister) -// with echan using my static x25519 secret, returning a local plaintext SOCKET +// Service: wrap an already-paired rendezvous socket (RvServiceRegister followed +// by RvServiceWaitPaired -- the pairing byte MUST already be consumed) with +// echan using my static x25519 secret, returning a local plaintext SOCKET // for an inbound CNode. Consumes `rv` (closed when the tunnel tears down). btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]); diff --git a/src/rpc.cpp b/src/rpc.cpp index 3dd05bb..21a8d77 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -6,8 +6,8 @@ // Architecture: // PoolEventLoop -- single thread, owns all miner fds, all job/share/payout // state. No locks needed on any of that data. -// AcceptOneFn -- one thread per relay. Blocks on RvServiceRegister + -// BtfServiceWrap, pushes finished socket to gReadyQueue. +// AcceptOneFn -- one thread per relay. Registers, waits to be paired, +// wraps the tunnel, pushes finished socket to gReadyQueue. // AcceptLoopFn -- spawns AcceptOneFn threads, re-polls relay list every // 60s so dynamically discovered relays get picked up. // @@ -1388,12 +1388,11 @@ static void AcceptOneFn(void* arg) LogPrint("pool", "[pool] accept thread started for relay %s\n", relay.c_str()); while (!fShutdown && gPoolRunning) { - // Advertise this relay as the pool's meeting node BEFORE registering: - // RvServiceRegister blocks until a miner pairs, and miners can only - // find us if the published descriptor already names a relay where the + // Advertise this relay as the pool's meeting node BEFORE registering, + // so that miners which resolve our descriptor find a relay where the // pool (not the node's peer-accept) holds the registration. BtfSetActiveRelay(relay); - // Blocks until a miner connects to us at this relay + // Returns as soon as the relay lists us -- registered, not yet paired. btf::RvSocket rv = btf::RvServiceRegister(host.c_str(), (unsigned short)port, pk); if (fShutdown || !gPoolRunning) { @@ -1407,6 +1406,23 @@ static void AcceptOneFn(void* arg) continue; } + // Block for a miner to actually arrive. RvServiceRegister used to do + // this itself and returned with the relay's 0x01 pairing byte already + // consumed; it no longer does. Without waiting here, BtfServiceWrap + // reads that 0x01 as the first byte of the miner's 32-byte ephemeral + // key -- every subsequent byte is shifted by one, so the X25519 + // handshake completes against a garbage shared secret (it has no + // confirmation step to catch it), the tunnel is handed to the event + // loop, and the miner's mining.subscribe never decrypts into a valid + // line. The miner sees the pool close on it; the pool sees a peer that + // never authorized. + if (!btf::RvServiceWaitPaired(rv)) { + btf::RvClose(rv); + LogPrint("pool", "[pool] relay %s: dropped us before any miner dialed" + " -- re-registering\n", relay.c_str()); + continue; + } + // Echan handshake -- blocking but fast (local + relay round-trip) btf_socket_t fd = btf::BtfServiceWrap(rv, sk); if (fd == INVALID_SOCKET) { diff --git a/src/test_rvaccept.cpp b/src/test_rvaccept.cpp new file mode 100644 index 0000000..afa9046 --- /dev/null +++ b/src/test_rvaccept.cpp @@ -0,0 +1,180 @@ +// 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; +} -- cgit v1.2.3 From 7c70eb4498e88725241493033edfe89554ff1b18 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 09:03:01 +0000 Subject: tunnel: report which stage failed instead of guessing the cause --- src/btfrv.cpp | 16 ++++++++++++++-- src/btfrv.h | 19 +++++++++++++++++-- src/btftunnel.cpp | 37 +++++++++++++++++++++++++++++++++---- src/btftunnel.h | 25 +++++++++++++++++++++++-- src/main.cpp | 11 ++++++----- 5 files changed, 93 insertions(+), 15 deletions(-) (limited to 'src/btftunnel.h') diff --git a/src/btfrv.cpp b/src/btfrv.cpp index 133eb89..864fe3d 100644 --- a/src/btfrv.cpp +++ b/src/btfrv.cpp @@ -334,17 +334,29 @@ bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs) } RvSocket RvClientConnect(const char* relay_host, unsigned short port, - const unsigned char target_pubkey[32]) + const unsigned char target_pubkey[32], + RvClientFail* pFail) { + if (pFail) *pFail = RVC_FAIL_DIAL; if (!RvInit()) return RV_INVALID; SOCKET s = ConnectTo(relay_host, port); if (s == INVALID_SOCKET) return RV_INVALID; + if (pFail) *pFail = RVC_FAIL_PAIR; unsigned char hdr[33]; hdr[0] = (unsigned char)'C'; memcpy(hdr + 1, target_pubkey, 32); if (!WriteN(s, hdr, 33)) { CLOSESOCK(s); return RV_INVALID; } unsigned char paired = 0; - if (!ReadN(s, &paired, 1) || paired != 0x01) { CLOSESOCK(s); return RV_INVALID; } + if (!ReadN(s, &paired, 1)) { CLOSESOCK(s); return RV_INVALID; } + if (paired != 0x01) + { + // The relay answered and declined: HandleIncoming writes 0x00 and + // closes when no service is parked under this pubkey right now. + if (pFail) *pFail = RVC_FAIL_REFUSED; + CLOSESOCK(s); + return RV_INVALID; + } + if (pFail) *pFail = RVC_FAIL_NONE; return (RvSocket)s; } diff --git a/src/btfrv.h b/src/btfrv.h index 9cd7476..1e85cb0 100644 --- a/src/btfrv.h +++ b/src/btfrv.h @@ -57,10 +57,25 @@ RvSocket RvServiceRegister(const char* relay_host, unsigned short port, bool RvServiceWaitPaired(RvSocket s, int nTimeoutSecs = 0); // ---- client side (the node dialing a .btf address) ---- +// Where a failed RvClientConnect stopped. RV_INVALID alone collapses three +// different situations -- relay unreachable, relay reachable but nobody +// registered, connection died mid-pairing -- and they point at different +// components. The caller can only report the right one if this side says +// which it saw. +enum RvClientFail +{ + RVC_FAIL_NONE = 0, + RVC_FAIL_DIAL, // TCP connect to the relay itself failed + RVC_FAIL_REFUSED, // relay answered 0x00: no service registered under this pubkey + RVC_FAIL_PAIR, // relay connection died before pairing completed +}; + // Connect through the relay to the service registered under `target_pubkey`. -// Returns the paired socket, or RV_INVALID on error. +// Returns the paired socket, or RV_INVALID on error -- with `pFail`, if +// given, naming the stage that failed. RvSocket RvClientConnect(const char* relay_host, unsigned short port, - const unsigned char target_pubkey[32]); + const unsigned char target_pubkey[32], + RvClientFail* pFail = 0); // Blocking read/write of exactly n bytes over a rendezvous socket. Return false // on error/EOF. diff --git a/src/btftunnel.cpp b/src/btftunnel.cpp index 63d178b..408476e 100644 --- a/src/btftunnel.cpp +++ b/src/btftunnel.cpp @@ -136,18 +136,47 @@ static btf_socket_t StartTunnel(RvSocket rv, const unsigned char shared[32]) return appEnd; } +const char* TunnelFailText(TunnelFail f) +{ + switch (f) + { + case TUNNEL_FAIL_NONE: return "no failure"; + case TUNNEL_FAIL_INIT: return "local socket/crypto init failed"; + case TUNNEL_FAIL_DIAL: return "meeting node unreachable (tcp dial failed)"; + case TUNNEL_FAIL_REFUSED: return "relay reachable, but no service registered there for this address"; + case TUNNEL_FAIL_PAIR: return "relay connection died before pairing completed"; + case TUNNEL_FAIL_HANDSHAKE: return "end-to-end handshake failed"; + case TUNNEL_FAIL_LOCAL: return "local tunnel setup failed"; + } + return "unknown failure"; +} + btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, const unsigned char target_pubkey[32], - const unsigned char service_enc_pub[32]) + const unsigned char service_enc_pub[32], + TunnelFail* pFail) { + if (pFail) *pFail = TUNNEL_FAIL_INIT; if (!TunnelInit()) return INVALID_SOCKET; - RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey); - if (rv == RV_INVALID) return INVALID_SOCKET; + RvClientFail rvFail = RVC_FAIL_NONE; + RvSocket rv = RvClientConnect(meeting_host, port, target_pubkey, &rvFail); + if (rv == RV_INVALID) + { + if (pFail) + *pFail = rvFail == RVC_FAIL_DIAL ? TUNNEL_FAIL_DIAL + : rvFail == RVC_FAIL_REFUSED ? TUNNEL_FAIL_REFUSED + : TUNNEL_FAIL_PAIR; + return INVALID_SOCKET; + } unsigned char eph_pub[32], shared[32]; + if (pFail) *pFail = TUNNEL_FAIL_HANDSHAKE; if (!ChanClientHandshake(service_enc_pub, eph_pub, shared)) { RvClose(rv); return INVALID_SOCKET; } if (!RvWriteN(rv, eph_pub, 32)) { RvClose(rv); return INVALID_SOCKET; } - return StartTunnel(rv, shared); + if (pFail) *pFail = TUNNEL_FAIL_LOCAL; + btf_socket_t app = StartTunnel(rv, shared); + if (app != INVALID_SOCKET && pFail) *pFail = TUNNEL_FAIL_NONE; + return app; } btf_socket_t BtfServiceWrap(RvSocket rv, const unsigned char my_enc_sk[32]) diff --git a/src/btftunnel.h b/src/btftunnel.h index 24a7509..3cc87cd 100644 --- a/src/btftunnel.h +++ b/src/btftunnel.h @@ -36,12 +36,33 @@ namespace btf // Initialize (sockets + libsodium). Idempotent. bool TunnelInit(); +// Which stage of BtfClientTunnel failed. INVALID_SOCKET alone says only "no +// tunnel"; a caller that then guesses at causes in its log line sends whoever +// reads it to the wrong component (issue #17: "relay unreachable or pool not +// listening" blamed two healthy parts). The stage is what this side actually +// knows -- report that. +enum TunnelFail +{ + TUNNEL_FAIL_NONE = 0, + TUNNEL_FAIL_INIT, // local socket/crypto init failed + TUNNEL_FAIL_DIAL, // TCP connect to the meeting node failed + TUNNEL_FAIL_REFUSED, // relay reachable, but no service registered under the pubkey + TUNNEL_FAIL_PAIR, // relay connection died before pairing completed + TUNNEL_FAIL_HANDSHAKE, // end-to-end channel handshake failed + TUNNEL_FAIL_LOCAL, // local pump plumbing (socket pair / threads) failed +}; + +// Short description of a TunnelFail for log lines. +const char* TunnelFailText(TunnelFail f); + // Client: dial the service registered under `target_pubkey` at the meeting node // (host:port), box to its static x25519 `service_enc_pub`, and return a local -// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error. +// plaintext SOCKET tunneled end-to-end to the service. INVALID_SOCKET on error +// -- with `pFail`, if given, naming the stage that failed. btf_socket_t BtfClientTunnel(const char* meeting_host, unsigned short port, const unsigned char target_pubkey[32], - const unsigned char service_enc_pub[32]); + const unsigned char service_enc_pub[32], + TunnelFail* pFail = 0); // Service: wrap an already-paired rendezvous socket (RvServiceRegister followed // by RvServiceWaitPaired -- the pairing byte MUST already be consumed) with diff --git a/src/main.cpp b/src/main.cpp index 7b3e555..c0a8825 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3758,13 +3758,14 @@ static bool PoolParticipantMiner() // Step 2b: open encrypted tunnel to pool // ------------------------------------------------------------------ SetParticipantMiningStatus("opening tunnel"); + btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE; s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, - target_pubkey, service_enc_pub); + target_pubkey, service_enc_pub, &tunnelFail); if (s == INVALID_SOCKET) { - LogPrint("worker", "[worker] tunnel connect FAILED via %s" - " (relay unreachable or pool not listening)\n", - meetingHostPort.c_str()); - SetParticipantMiningStatus("tunnel connect failed"); + LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n", + meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail)); + SetParticipantMiningStatus(std::string("tunnel connect failed: ") + + btf::TunnelFailText(tunnelFail)); return false; } LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); -- cgit v1.2.3