diff options
Diffstat (limited to 'src/rpc.cpp')
| -rw-r--r-- | src/rpc.cpp | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/src/rpc.cpp b/src/rpc.cpp index 82f895b..f1a7153 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp | |||
| @@ -2511,6 +2511,16 @@ struct Miner { | |||
| 2511 | int64 lastShareTime = 0; // GetTime() of the previous accepted share | 2511 | int64 lastShareTime = 0; // GetTime() of the previous accepted share |
| 2512 | double hashRate = 0.0; // smoothed H/s estimate | 2512 | double hashRate = 0.0; // smoothed H/s estimate |
| 2513 | 2513 | ||
| 2514 | // Replay guard for mining.submit (#8): nonces already credited on this | ||
| 2515 | // connection for the job it is currently working. Keyed to the job id in | ||
| 2516 | // seenNoncesJob and cleared by SendJob whenever that id changes, so a | ||
| 2517 | // resubmission under a NEW job is never turned away, while a replayed | ||
| 2518 | // (job_id, nonce) pair under the same job is refused before it can be | ||
| 2519 | // credited twice into roundShareCount -- the figure the payout split | ||
| 2520 | // divides by. | ||
| 2521 | std::set<uint32_t> seenNonces; | ||
| 2522 | std::string seenNoncesJob; | ||
| 2523 | |||
| 2514 | explicit Miner(btf_socket_t f) | 2524 | explicit Miner(btf_socket_t f) |
| 2515 | : fd(f), connectTime(GetTime()), | 2525 | : fd(f), connectTime(GetTime()), |
| 2516 | extranonce2(gNextExtranonce2.fetch_add(1)) {} | 2526 | extranonce2(gNextExtranonce2.fetch_add(1)) {} |
| @@ -2693,6 +2703,17 @@ static json MakeNotifyParams(const StratumJob& job, const CBlock& b, | |||
| 2693 | static void SendJob(Miner* m, bool clean) | 2703 | static void SendJob(Miner* m, bool clean) |
| 2694 | { | 2704 | { |
| 2695 | if (!gHaveJob) return; | 2705 | if (!gHaveJob) return; |
| 2706 | // Every path that hands a miner a new job funnels through here, so this | ||
| 2707 | // is the one place the replay guard needs to follow the job id: nonces | ||
| 2708 | // credited under the previous job mean nothing under the new one. Keying | ||
| 2709 | // the clear to the id (rather than clearing unconditionally) keeps a | ||
| 2710 | // same-job re-notify -- the vardiff retarget and stall watchdog both | ||
| 2711 | // re-send the CURRENT job -- from reopening the duplicate window it | ||
| 2712 | // exists to close. | ||
| 2713 | if (m->seenNoncesJob != gCurrentJob.jobId) { | ||
| 2714 | m->seenNonces.clear(); | ||
| 2715 | m->seenNoncesJob = gCurrentJob.jobId; | ||
| 2716 | } | ||
| 2696 | CBlock mb = BuildMinerBlock(gCurrentJob, m->extranonce2); | 2717 | CBlock mb = BuildMinerBlock(gCurrentJob, m->extranonce2); |
| 2697 | // Use this miner's own shareTarget (set by vardiff) in the notify params. | 2718 | // Use this miner's own shareTarget (set by vardiff) in the notify params. |
| 2698 | // The participant miner reads p[3] as its share target, and HandleLine | 2719 | // The participant miner reads p[3] as its share target, and HandleLine |
| @@ -2951,6 +2972,22 @@ static bool HandleLine(Miner* m, const std::string& rawLine, | |||
| 2951 | } | 2972 | } |
| 2952 | unsigned int nNonce; memcpy(&nNonce, nb.data(), 4); | 2973 | unsigned int nNonce; memcpy(&nNonce, nb.data(), 4); |
| 2953 | 2974 | ||
| 2975 | // Reject a replayed (job, nonce) before it costs a rebuild and a | ||
| 2976 | // RandomX hash: nothing about the work below distinguishes a second | ||
| 2977 | // submission of a valid share from the first, so without this check | ||
| 2978 | // it passes every test and is credited again -- and roundShareCount | ||
| 2979 | // is what the payout split divides by, so each replay multiplies the | ||
| 2980 | // address's cut of the round. The set is per-connection and cleared | ||
| 2981 | // by SendJob on every job change, so only a same-job repeat is | ||
| 2982 | // refused. | ||
| 2983 | if (m->seenNonces.count(nNonce)) { | ||
| 2984 | LogPrint("worker", "[worker->pool] duplicate share from %s" | ||
| 2985 | " nonce=%u job=%s -- already credited, not counting again\n", | ||
| 2986 | m->address.c_str(), nNonce, submitJobId.c_str()); | ||
| 2987 | reply(false, "Duplicate share"); | ||
| 2988 | return true; | ||
| 2989 | } | ||
| 2990 | |||
| 2954 | // Rebuild this miner's exact block (unique merkle via extranonce2) | 2991 | // Rebuild this miner's exact block (unique merkle via extranonce2) |
| 2955 | CBlock b = BuildMinerBlock(gCurrentJob, m->extranonce2); | 2992 | CBlock b = BuildMinerBlock(gCurrentJob, m->extranonce2); |
| 2956 | b.nNonce = nNonce; | 2993 | b.nNonce = nNonce; |
| @@ -2978,6 +3015,7 @@ static bool HandleLine(Miner* m, const std::string& rawLine, | |||
| 2978 | m->roundShares++; | 3015 | m->roundShares++; |
| 2979 | m->totalShares++; | 3016 | m->totalShares++; |
| 2980 | m->vardiffShares++; | 3017 | m->vardiffShares++; |
| 3018 | m->seenNonces.insert(nNonce); // replay guard: credit each nonce once per job | ||
| 2981 | int64 shareTime = GetTime(); | 3019 | int64 shareTime = GetTime(); |
| 2982 | m->lastSeen = shareTime; | 3020 | m->lastSeen = shareTime; |
| 2983 | roundShareCount[m->address]++; | 3021 | roundShareCount[m->address]++; |