Commit Graph
10 Commits
Author SHA1 Message Date
satoshi ea4c514d78 Best (session): use only client.best_diff so it actually resets
Math.max(client.best_diff, worker.best_diff) defeated the whole
point of the column. ckpool keeps two separate counters:

  stratum_instance.best_diff  — per TCP session, in memory only,
                                freed on disconnect, gone on
                                ckpool restart.
  worker_instance.best_diff   — per worker name, persisted to the
                                logdir, restored on ckpool restart,
                                survives client disconnects.

Falling back to worker.best_diff when the client value was lower
meant the displayed "session" diff carried over the very events
(miner disconnect, pool restart) that should reset it.

Switch every "Best (session)" computation to read only the live
stratum_instance value. Offline workers — those with a worker
record but no current client — show 0, which is correct: there is
no current session to have a best in.
2026-04-26 18:13:18 +03:00
satoshi 91902f38a0 Unify best-share semantics across leaderboard / miners / user page
BestShares read worker.bestdiff for its "Best (round)" column,
while MinersTable used client.bestdiff for online workers (and
worker.bestdiff only as a fallback for offline ones). ckpool
resets *both* fields in reset_bestshares() when the pool finds a
block, but reconnect / timing can leave them briefly divergent,
so the same worker could show different values in the two tables.

Switch every "session best" display to the same computation:

    max(client.bestdiff, worker.bestdiff)

…picking whichever is currently higher. Relabel the column from
"Best (round)" to "Best (session)" in MinersTable, BestShares, and
UserDetailPage so the meaning matches the value. UserDetailPage's
per-user summary tile used to show the user's round-best as a
sub-line; drop it, since it contradicts the "session" framing at
the row level.

Net effect: scroll between the leaderboard and the miners table
and the same worker's best-share number stays put.
2026-04-24 01:34:04 +03:00
satoshi 49c951f94f User page: hash-routed view, full block hash, aligned rewards
The per-user view was an overlay modal, but users asked for an
actual page — focused, scrollable, addressable. Replace the modal
with a hash-routed page: selectUser(addr) sets window.location.hash
to #/user/<addr> and a hashchange listener syncs the selection
state back, so browser back/forward and direct-link refresh all
work without a routing library. App renders the dashboard or the
UserDetailPage based on selection.user.

Make the clickable BTC addresses in the Miners table obviously
interactive: accent colour, always-on dashed underline, brighter on
hover/focus. The previous styling rendered them as dim static text
with a hover underline, so it wasn't obvious they were links.

BlocksTable: show the full 64-char block hash as a link to
mempool.space (auto-picks testnet4/signet based on snap.chain).
word-break: break-all keeps the hash from blowing out the column
width. Reward column is split into a tabular-num number span and a
dim 0.75em "BTC" unit span, matching the Block reward tile in the
overview, so numbers line up cleanly across rows.
2026-04-24 01:02:11 +03:00
satoshi c104c1eefa Dashboard polish: per-user modal, block-found animation, readable chart
Block-reward tile was cached for 60s, so users saw it sit still even
as bitcoind's CreateNewBlock fired every few seconds with updated
fee totals. Drop the template cache TTL to 15s — bitcoind caches
the template internally, so the extra RPC cost is trivial.

Hashrate chart was rendering 1440 raw per-minute points across a
~780px plot, which collapsed into a noisy smear. Bucket-average to
~96 points so the chart actually communicates a trend. Raw samples
under the target pass through unchanged (early process lifetime).

Layout: Blocks found is now full-width, with the Best shares
leaderboard stacked below it. The previous side-by-side was
squeezing both tables on typical displays.

New user-detail modal. Clicking a BTC address in the Miners table
opens a focused view: per-user aggregate hashrate (1m/5m/1h/24h),
best round / best ever, and a worker-level breakdown with the same
columns as the main table. Driven by a small selection store so any
component in the tree can open it. Closes on backdrop click or Esc.

New block-found animation. When the network tip advances, a brief
full-screen overlay plays: a radial Hinokami Kagura ember burst
above the header, a faint sun-ray sweep, and ~22 falling sakura
petals with randomised drift / rotation / delay so no two blocks
look identical. The existing block-height tile flash still fires
alongside; the overlay is pointer-events: none so nothing in the
UI becomes unreachable during the ~3.6s animation.
2026-04-24 00:46:16 +03:00
satoshi cbea202929 Track cumulative work via accounted_diff_shares, surface round effort
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.
2026-04-22 21:34:10 +03:00
satoshi 64ca14435c Polish dashboard: real retarget prediction, symmetric 5x2, Kamado theme
The diff-adjustment tile was showing raw epoch progress (blocks-so-far
/ 2016 * 100%), which is never what anyone wants to see there. Compute
the real predicted change server-side: fetch the timestamp of the
first block in the current retarget epoch once per epoch, then each
refresh derive (expected_interval / actual_interval - 1) * 100,
clamped to Bitcoin's consensus bounds of [-75%, +300%]. The tile now
renders the signed percent, green when positive, red when negative.

Tile layout is forced to an explicit 5x2 grid so the number of
columns doesn't depend on viewport width. Reordering puts the
difficulty tile at (row 1, col 5) with the next-adjustment tile
directly below it at (row 2, col 5); block height, block reward,
total work, and expected block fill the rest of the second row.
Mobile breakpoints fall back to 3 and then 2 columns.

Header keeps only a live/connecting/offline status dot — the chain
name and block height badges were redundant now that the height tile
exists and animates on its own.

Stat-sub text bumped from 0.85em to 0.95em for readability.

Background: a warm ember-glow radial at the top of the viewport
layered with two accent radials and a low-opacity haori-checker tile
pattern (inline SVG data URI). Cards get a translucent backdrop so
the texture peeks through between them.
2026-04-22 21:29:11 +03:00
satoshi e881bc930d Revamp dashboard and persist pool stats across restarts
Dashboard now renders 10 tiles in a 5x2 overview: hashrate, best
share, miners, network hashrate, and expected block on the top row;
difficulty, block height, block reward, total work, and the
difficulty-adjustment countdown on the bottom row. Difficulty is
rendered with T/P suffixes instead of scientific notation, the main
hashrate card shows the 1-minute value, and the block-height tile
pulses orange when the network tip advances.

Added a 24-hour hashrate area chart below the overview, sampled
once per minute. Samples are persisted to a new hashrate_samples
SQLite table and restored on startup so the chart doesn't reset
every time kamado-api is restarted.

Cumulative pool work (sum of accepted diff-1-normalized shares) is
now tracked across ckpool restarts. The aggregator integrates only
positive deltas on pool.Shares — a regression means ckpool's
counter reset to zero and the baseline is refreshed without losing
the running total. A hasPoolSharesBaseline flag prevents double-
counting on the first refresh after a kamado-api restart. The
value is persisted to a new kv table once per minute.

Next-block reward (subsidy + fees) is fetched from bitcoind
getblocktemplate at most once per minute and surfaced as a tile.

Header's block-height badge now reads prevHeight via untrack() so
the effect doesn't form a dependency cycle with its own write.
2026-04-22 21:07:22 +03:00
satoshi 64d8af407d Record and display winning share difficulty on found blocks
Logmon now captures the share diff from ckpool's "Possible block
solve" line preceding the confirmation and attaches it to the
BlockEvent. Persisted as share_diff alongside height/hash/reward
and rendered as a new column in the dashboard block history.
2026-04-14 18:15:44 +03:00
satoshi c92a991e89 Make ckpool client resilient to warm-up EOFs
The stratifier can accept a connection and close it without
writing a response during early startup — the refresh loop
then logs 'ckpool poolstats failed: read len: EOF' on every
tick until the stratifier is ready, which is noisy and looks
like a real fault.

- client.Send retries once after 200ms if the server closed
  the connection before any bytes were read (io.EOF anywhere
  in the wrapped chain).
- Aggregator tracks a ckFailStreak counter: first two
  consecutive failures log at DEBUG, third and beyond escalate
  to WARN. Successful refreshes reset the streak.
2026-04-14 10:58:26 +03:00
satoshi d37a23bc57 Phase 3: Svelte 5 UI skeleton
Svelte 5 + Vite + TypeScript dashboard that consumes kamado-api over
REST for the first paint and then subscribes to /api/ws for live
updates. Zero runtime deps beyond svelte itself; plain CSS, no
component library.

Layout:

- Header with brand, WebSocket status badge, chain + height
- PoolOverview: hashrate (1m/5m/1h/24h), miner count, network
  hashrate + diff, pool share (ppb), expected time to block, uptime
- BlocksTable: recent solves from the in-memory ring (will be
  SQLite-backed in Phase 2b.5)
- BestShares: top-10 workers by bestever, falling back to bestdiff
  when the ckpool patch isn't present
- MinersTable: joined view of stratum clients and workers with
  user-agent-based hardware detection (Bitaxe, NerdQAxe, Antminer,
  ...), hashrate, best-round, best-ever, last share

State is a single $state() snapshot store in svelte-runes form;
components read from it via $derived. The store does one initial
REST snapshot fetch, then owns the WebSocket with exponential
backoff reconnects.

Vite dev server on :5173 proxies /api and /api/ws to localhost:8080
so you can run `make ui-dev` alongside `make up` in development.
Production serving (bundled into the Go binary via embed, behind /
on :8080) lands in Phase 4.
2026-04-13 03:07:59 +03:00