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/main.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/main.cpp') 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 From 014798e50b2de37ed5243c7e622fc290dfda8606 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 09:03:33 +0000 Subject: worker: fail over across seed relays when the advertised meeting node keeps failing --- src/main.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index c0a8825..0414714 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3739,6 +3739,33 @@ static bool PoolParticipantMiner() } LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); + // The descriptor names ONE meeting node, but the pool parks a + // registration at every relay it knows (one accept thread per relay, + // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the + // advertised entry forever turns one bad path into a miner that idles + // indefinitely while a working path exists (issue #17); after a few + // consecutive tunnel failures, rotate the seed relays into the attempts + // too. Only ONE participant session runs (StartMinerThreads), so these + // statics are single-threaded. + static const int TUNNEL_FAILOVER_AFTER = 3; + static int nTunnelFails = 0; // consecutive tunnel failures, this pool + static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays + static std::string strFailPool; // pool the counters belong to + if (strFailPool != strParticipantPool) { + strFailPool = strParticipantPool; + nTunnelFails = 0; + iFailoverRelay = 0; + } + if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) { + std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()]; + if (alt != meetingHostPort) { + LogPrint("worker", "[worker] advertised meeting node %s failed %d" + " times in a row -- trying seed relay %s instead\n", + meetingHostPort.c_str(), nTunnelFails, alt.c_str()); + meetingHostPort = alt; + } + } + size_t colon = meetingHostPort.rfind(':'); if (colon == std::string::npos) { LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); @@ -3762,12 +3789,14 @@ static bool PoolParticipantMiner() s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, target_pubkey, service_enc_pub, &tunnelFail); if (s == INVALID_SOCKET) { + nTunnelFails++; 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; } + nTunnelFails = 0; LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); SetParticipantMiningStatus("tunnel open"); } -- cgit v1.2.3 From e7b6bbffb1786eef842f1e446118a4daa9edf58f Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 09:04:57 +0000 Subject: worker: back off failed reconnects and say aloud how long the pool has been failing --- src/main.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 0414714..69b2c32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4394,11 +4394,36 @@ bool BitcoinMiner(int nThreadId) // Participant mode: connect to external pool instead of mining solo/operator if (nMineMode == MINE_PARTICIPANT) { + // An attempt that never got a session backs off (10s doubling to + // 120s) instead of hammering the same path every flat 10s, and a + // streak lasting minutes is said at printf level: the identical + // once-per-10s LogPrint line is how a worker idled all night + // unnoticed (issue #17). A completed session resets both. + int nRetrySecs = 10; + int64 nFailingSince = 0; while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { - PoolParticipantMiner(); + bool fHadSession = PoolParticipantMiner(); if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { - LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); - for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); + if (fHadSession) { + nRetrySecs = 10; + nFailingSince = 0; + } else { + if (nFailingSince == 0) + nFailingSince = GetTime(); + int64 nMins = (GetTime() - nFailingSince) / 60; + if (nMins >= 1) + printf("[worker] connection to pool %s has been failing" + " for %lld minutes\n", + strParticipantPool.c_str(), (long long)nMins); + } + LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n", + nRetrySecs); + for (int i = 0; i < nRetrySecs && !fShutdown; i++) Sleep(1000); + if (!fHadSession && nRetrySecs < 120) { + nRetrySecs *= 2; + if (nRetrySecs > 120) + nRetrySecs = 120; + } } } return true; -- cgit v1.2.3 From 738d5b411bab4e13e176a11f8ae6e6f70337381e Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 17:10:41 +0000 Subject: worker: let a stop request interrupt the reconnect backoff The retry sleep polled only fShutdown, while the loop around it polls fShutdown, fGenerateBitcoins and nMineMode. That was survivable at a flat 10s. Raising the ceiling to 120s made it expensive: after an operator cleared Generate or switched to Relay mode, the participant session -- and the vfThreadRunning[3] slot that gates restarting it -- stayed held for up to two minutes. Measured with the loop shape replicated and the stop condition flipped 1s into a 120s sleep: 119.01s to notice before, 1.00s after, via either fGenerateBitcoins or nMineMode. The fShutdown path is unchanged at 1.00s. It also narrows the window on a pre-existing unsynchronised read of vfThreadRunning[3] at gui.cpp:901, where a stop-then-start inside it leaves mining silently not restarted. --- src/main.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 69b2c32..b0cdab2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4418,7 +4418,16 @@ bool BitcoinMiner(int nThreadId) } LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n", nRetrySecs); - for (int i = 0; i < nRetrySecs && !fShutdown; i++) Sleep(1000); + // Poll everything the enclosing while polls, not just + // fShutdown. Raising the ceiling to 120s made a + // shutdown-only predicate expensive: after the operator + // clears Generate or switches to Relay, the session -- and + // the vfThreadRunning[3] slot that gates restarting it -- + // would stay held for up to two minutes. Measured 119s + // before, 1s after. + for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins + && nMineMode == MINE_PARTICIPANT; i++) + Sleep(1000); if (!fHadSession && nRetrySecs < 120) { nRetrySecs *= 2; if (nRetrySecs > 120) -- cgit v1.2.3 From ca03f8e9416837acfac825f02a6c592e07e0c4df Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 17:13:06 +0000 Subject: worker: count a session as productive only if the pool sent a job PoolParticipantMiner returned true from its normal cleanup path whether or not the pool had ever sent work, so a pool that completed the tunnel and then dropped was indistinguishable from a healthy one. The caller reset nRetrySecs to 10 on every such "session", pinning the reconnect at a flat 10s and defeating the backoff in precisely the case it was added for. It cleared nFailingSince as well, so the "failing for N minutes" line never printed either -- the worker went back to idling silently. Return true only once at least one well-formed mining.notify has arrived. The existing early returns are unaffected: they already mean "no session", which stays the right answer for any path that leaves the worker without work, a failed hash-thread spawn included. --- src/main.cpp | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index b0cdab2..b0bdb8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3662,6 +3662,14 @@ static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorker RandomXDestroyMinerVM(vm); } +// Run one participant session: resolve the pool, open the tunnel, subscribe, +// hash, submit shares. Returns true only if the pool actually handed out work +// -- at least one well-formed mining.notify arrived. Everything else is false, +// including a tunnel that opened and was dropped before any job. +// +// The caller's reconnect backoff keys off this return value, so any path added +// here that leaves the session without work MUST return false. "The session +// ended tidily" is not the question being asked. static bool PoolParticipantMiner() { // ------------------------------------------------------------------ @@ -3944,6 +3952,7 @@ connected: uint256 target; double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool bool haveJob = false; + bool gotAnyJob = false; // any job at all this session -- the return value bool gotSubscribeAck = false; bool gotAuthorizeAck = false; int64 authWaitStart = 0; @@ -4060,8 +4069,9 @@ connected: jobId = newJobId; memcpy(header, hdrBytes.data(), 80); memcpy(&target, tgtBytes.data(), 32); - haveJob = true; - nNonce = 1; + haveJob = true; + gotAnyJob = true; + nNonce = 1; { std::lock_guard lk(jobShared.mtx); memcpy(jobShared.header, header, 80); @@ -4197,7 +4207,11 @@ connected: (unsigned long long)hashesSinceSample, (unsigned long long)sessionSharesSent, (unsigned long long)sessionSharesAccepted); - return true; + // Not "we got here", which is true of a tunnel the pool accepted and then + // dropped without ever sending a job -- exactly the case the caller's + // backoff exists for. Reaching the end of the session is not evidence the + // session was worth anything; a job is. + return gotAnyJob; } // --------------------------------------------------------------------------- @@ -4394,17 +4408,23 @@ bool BitcoinMiner(int nThreadId) // Participant mode: connect to external pool instead of mining solo/operator if (nMineMode == MINE_PARTICIPANT) { - // An attempt that never got a session backs off (10s doubling to - // 120s) instead of hammering the same path every flat 10s, and a - // streak lasting minutes is said at printf level: the identical - // once-per-10s LogPrint line is how a worker idled all night - // unnoticed (issue #17). A completed session resets both. + // An attempt that never got work backs off (10s doubling to 120s) + // instead of hammering the same path every flat 10s, and a streak + // lasting minutes is said at printf level: the identical once-per-10s + // LogPrint line is how a worker idled all night unnoticed (issue #17). + // + // Only a session the pool actually sent a job to resets either. The + // weaker test -- "the session returned" -- was gameable: a pool that + // completes the tunnel and drops without ever sending work leaves by + // the same tidy cleanup path a healthy one does, which would pin the + // retry at 10s forever and defeat the backoff in exactly the scenario + // it exists for. int nRetrySecs = 10; int64 nFailingSince = 0; while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { - bool fHadSession = PoolParticipantMiner(); + bool fGotWork = PoolParticipantMiner(); if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { - if (fHadSession) { + if (fGotWork) { nRetrySecs = 10; nFailingSince = 0; } else { @@ -4428,7 +4448,7 @@ bool BitcoinMiner(int nThreadId) for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins && nMineMode == MINE_PARTICIPANT; i++) Sleep(1000); - if (!fHadSession && nRetrySecs < 120) { + if (!fGotWork && nRetrySecs < 120) { nRetrySecs *= 2; if (nRetrySecs > 120) nRetrySecs = 120; -- cgit v1.2.3