From 541223daa6e6510daf3bdfdcb4dd4f6b0e38d877 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Sat, 8 Aug 2026 02:29:26 +0000 Subject: pool: sync-gate job creation, retry failed payouts; widen nostr relay set - RebuildJob: no jobs with zero peers or a tip older than 30min; mining unsynced produced orphaned blocks and payouts that can never be paid (observed in production: 8 orphan 'found blocks') - payout exec: on SendMoney failure requeue with 60s backoff, up to 30 attempts, instead of silently forfeiting the payment - nostr: add oxtr.dev, nos.social, fmt.wiz.biz, current.fyi, nostr.bg (12 relays total) for announcement redundancy (cherry picked from commit f1986cd7e4a6f69469a2256eb96ff03f3f146792) --- src/rpc.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 4 deletions(-) (limited to 'src/rpc.cpp') diff --git a/src/rpc.cpp b/src/rpc.cpp index 41c0328..98b6208 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -143,6 +143,20 @@ static bool RebuildJob() return false; } + // Don't issue jobs until the chain is synced: mining against a stale + // tip produces blocks the network will orphan, and payout entries + // that can never be paid (their coinbases never exist on the main + // chain). Require at least one peer and a fresh tip. + if (vNodes.empty()) { + LogPrint("pool", "RebuildJob: no peers yet -- not issuing jobs\n"); + return false; + } + if (GetAdjustedTime() - (int64)pindexBest->nTime > 30*60) { + LogPrint("pool", "RebuildJob: tip is %llds old -- still syncing, not issuing jobs\n", + (long long)(GetAdjustedTime() - (int64)pindexBest->nTime)); + return false; + } + CBlockIndex* pindexPrev = pindexBest; unsigned int nBits = GetNextWorkRequired(pindexPrev); @@ -218,6 +232,8 @@ static bool RebuildJob() struct PendingPayout { int matureAtHeight; std::map amounts; // address -> satoshis + int retries = 0; // exec-thread only: SendMoney attempts so far + int64 retryAfter = 0; // exec-thread only: unix time, 0 = ready now }; static std::vector gPendingPayouts; @@ -403,6 +419,11 @@ static void PayoutThreadFn(void*) } for (auto& pp : batch) { + if (pp.retryAfter > GetTime()) { + std::lock_guard lk(gPayoutExecMutex); + gPayoutExecQueue.push_back(pp); + continue; + } LogPrint("payout", "[payout] executing payments for block matured at %d" " (%zu miners)\n", pp.matureAtHeight, pp.amounts.size()); for (auto& kv : pp.amounts) { @@ -418,13 +439,34 @@ static void PayoutThreadFn(void*) CWalletTx wtx; wtx.mapValue["comment"] = strprintf("Pool payout height %d", pp.matureAtHeight); - if (SendMoney(sc, kv.second, wtx)) + if (SendMoney(sc, kv.second, wtx)) { LogPrint("payout", "[payout] paid %s to %s\n", FormatMoney(kv.second).c_str(), kv.first.c_str()); - else + } else if (pp.retries < 30) { + // Transient failures (immature coinbase, reorg, locked + // wallet) must not silently burn the payment -- requeue + // with backoff. Permanent cases (orphaned coinbase) give + // up loudly after 30 tries (~30 min). + PendingPayout rp; + rp.matureAtHeight = pp.matureAtHeight; + rp.amounts[kv.first] = kv.second; + rp.retries = pp.retries + 1; + rp.retryAfter = GetTime() + 60; + { + std::lock_guard lk(gPayoutExecMutex); + gPayoutExecQueue.push_back(std::move(rp)); + } LogPrint("payout", "[payout] FAILED to pay %s to %s" - " (insufficient funds or wallet locked?)\n", - FormatMoney(kv.second).c_str(), kv.first.c_str()); + " -- retry %d/30 in 60s\n", + FormatMoney(kv.second).c_str(), kv.first.c_str(), + pp.retries + 1); + } else { + LogPrint("payout", "[payout] GIVING UP on %s to %s after %d" + " retries (orphaned coinbase or persistent wallet" + " error?) -- payment forfeited\n", + FormatMoney(kv.second).c_str(), kv.first.c_str(), + pp.retries); + } } } @@ -543,6 +585,45 @@ static void PushReady(btf_socket_t s) gReadyQueue.push_back(s); } +// Loopback stratum listener: lets a miner running on the SAME machine reach +// this pool over plain TCP (127.0.0.1:8334), skipping the .btf rendezvous +// entirely. The rendezvous relay only allows one service registration per +// pubkey, so the pool service and the node's own peer-accept fight over the +// node identity at the relay -- loopback avoids that fight for local miners. +static void LocalAcceptFn(void*) +{ + SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (ls == INVALID_SOCKET) + return; + int yes = 1; + setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = htons(8334); + if (bind(ls, (struct sockaddr*)&addr, sizeof(addr)) != 0 || listen(ls, 16) != 0) + { + LogPrint("pool", "[pool] loopback stratum bind/listen failed on 127.0.0.1:8334\n"); + closesocket(ls); + return; + } + LogPrint("pool", "[pool] loopback stratum listening on 127.0.0.1:8334\n"); + while (!fShutdown && gPoolRunning) + { + SOCKET c = accept(ls, NULL, NULL); + if (c == INVALID_SOCKET) + { + if (fShutdown || !gPoolRunning) + break; + continue; + } + LogPrint("pool", "[pool] miner arrived via loopback -- pushing to event loop\n"); + PushReady((btf_socket_t)c); + } + closesocket(ls); +} + // --------------------------------------------------------------------------- // I/O helpers -- event loop thread only // --------------------------------------------------------------------------- @@ -1307,6 +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 + // pool (not the node's peer-accept) holds the registration. + BtfSetActiveRelay(relay); // Blocks until a miner connects to us at this relay btf::RvSocket rv = btf::RvServiceRegister(host.c_str(), (unsigned short)port, pk); @@ -1408,6 +1494,7 @@ void ThreadRPCServer(void*) LoadPendingPayouts(); _beginthread(AcceptLoopFn, 0, NULL); + _beginthread(LocalAcceptFn, 0, NULL); _beginthread(ThreadBtfPoolAnnouncer, 0, NULL); _beginthread(PayoutThreadFn, 0, NULL); -- cgit v1.2.3