diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:35 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-03 21:49:35 +0000 |
| commit | ea8fe94658bf20d2b669b619231238f12e6bd8fc (patch) | |
| tree | 02b8f4485d3e4a4117a3c16751af24a702037ce9 /src/main.cpp | |
| parent | 944f93f163cb560f095556834b6eb75bfde60c9c (diff) | |
| parent | ca03f8e9416837acfac825f02a6c592e07e0c4df (diff) | |
merge: !8 fix/tunnel-retry into deploy/all-2026-08-24
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 106 |
1 files changed, 95 insertions, 11 deletions
diff --git a/src/main.cpp b/src/main.cpp index 681673c..6b8e0de 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -3970,6 +3970,14 @@ static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorker | |||
| 3970 | RandomXDestroyMinerVM(vm); | 3970 | RandomXDestroyMinerVM(vm); |
| 3971 | } | 3971 | } |
| 3972 | 3972 | ||
| 3973 | // Run one participant session: resolve the pool, open the tunnel, subscribe, | ||
| 3974 | // hash, submit shares. Returns true only if the pool actually handed out work | ||
| 3975 | // -- at least one well-formed mining.notify arrived. Everything else is false, | ||
| 3976 | // including a tunnel that opened and was dropped before any job. | ||
| 3977 | // | ||
| 3978 | // The caller's reconnect backoff keys off this return value, so any path added | ||
| 3979 | // here that leaves the session without work MUST return false. "The session | ||
| 3980 | // ended tidily" is not the question being asked. | ||
| 3973 | static bool PoolParticipantMiner() | 3981 | static bool PoolParticipantMiner() |
| 3974 | { | 3982 | { |
| 3975 | // ------------------------------------------------------------------ | 3983 | // ------------------------------------------------------------------ |
| @@ -4047,6 +4055,33 @@ static bool PoolParticipantMiner() | |||
| 4047 | } | 4055 | } |
| 4048 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); | 4056 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); |
| 4049 | 4057 | ||
| 4058 | // The descriptor names ONE meeting node, but the pool parks a | ||
| 4059 | // registration at every relay it knows (one accept thread per relay, | ||
| 4060 | // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the | ||
| 4061 | // advertised entry forever turns one bad path into a miner that idles | ||
| 4062 | // indefinitely while a working path exists (issue #17); after a few | ||
| 4063 | // consecutive tunnel failures, rotate the seed relays into the attempts | ||
| 4064 | // too. Only ONE participant session runs (StartMinerThreads), so these | ||
| 4065 | // statics are single-threaded. | ||
| 4066 | static const int TUNNEL_FAILOVER_AFTER = 3; | ||
| 4067 | static int nTunnelFails = 0; // consecutive tunnel failures, this pool | ||
| 4068 | static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays | ||
| 4069 | static std::string strFailPool; // pool the counters belong to | ||
| 4070 | if (strFailPool != strParticipantPool) { | ||
| 4071 | strFailPool = strParticipantPool; | ||
| 4072 | nTunnelFails = 0; | ||
| 4073 | iFailoverRelay = 0; | ||
| 4074 | } | ||
| 4075 | if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) { | ||
| 4076 | std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()]; | ||
| 4077 | if (alt != meetingHostPort) { | ||
| 4078 | LogPrint("worker", "[worker] advertised meeting node %s failed %d" | ||
| 4079 | " times in a row -- trying seed relay %s instead\n", | ||
| 4080 | meetingHostPort.c_str(), nTunnelFails, alt.c_str()); | ||
| 4081 | meetingHostPort = alt; | ||
| 4082 | } | ||
| 4083 | } | ||
| 4084 | |||
| 4050 | size_t colon = meetingHostPort.rfind(':'); | 4085 | size_t colon = meetingHostPort.rfind(':'); |
| 4051 | if (colon == std::string::npos) { | 4086 | if (colon == std::string::npos) { |
| 4052 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); | 4087 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); |
| @@ -4066,15 +4101,18 @@ static bool PoolParticipantMiner() | |||
| 4066 | // Step 2b: open encrypted tunnel to pool | 4101 | // Step 2b: open encrypted tunnel to pool |
| 4067 | // ------------------------------------------------------------------ | 4102 | // ------------------------------------------------------------------ |
| 4068 | SetParticipantMiningStatus("opening tunnel"); | 4103 | SetParticipantMiningStatus("opening tunnel"); |
| 4104 | btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE; | ||
| 4069 | s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, | 4105 | s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, |
| 4070 | target_pubkey, service_enc_pub); | 4106 | target_pubkey, service_enc_pub, &tunnelFail); |
| 4071 | if (s == INVALID_SOCKET) { | 4107 | if (s == INVALID_SOCKET) { |
| 4072 | LogPrint("worker", "[worker] tunnel connect FAILED via %s" | 4108 | nTunnelFails++; |
| 4073 | " (relay unreachable or pool not listening)\n", | 4109 | LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n", |
| 4074 | meetingHostPort.c_str()); | 4110 | meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail)); |
| 4075 | SetParticipantMiningStatus("tunnel connect failed"); | 4111 | SetParticipantMiningStatus(std::string("tunnel connect failed: ") |
| 4112 | + btf::TunnelFailText(tunnelFail)); | ||
| 4076 | return false; | 4113 | return false; |
| 4077 | } | 4114 | } |
| 4115 | nTunnelFails = 0; | ||
| 4078 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); | 4116 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); |
| 4079 | SetParticipantMiningStatus("tunnel open"); | 4117 | SetParticipantMiningStatus("tunnel open"); |
| 4080 | } | 4118 | } |
| @@ -4253,6 +4291,7 @@ connected: | |||
| 4253 | int nBestHeightResolved = -1; // nBestHeight when we last resolved | 4291 | int nBestHeightResolved = -1; // nBestHeight when we last resolved |
| 4254 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool | 4292 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool |
| 4255 | bool haveJob = false; | 4293 | bool haveJob = false; |
| 4294 | bool gotAnyJob = false; // any job at all this session -- the return value | ||
| 4256 | bool gotSubscribeAck = false; | 4295 | bool gotSubscribeAck = false; |
| 4257 | bool gotAuthorizeAck = false; | 4296 | bool gotAuthorizeAck = false; |
| 4258 | int64 authWaitStart = 0; | 4297 | int64 authWaitStart = 0; |
| @@ -4369,8 +4408,9 @@ connected: | |||
| 4369 | jobId = newJobId; | 4408 | jobId = newJobId; |
| 4370 | memcpy(header, hdrBytes.data(), 80); | 4409 | memcpy(header, hdrBytes.data(), 80); |
| 4371 | memcpy(&target, tgtBytes.data(), 32); | 4410 | memcpy(&target, tgtBytes.data(), 32); |
| 4372 | haveJob = true; | 4411 | haveJob = true; |
| 4373 | nNonce = 1; | 4412 | gotAnyJob = true; |
| 4413 | nNonce = 1; | ||
| 4374 | { | 4414 | { |
| 4375 | std::lock_guard<std::mutex> lk(jobShared.mtx); | 4415 | std::lock_guard<std::mutex> lk(jobShared.mtx); |
| 4376 | memcpy(jobShared.header, header, 80); | 4416 | memcpy(jobShared.header, header, 80); |
| @@ -4562,7 +4602,11 @@ connected: | |||
| 4562 | (unsigned long long)hashesSinceSample, | 4602 | (unsigned long long)hashesSinceSample, |
| 4563 | (unsigned long long)sessionSharesSent, | 4603 | (unsigned long long)sessionSharesSent, |
| 4564 | (unsigned long long)sessionSharesAccepted); | 4604 | (unsigned long long)sessionSharesAccepted); |
| 4565 | return true; | 4605 | // Not "we got here", which is true of a tunnel the pool accepted and then |
| 4606 | // dropped without ever sending a job -- exactly the case the caller's | ||
| 4607 | // backoff exists for. Reaching the end of the session is not evidence the | ||
| 4608 | // session was worth anything; a job is. | ||
| 4609 | return gotAnyJob; | ||
| 4566 | } | 4610 | } |
| 4567 | 4611 | ||
| 4568 | // --------------------------------------------------------------------------- | 4612 | // --------------------------------------------------------------------------- |
| @@ -4759,11 +4803,51 @@ bool BitcoinMiner(int nThreadId) | |||
| 4759 | // Participant mode: connect to external pool instead of mining solo/operator | 4803 | // Participant mode: connect to external pool instead of mining solo/operator |
| 4760 | if (nMineMode == MINE_PARTICIPANT) | 4804 | if (nMineMode == MINE_PARTICIPANT) |
| 4761 | { | 4805 | { |
| 4806 | // An attempt that never got work backs off (10s doubling to 120s) | ||
| 4807 | // instead of hammering the same path every flat 10s, and a streak | ||
| 4808 | // lasting minutes is said at printf level: the identical once-per-10s | ||
| 4809 | // LogPrint line is how a worker idled all night unnoticed (issue #17). | ||
| 4810 | // | ||
| 4811 | // Only a session the pool actually sent a job to resets either. The | ||
| 4812 | // weaker test -- "the session returned" -- was gameable: a pool that | ||
| 4813 | // completes the tunnel and drops without ever sending work leaves by | ||
| 4814 | // the same tidy cleanup path a healthy one does, which would pin the | ||
| 4815 | // retry at 10s forever and defeat the backoff in exactly the scenario | ||
| 4816 | // it exists for. | ||
| 4817 | int nRetrySecs = 10; | ||
| 4818 | int64 nFailingSince = 0; | ||
| 4762 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4819 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4763 | PoolParticipantMiner(); | 4820 | bool fGotWork = PoolParticipantMiner(); |
| 4764 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4821 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4765 | LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); | 4822 | if (fGotWork) { |
| 4766 | for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); | 4823 | nRetrySecs = 10; |
| 4824 | nFailingSince = 0; | ||
| 4825 | } else { | ||
| 4826 | if (nFailingSince == 0) | ||
| 4827 | nFailingSince = GetTime(); | ||
| 4828 | int64 nMins = (GetTime() - nFailingSince) / 60; | ||
| 4829 | if (nMins >= 1) | ||
| 4830 | printf("[worker] connection to pool %s has been failing" | ||
| 4831 | " for %lld minutes\n", | ||
| 4832 | strParticipantPool.c_str(), (long long)nMins); | ||
| 4833 | } | ||
| 4834 | LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n", | ||
| 4835 | nRetrySecs); | ||
| 4836 | // Poll everything the enclosing while polls, not just | ||
| 4837 | // fShutdown. Raising the ceiling to 120s made a | ||
| 4838 | // shutdown-only predicate expensive: after the operator | ||
| 4839 | // clears Generate or switches to Relay, the session -- and | ||
| 4840 | // the vfThreadRunning[3] slot that gates restarting it -- | ||
| 4841 | // would stay held for up to two minutes. Measured 119s | ||
| 4842 | // before, 1s after. | ||
| 4843 | for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins | ||
| 4844 | && nMineMode == MINE_PARTICIPANT; i++) | ||
| 4845 | Sleep(1000); | ||
| 4846 | if (!fGotWork && nRetrySecs < 120) { | ||
| 4847 | nRetrySecs *= 2; | ||
| 4848 | if (nRetrySecs > 120) | ||
| 4849 | nRetrySecs = 120; | ||
| 4850 | } | ||
| 4767 | } | 4851 | } |
| 4768 | } | 4852 | } |
| 4769 | return true; | 4853 | return true; |