From cbea2029297ca28044d2c0a8e5030503bfd9197a Mon Sep 17 00:00:00 2001 From: satoshi Date: Wed, 22 Apr 2026 21:34:10 +0300 Subject: [PATCH] Track cumulative work via accounted_diff_shares, surface round effort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Total work was summing pool.Shares (ckpool's accounted_shares — raw per-share count) and multiplying by 2^32, which is nonsense: each share's actual difficulty was ignored, so a pool running at any real hashrate would show a tiny number. Switch to pool.Accepted, which ckpool exposes as accounted_diff_shares (sum of each accepted share's difficulty in diff-1-normalized units). cumulative_shares * 2^32 is now actually total hashes. Bump the kv key from "cumulative_shares" to "cumulative_work" so any value saved under the old name is orphaned rather than mixed into the new (correctly-unit'd) counter on upgrade. Total work tile's sub-info now shows the round effort: cumulative_shares / network_difficulty * 100. Matches ckpool's own formula at stratifier.c:8201 for the percent-of-block display. Drop the duplicate effort text from the Expected block tile, which was an uptime-based approximation of the same thing. --- api/internal/state/aggregator.go | 23 ++++++++++++++++------- ui/src/lib/PoolOverview.svelte | 21 ++++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/api/internal/state/aggregator.go b/api/internal/state/aggregator.go index b719c8d..b4447b8 100644 --- a/api/internal/state/aggregator.go +++ b/api/internal/state/aggregator.go @@ -76,7 +76,13 @@ type Snapshot struct { const ( maxHistoryPoints = 1440 // 24h at 1 sample/min - kvCumulativeWork = "cumulative_shares" + + // kvCumulativeWork is versioned: "cumulative_shares" (v1) accidentally + // summed pool.shares (raw per-share count), which is meaningless for + // hashrate math. "cumulative_work" (v2) sums pool.accepted — the + // diff-1-normalized work, so cumulative_work * 2^32 is real hashes. + // The old key is orphaned in the kv table on upgrade; harmless. + kvCumulativeWork = "cumulative_work" ) // Aggregator refreshes a Snapshot on a ticker. @@ -285,13 +291,16 @@ func (a *Aggregator) refresh(ctx context.Context) { now := time.Now() // --- cumulative work tracking --- - // Integrate only positive deltas on pool.Shares. A decrease means - // ckpool restarted (or its state reset); reset the baseline without - // touching the accumulated total. The first observation after load - // only establishes the baseline — those shares were already - // accumulated before the previous shutdown. + // Integrate only positive deltas on pool.Accepted, which is ckpool's + // accounted_diff_shares — the sum of each accepted share's + // difficulty, in diff-1-normalized units. pool.Shares is the raw + // per-share count and is useless for hashrate math. A decrease + // means ckpool restarted (or its state reset); we reset the + // baseline without touching the accumulated total. The first + // observation after load only establishes the baseline — the + // current counter was already integrated before we shut down. if next.Pool != nil { - cur := float64(next.Pool.Shares) + cur := float64(next.Pool.Accepted) if a.hasPoolSharesBaseline && cur >= a.lastPoolShares { a.cumulativeShares += cur - a.lastPoolShares } diff --git a/ui/src/lib/PoolOverview.svelte b/ui/src/lib/PoolOverview.svelte index 7d55f25..82c9aa3 100644 --- a/ui/src/lib/PoolOverview.svelte +++ b/ui/src/lib/PoolOverview.svelte @@ -26,11 +26,14 @@ return expectedBlockSeconds(data.hashrate_hs_1m, data.network_hashrate_hs, diff); }); - const effort = $derived.by(() => { - if (!isFinite(expected) || expected <= 0) return 0; - const uptime = data.uptime_seconds; - if (!uptime || uptime <= 0) return 0; - return (uptime / expected) * 100; + // Round effort: share of the expected work-to-find-a-block that the + // pool has already submitted, at the current network difficulty. + // Matches ckpool's own accounted_diff_shares / network_diff * 100 + // (stratifier.c:8201). + const roundEffort = $derived.by(() => { + const d = data.chain?.difficulty ?? 0; + if (!d || d <= 0) return 0; + return (data.cumulative_shares / d) * 100; }); // Blocks remaining + ETA until the retarget. The predicted adjustment @@ -121,15 +124,15 @@
Total work
{formatWork(totalHashes)}
-
hashes submitted by pool
+
+ round effort {roundEffort.toFixed(1)}% of current difficulty +
Expected block
{formatDuration(expected)}
-
- effort {effort.toFixed(1)}% · uptime {formatUptime(data.uptime_seconds)} -
+
uptime {formatUptime(data.uptime_seconds)}