diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 17:51:07 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 17:51:07 +0000 |
| commit | 04f5b6827c9bc1d2b225318db0c3f0d1e27e6819 (patch) | |
| tree | f80ca00fa9f9f7a88e17dc039a3436c4145c34ab | |
| parent | 2a804c2d9736e91271b3209fbc41080c67c100da (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.cpp | 67 |
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 | } |