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)}