Commit Graph
7 Commits
Author SHA1 Message Date
satoshi df0dbf89e5 Harden block-recording pipeline: P0 reliability fixes
Closes the silent-failure modes between "ckpool logs a solve" and
"block correctly displayed":

* Difficulty estimate matched mempool.space — the projection now uses
  (inEpoch + 1) intervals so it converges on Bitcoin Core's eventual
  retarget formula at end-of-epoch instead of undershooting by ~0.05–
  0.10 % throughout.

* Tailer resumes mid-log on restart — persists (inode, offset) to kv
  every EOF + on shutdown, and replays the unread tail next time. Any
  solve line written while kamado-api was down would previously be
  invisible forever.

* Background reconcile loop (60 s) retries hash/reward enrichment for
  blocks the original RPC missed, so a transient bitcoind-index race no
  longer permanently leaves a block hashless.

* Reorg detection: same loop compares each recent stored hash against
  getblockhash(height); a mismatch stamps orphaned_at. UI renders these
  strikethrough with a red "orphaned" tag instead of showing illusory
  rewards forever.

* InsertBlock now reports whether a row was actually inserted; the
  caller WARN-logs duplicate-height ignores so a re-mined orphaned
  height can't disappear silently.

* Submit-attempt vs confirmed counters surface failed submissions:
  every "Possible/Submitting block solve" log line increments
  block_submit_attempts; "Solved and confirmed" increments
  block_submits_confirmed. A growing gap means bitcoind is rejecting
  our submissions — previously invisible.

* share_err patch refreshed against pinned ckpool source: added
  SE_NO_JOBID -> 21 and SE_WORKER_MISMATCH -> 24 mappings, kept
  SE_INVALID_NONCE2 in 20 (it's a malformed-input error, not low-diff).
  AxeOS users now see actionable Stratum codes instead of
  "unknown error".

UI gets new orphaned_at + block_submit_attempts/confirmed fields on
the snapshot type and a strikethrough-with-tag rendering for orphaned
blocks in BlocksTable.
2026-04-27 16:30:15 +03:00
satoshi 81227cb90f Tighten OSS detection, refresh TLS badge, plumb custom mempool URL
isOpenSource was matching things like Braiins OS, cgminer, bfgminer,
and a generic "esp32" — software that's open but runs on closed
hardware (Antminer, unknown rigs). public-pool's UI reserves the
star for open-source HARDWARE, with open firmware on top, and that's
the convention to match. New regex covers the family verbatim from
public-pool-ui's user-agent-link switch table:

  bitaxe, bitaxeHex, NerdMiner, NerdNOS, NerdAxe, NerdAxeGamma,
  NerdOCTAXE, NerdEKO, NerdQAxe+, NerdQAxe++, PiAxe, QAxe, QAxe+,
  0xAxe, LeafMiner

Also extend detectHardware so all those user-agents get a recognised
label instead of "esp32" / raw token.

Replace the squished "TLS" pill with an inline-flex badge: 0.78em
text, 5px corners, padlock SVG, generous left/right padding so it
reads at a glance instead of looking like a typo.

Custom block explorer: new optional union under StartOS Advanced
config ("Block Explorer" — defaults to "mempool.space"). Picking
"Custom URL" surfaces the value as MEMPOOL_BASE_URL on the
container env. config.Config picks it up, the aggregator copies
it into every Snapshot (mempool_base_url field), and the UI's
shared explorerBaseFor() helper trusts the custom URL verbatim
when present (no /testnet4 / /signet path appended — a self-hosted
instance is presumably single-network already). Falls back to the
public mempool.space mirrors per chain when unset, which is the
default behaviour.
2026-04-26 23:10:36 +03:00
satoshi 2f4ed4aa88 Fix BTC-vs-source-IP confusion; redesign user page; OSS + TLS icons
ckpool's stratum_instance.address (exposed as the "address" field in
the runtime JSON) is the SOURCE IP of the connection — set from
inet_ntop in connector.c — not the BTC payout address. The miner's
BTC payout comes from the stratum username, which ckpool stores in
worker.user (and in the dotted prefix of worker.workername).

Every "user" reference in the dashboard was reading c.address and
treating it as the BTC. Effects:

  • clicking an online miner navigated to #/user/<source-ip>; the
    UserDetailPage filters never matched and the page rendered with
    junk values (or the user struct's stale residual hashrate when
    the BTC happened to come from a sibling row).
  • offline-miner clicks worked, but totals came from user.dsps*
    which decay slowly inside ckpool, so a miner that had just
    disconnected still showed positive hashrate for several minutes.
  • the "Best (ever)" tile fell back to bestdiff (session) when
    bestever was zero, so it lied about its semantics.

Fixes:

  • Add btcAddressOf() helper and use w.user (or it) when extracting
    the BTC for the user-link button. The source IP gets its own
    sub-line under the worker name, clearly labelled.
  • Redesign UserDetailPage: filter clients by workername prefix
    against the BTC, never by c.address; compute hashrate totals by
    SUMMING the user's currently-connected clients (so 0 online
    clients => 0 hashrate, no stale decay artifacts); compute
    best_ever as max across the user's worker.bestever values; show
    online/total worker counts and a per-worker status pill.
  • Add an explorer link (mempool.space) for the user's BTC.

TLS detection moves to a clean signal: ckpool now binds two stratum
sockets — public plaintext and loopback-only. stunnel forwards to
the loopback bind, so TLS clients arrive with c.server == 1. The
dashboard reads that and renders a green TLS pill next to the
worker name. No source-IP heuristics needed.

Open-source mark: new isOpenSource() heuristic over the stratum
useragent matches Bitaxe family (NerdAxe / NerdQAxe / NerdMiner /
NerdOctaxe / Lucky / QAxe / MCCM), Braiins OS, cgminer / bfgminer /
ckminer, and ESP32 builds. Renders as an orange ★ next to the
hardware label, matching public-pool's convention.

types.ts: document StratumClient.address (source IP, not BTC) and
add the previously-undeclared `server` field. Surfacing the runtime
value that has been there all along since cfb0f83.
2026-04-26 18:41:05 +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 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