Patch 9: fix shares being rejected after a pool restart bug

This commit is contained in:
satoshi committed 2026-09-01 23:53:06 +03:00
1 parent fc69410792
commit 4e40e08fd4
2 files changed
+120 -1

No files matched your search

+3 -1
View File
@@ -32,7 +32,7 @@ CKPool is a high-performance stratum server, but it has no web interface and lim
### Patches Applied to CKPool ### Patches Applied to CKPool
Eight patches are applied to upstream commit `cfb0f83` (which itself includes the workbase_id fix, extended ESP32/NerdMiner timeouts, configurable `dropidle`, and vardiff improvements): Nine patches are applied to upstream commit `cfb0f83` (which itself includes the workbase_id fix, extended ESP32/NerdMiner timeouts, configurable `dropidle`, and vardiff improvements):
| Patch | Purpose | | Patch | Purpose |
|-------|---------| |-------|---------|
@@ -44,6 +44,7 @@ Eight patches are applied to upstream commit `cfb0f83` (which itself includes th
| `0006` | Expose raw reject count in pool stats | | `0006` | Expose raw reject count in pool stats |
| `0007` | Replace hardcoded "ckpool" branding in the coinbase scriptSig with "kamado" (same 6 bytes, consensus-safe) | | `0007` | Replace hardcoded "ckpool" branding in the coinbase scriptSig with "kamado" (same 6 bytes, consensus-safe) |
| `0008` | Log full PoW reproduction data (raw 80-byte header, coinbase, merkle branches) when a share sets a new best difficulty | | `0008` | Log full PoW reproduction data (raw 80-byte header, coinbase, merkle branches) when a share sets a new best difficulty |
| `0009` | Send `mining.set_difficulty` before the first `mining.notify`, resume a reconnecting miner's tuned difficulty, and grant its first job a difficulty grace window |
### Middleware API ### Middleware API
@@ -94,6 +95,7 @@ The Svelte 5 dashboard connects via WebSocket for real-time push updates (no pol
- **ckpool kill-on-failure** — When bitcoind becomes unreachable, kamado-api kills ckpool so miners can failover to another pool. ckpool restarts automatically when bitcoind recovers. - **ckpool kill-on-failure** — When bitcoind becomes unreachable, kamado-api kills ckpool so miners can failover to another pool. ckpool restarts automatically when bitcoind recovers.
- **Deferred RPC** — Dashboard RPCs wait until the ckpool notifier completes on tip change to prioritize system resources for block reconstruction and validation. - **Deferred RPC** — Dashboard RPCs wait until the ckpool notifier completes on tip change to prioritize system resources for block reconstruction and validation.
- **Log cursor persistence** — The tailer's file offset is stored in SQLite so block-solve detection resumes correctly after restart. - **Log cursor persistence** — The tailer's file offset is stored in SQLite so block-solve detection resumes correctly after restart.
- **Clean restarts for miners** — Upstream ckpool sends a reconnecting miner its first job *before* telling it the difficulty, and resets every client to the pool's `startdiff`, discarding what vardiff had tuned. Both produce a burst of "Above target" rejects on every pool restart. Patch `0009` sends `mining.set_difficulty` first, seeds the starting difficulty from the worker's retained hashrate, and accepts shares for the already-in-flight job down to the pool minimum until the next `mining.notify`.
- **Warm-up resilience** — The ckpool socket client handles transient EOFs during startup gracefully instead of crashing. - **Warm-up resilience** — The ckpool socket client handles transient EOFs during startup gracefully instead of crashing.
## API Reference ## API Reference
@@ -0,0 +1,117 @@
diff --git a/src/stratifier.c b/src/stratifier.c
index a856b3eb..fcb9eb94 100644
--- a/src/stratifier.c
+++ b/src/stratifier.c
@@ -5477,6 +5477,69 @@ static void update_solo_client(sdata_t *sdata, workbase_t *wb, const int64_t cli
stratum_add_send(sdata, json_msg, client_id, SM_UPDATE);
}
+/* Kamado (patches/0009): restore a reconnecting miner's difficulty and
+ * grant the work it already holds a difficulty grace window.
+ *
+ * Upstream gives every new connection ckp->startdiff, so a pool restart
+ * discards the difficulty vardiff had tuned each miner to and makes it
+ * re-converge from the pool default. ckpool does persist per-worker
+ * hashrate across restarts (read_userstats, decayed by the downtime), so
+ * the starting difficulty can be derived from it with the same formula
+ * and clamps add_submit's vardiff uses.
+ *
+ * The grace window covers work in flight. A miner may hold queued jobs,
+ * and much firmware only applies a new difficulty on the following job,
+ * so its first shares can be below the difficulty we just assigned.
+ * Pointing diff_change_job_id past the current workbase makes the
+ * existing check in parse_submit validate those against old_diff (the
+ * pool minimum) instead of rejecting them as "high diff". The next
+ * mining.notify closes the window, since by then the client has had the
+ * difficulty for a whole job.
+ *
+ * Needs to be entered with no workbase or instance lock held. */
+static void resume_client_diff(ckpool_t *ckp, sdata_t *sdata, stratum_instance_t *client)
+{
+ const worker_instance_t *worker = client->worker_instance;
+ int64_t optimal, mindiff, network_diff = 0;
+ double dsps = 0;
+
+ ck_rlock(&sdata->workbase_lock);
+ if (sdata->current_workbase) {
+ network_diff = sdata->current_workbase->network_diff;
+ /* Every job sent so far, including the one about to go out,
+ * predates this client acting on its difficulty. */
+ client->diff_change_job_id = sdata->workbase_id + 1;
+ }
+ ck_runlock(&sdata->workbase_lock);
+
+ /* Read unlocked, as the vardiff path in add_submit does: these are
+ * decaying averages used as a heuristic, not accounting. */
+ if (worker)
+ dsps = worker->dsps5 > 0 ? worker->dsps5 : worker->dsps1;
+ if (dsps > 0) {
+ optimal = lround(dsps * 3.33);
+ /* Client suggest diff overrides worker mindiff */
+ if (client->suggest_diff)
+ mindiff = client->suggest_diff;
+ else
+ mindiff = worker->mindiff;
+ optimal = MAX(optimal, ckp->mindiff);
+ optimal = MAX(optimal, mindiff);
+ if (ckp->maxdiff)
+ optimal = MIN(optimal, ckp->maxdiff);
+ if (network_diff > 0)
+ optimal = MIN(optimal, network_diff);
+ if (optimal > 0 && optimal != client->diff) {
+ LOGNOTICE("Client %s resuming at diff %"PRId64" from retained hashrate instead of startdiff %"PRId64,
+ client->identity, optimal, ckp->startdiff);
+ client->diff = optimal;
+ }
+ }
+ /* Floor for the grace window only. client->diff stays what we
+ * advertise; vardiff overwrites old_diff on its first adjustment. */
+ client->old_diff = ckp->mindiff > 0 ? ckp->mindiff : 1;
+}
+
/* Needs to be entered with client holding a ref count. */
static json_t *parse_authorise(stratum_instance_t *client, const json_t *params_val,
json_t **err_val)
@@ -5558,6 +5621,19 @@ out:
sdata_t *sdata = ckp->sdata;
workbase_t *wb;
+ /* Kamado (patches/0009): pick up where vardiff left off and
+ * arm the first-job grace window. Called before any lock is
+ * taken here — it acquires the workbase lock itself. */
+ resume_client_diff(ckp, sdata, client);
+
+ /* Kamado (patches/0009): mining.set_difficulty must precede
+ * the first mining.notify. Upstream sent the job first, so a
+ * miner started hashing it at whatever difficulty its firmware
+ * defaults to and its early shares came back under the
+ * difficulty we had assigned — a burst of "high diff" rejects
+ * from every miner on every pool restart. */
+ stratum_send_diff(sdata, client);
+
/* To avoid grabbing recursive lock */
ck_wlock(&sdata->workbase_lock);
wb = sdata->current_workbase;
@@ -5573,8 +5649,6 @@ out:
ck_wlock(&sdata->workbase_lock);
wb->readcount--;
ck_wunlock(&sdata->workbase_lock);
-
- stratum_send_diff(sdata, client);
}
return json_boolean(ret);
}
@@ -6567,9 +6641,12 @@ static void update_client(const stratum_instance_t *client, const int64_t client
{
sdata_t *sdata = client->sdata;
+ /* Kamado (patches/0009): difficulty before work, so a client never
+ * starts a job without knowing the difficulty it will be judged
+ * against. Upstream sent the update first. */
+ stratum_send_diff(sdata, client);
if (!client->ckp->btcsolo)
stratum_send_update(sdata, client_id, true);
- stratum_send_diff(sdata, client);
}
static json_params_t