diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 07:15:45 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-02 07:15:45 +0000 |
| commit | f094dc2f95df213f8c89d1d6f25dc864062623ae (patch) | |
| tree | 06e9ac2db31590b2c9803e51f326105806d611d9 /src/rpc.cpp | |
| parent | e4f898c4a6b9900553c64b9804889b5fdae3e55f (diff) | |
pool: refuse to build a job whose coinbase would burn the subsidy
GetKeyFromPool() returns an empty vector when the wallet is encrypted
and locked, and when key generation itself fails. RebuildJob() fed
whatever came back straight into the coinbase, and CScript::operator<<
serialises an empty vector as a zero-length push, so the template's
coinbase script became `<empty> OP_CHECKSIG` -- satisfiable by nobody.
Every block the pool's miners found against such a job burned its whole
subsidy: the coins exist and can never move, while QueuePayouts records
the full obligation and the miners are then paid out of coin that block
never contributed. An operator who locks the wallet overnight while the
miners keep finding blocks pays twice for every one of them.
BitcoinMiner has exactly this guard ("no wallet key available for
coinbase; mining stopped"); this builder was missed. Mirror it: log the
reason and return false before any job state is touched. Every caller
already treats a false return as "no job this round" -- the submit
handler and the new-block path log a warning and leave gHaveJob false,
the no-job watchdog goes on retrying every few seconds, and the
periodic nTime refresh keeps broadcasting the old job, whose key was
drawn while the wallet could still produce one. Once the wallet can
hand out a key again the next retry issues a spendable job unprompted.
Diffstat (limited to 'src/rpc.cpp')
| -rw-r--r-- | src/rpc.cpp | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index a4b53ca..cc3fffa 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -223,6 +223,29 @@ static bool RebuildJob() | |||
| 223 | // From the pool, so the template's payout key is in wallet.dat before | 223 | // From the pool, so the template's payout key is in wallet.dat before |
| 224 | // any miner is handed work against it. | 224 | // any miner is handed work against it. |
| 225 | vector<unsigned char> vchPubKey = GetKeyFromPool(); | 225 | vector<unsigned char> vchPubKey = GetKeyFromPool(); |
| 226 | if (vchPubKey.empty()) { | ||
| 227 | // Empty means the wallet could not produce a key: encrypted and | ||
| 228 | // locked, or key generation itself failed. Serialised, an empty | ||
| 229 | // vector is a zero-length push, so the coinbase would become | ||
| 230 | // `<empty> OP_CHECKSIG` -- a script nobody can satisfy. Every | ||
| 231 | // block mined against such a job burns its whole subsidy, while | ||
| 232 | // QueuePayouts still records the full obligation and the miners | ||
| 233 | // are then paid out of coin that block never contributed. | ||
| 234 | // BitcoinMiner refuses to mine in this state; refuse to hand the | ||
| 235 | // pool's miners work in it too. printf and not LogPrint for the | ||
| 236 | // reason given above the deaf-node refusal, and throttled the | ||
| 237 | // same way -- the no-job watchdog retries every few seconds for | ||
| 238 | // as long as the condition lasts. | ||
| 239 | static int64 nLastSaidNoKey = 0; | ||
| 240 | if (GetTime() - nLastSaidNoKey > 60) { | ||
| 241 | nLastSaidNoKey = GetTime(); | ||
| 242 | printf("RebuildJob: no wallet key available for the coinbase " | ||
| 243 | "(wallet locked, or key generation failed -- the " | ||
| 244 | "GetKeyFromPool line above says which) -- not issuing " | ||
| 245 | "jobs\n"); | ||
| 246 | } | ||
| 247 | return false; | ||
| 248 | } | ||
| 226 | static std::atomic<uint32_t> sExtra{0}; | 249 | static std::atomic<uint32_t> sExtra{0}; |
| 227 | uint32_t extraNonce = ++sExtra; | 250 | uint32_t extraNonce = ++sExtra; |
| 228 | 251 | ||