upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 17:51:07 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-02 17:51:07 +0000
commit04f5b6827c9bc1d2b225318db0c3f0d1e27e6819 (patch)
treef80ca00fa9f9f7a88e17dc039a3436c4145c34ab
parent2a804c2d9736e91271b3209fbc41080c67c100da (diff)
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.
-rw-r--r--src/main.cpp67
1 files changed, 55 insertions, 12 deletions
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:
3912 std::string jobId; 3912 std::string jobId;
3913 unsigned char header[80] = {}; 3913 unsigned char header[80] = {};
3914 uint256 target; 3914 uint256 target;
3915 // Cached answer to "which block is this job built on", for the
3916 // mining.notify handler below -- see the comment there. Session-local, so
3917 // a reconnect re-resolves rather than trusting an answer obtained before
3918 // a possible change of pool.
3919 uint256 hashPrevResolved; // prevhash the cached answer describes
3920 bool fPrevResolved = false; // whether that answer exists yet
3921 int nPrevHeight = -1; // its height, or -1 == unknown to us
3922 int nBestHeightResolved = -1; // nBestHeight when we last resolved
3915 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool 3923 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool
3916 bool haveJob = false; 3924 bool haveJob = false;
3917 bool gotSubscribeAck = false; 3925 bool gotSubscribeAck = false;
@@ -4047,22 +4055,57 @@ connected:
4047 // actually hash (== p[2] of the notify); 4055 // actually hash (== p[2] of the notify);
4048 // a brief cs_main lookup is safe here -- 4056 // a brief cs_main lookup is safe here --
4049 // this thread holds no other lock. 4057 // this thread holds no other lock.
4058 //
4059 // Resolving it is the expensive half, and
4060 // this is the socket-reading thread: block
4061 // validation can hold cs_main for a long
4062 // time, and every moment spent waiting on
4063 // it here is a moment set_difficulty and
4064 // share responses are not being handled.
4065 // Most notifies in a round are a new job
4066 // on the SAME tip, so the answer is cached
4067 // and re-resolved only when it could come
4068 // out differently: a prevhash we have not
4069 // resolved, or one we could NOT resolve
4070 // last time while the chain has since
4071 // moved (unknown -> known). A block's
4072 // height is fixed once mapBlockIndex knows
4073 // it, so a cached "known" never goes
4074 // stale. nBestHeight is read unlocked on
4075 // purpose -- it only decides whether to
4076 // repeat a lookup, never what gets
4077 // printed, so a stale read costs at most
4078 // one deferred or one extra lookup.
4079 // The line still prints in full for every
4080 // job, cached or not.
4050 uint256 hashPrev; 4081 uint256 hashPrev;
4051 memcpy(&hashPrev, header + 4, 32); 4082 memcpy(&hashPrev, header + 4, 32);
4052 int nPrevHeight = -1; 4083 if (LogAcceptsCategory("worker"))
4053 CRITICAL_BLOCK(cs_main)
4054 { 4084 {
4055 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev); 4085 int nBestHeightNow = nBestHeight;
4056 if (mi != mapBlockIndex.end()) 4086 if (!fPrevResolved
4057 nPrevHeight = (*mi).second->nHeight; 4087 || hashPrev != hashPrevResolved
4088 || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved))
4089 {
4090 nPrevHeight = -1;
4091 CRITICAL_BLOCK(cs_main)
4092 {
4093 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev);
4094 if (mi != mapBlockIndex.end())
4095 nPrevHeight = (*mi).second->nHeight;
4096 }
4097 hashPrevResolved = hashPrev;
4098 nBestHeightResolved = nBestHeightNow;
4099 fPrevResolved = true;
4100 }
4101 if (nPrevHeight >= 0)
4102 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n",
4103 jobId.c_str(), hashPrev.GetHex().c_str(),
4104 nPrevHeight + 1);
4105 else
4106 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n",
4107 jobId.c_str(), hashPrev.GetHex().c_str());
4058 } 4108 }
4059 if (nPrevHeight >= 0)
4060 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n",
4061 jobId.c_str(), hashPrev.GetHex().c_str(),
4062 nPrevHeight + 1);
4063 else
4064 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n",
4065 jobId.c_str(), hashPrev.GetHex().c_str());
4066 SetParticipantMiningStatus("hashing job " + jobId); 4109 SetParticipantMiningStatus("hashing job " + jobId);
4067 } 4110 }
4068 } 4111 }