From 2a804c2d9736e91271b3209fbc41080c67c100da Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 06:49:28 +0000 Subject: worker: log the chain a pool job builds on, not just its id --- src/main.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 7b3e555..3de3db0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4040,8 +4040,29 @@ connected: jobShared.jobSeq++; jobShared.haveJob = true; } - LogPrint("worker", "[pool->worker] new job %s received\n", - jobId.c_str()); + // Log the chain position the pool is + // building on, so a stale or private + // fork is auditable from worker logs. + // The prevhash comes from the header we + // actually hash (== p[2] of the notify); + // a brief cs_main lookup is safe here -- + // this thread holds no other lock. + uint256 hashPrev; + memcpy(&hashPrev, header + 4, 32); + int nPrevHeight = -1; + CRITICAL_BLOCK(cs_main) + { + map::iterator mi = mapBlockIndex.find(hashPrev); + if (mi != mapBlockIndex.end()) + nPrevHeight = (*mi).second->nHeight; + } + if (nPrevHeight >= 0) + LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n", + jobId.c_str(), hashPrev.GetHex().c_str(), + nPrevHeight + 1); + else + LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n", + jobId.c_str(), hashPrev.GetHex().c_str()); SetParticipantMiningStatus("hashing job " + jobId); } } -- cgit v1.2.3 From 04f5b6827c9bc1d2b225318db0c3f0d1e27e6819 Mon Sep 17 00:00:00 2001 From: bitflash-maintainer Date: Wed, 2 Sep 2026 17:51:07 +0000 Subject: worker: only look up a job's chain position when it changes 2a804c2 resolves a job's prevhash against mapBlockIndex under cs_main so the worker log names the chain the pool is building on (#15). It did that on every mining.notify, unconditionally, before the LogPrint that consumes the answer -- and LogPrint's category gate and its 3-second dedup both run after the lookup has already happened. The dedup bounds the log line. It never bounded the work. That left an unbounded cs_main acquisition rate on the participant's socket-reading thread, driven by the remote pool. Block validation can hold cs_main for a long time, and this thread is the one that handles mining.set_difficulty and share-submission replies, so a pool sending rapid notifies could stall the responses a miner is paid for. Hashing was never at risk -- jobShared is published before the lookup -- but the reader is not free to block. Most notifies in a round are a new job on the same tip, so the answer is now cached and re-resolved only when it could come out differently: a prevhash we have not resolved, or one we could not resolve last time while the chain has since moved. A block's height is fixed once mapBlockIndex knows it, so a cached "known" cannot go stale; an unknown is re-checked whenever nBestHeight changes, which is what catches a pool we were behind once we have caught up. The worst case -- a different prevhash every notify -- degrades to exactly the old cost, never worse. Measured over a 10-tip round of 30 jobs each, 300 acquisitions become 10; over 1000 notifies on a single fork we do not have, 1000 become 1. The log line is unchanged and still prints in full for every job, cached or not: prevhash plus height-or-"unknown to our chain". Nothing #15 asked for is lost, and a fork we cannot resolve keeps saying so on every job. The lookup is also wrapped in LogAcceptsCategory("worker") so the work follows the gate deciding whether anyone reads it. That is a no-op today -- "worker" is default-on in LogCategoryDefaultOn() -- and is there so the coupling survives the category ever moving behind -debug. The caching, not the gate, is what removes the acquisitions. The cache is four locals in the session scope, not statics: participant mode runs exactly one such thread (StartMinerThreads forces nThreads to 1 for MINE_PARTICIPANT), and session scope additionally means a reconnect re-resolves rather than trusting an answer from before a possible change of pool. --- src/main.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 12 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 3de3db0..c2d059b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3912,6 +3912,14 @@ connected: std::string jobId; unsigned char header[80] = {}; uint256 target; + // Cached answer to "which block is this job built on", for the + // mining.notify handler below -- see the comment there. Session-local, so + // a reconnect re-resolves rather than trusting an answer obtained before + // a possible change of pool. + uint256 hashPrevResolved; // prevhash the cached answer describes + bool fPrevResolved = false; // whether that answer exists yet + int nPrevHeight = -1; // its height, or -1 == unknown to us + int nBestHeightResolved = -1; // nBestHeight when we last resolved double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool bool haveJob = false; bool gotSubscribeAck = false; @@ -4047,22 +4055,57 @@ connected: // actually hash (== p[2] of the notify); // a brief cs_main lookup is safe here -- // this thread holds no other lock. + // + // Resolving it is the expensive half, and + // this is the socket-reading thread: block + // validation can hold cs_main for a long + // time, and every moment spent waiting on + // it here is a moment set_difficulty and + // share responses are not being handled. + // Most notifies in a round are a new job + // on the SAME tip, so the answer is cached + // and re-resolved only when it could come + // out differently: a prevhash we have not + // resolved, or one we could NOT resolve + // last time while the chain has since + // moved (unknown -> known). A block's + // height is fixed once mapBlockIndex knows + // it, so a cached "known" never goes + // stale. nBestHeight is read unlocked on + // purpose -- it only decides whether to + // repeat a lookup, never what gets + // printed, so a stale read costs at most + // one deferred or one extra lookup. + // The line still prints in full for every + // job, cached or not. uint256 hashPrev; memcpy(&hashPrev, header + 4, 32); - int nPrevHeight = -1; - CRITICAL_BLOCK(cs_main) + if (LogAcceptsCategory("worker")) { - map::iterator mi = mapBlockIndex.find(hashPrev); - if (mi != mapBlockIndex.end()) - nPrevHeight = (*mi).second->nHeight; + int nBestHeightNow = nBestHeight; + if (!fPrevResolved + || hashPrev != hashPrevResolved + || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved)) + { + nPrevHeight = -1; + CRITICAL_BLOCK(cs_main) + { + map::iterator mi = mapBlockIndex.find(hashPrev); + if (mi != mapBlockIndex.end()) + nPrevHeight = (*mi).second->nHeight; + } + hashPrevResolved = hashPrev; + nBestHeightResolved = nBestHeightNow; + fPrevResolved = true; + } + if (nPrevHeight >= 0) + LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n", + jobId.c_str(), hashPrev.GetHex().c_str(), + nPrevHeight + 1); + else + LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n", + jobId.c_str(), hashPrev.GetHex().c_str()); } - if (nPrevHeight >= 0) - LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n", - jobId.c_str(), hashPrev.GetHex().c_str(), - nPrevHeight + 1); - else - LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n", - jobId.c_str(), hashPrev.GetHex().c_str()); SetParticipantMiningStatus("hashing job " + jobId); } } -- cgit v1.2.3