Commit Graph
11 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 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 b786f489a1 Add ZMQ hashblock subscriber for sub-second chain refresh
New internal/zmqmon package subscribes to bitcoind's hashblock
ZMQ topic and emits TipEvents. Uses github.com/go-zeromq/zmq4
(pure Go, builds with CGO_ENABLED=0). Exponential backoff on
connection failure so bitcoind restarts don't kill the
subscriber permanently; event channel drops rather than blocks
if the consumer is slow (signals are advisory, not logs).

Aggregator.Run now takes a <-chan TipEvent; when a tip arrives
it fires an immediate refresh() outside the normal ticker
cadence. With a 5s poll interval and 0.5-1s ZMQ latency from
bitcoind, dashboards now reflect new tips roughly 4x faster.

Endpoint comes from BITCOIN_ZMQ_BLOCK — empty disables ZMQ
entirely and the aggregator just runs on the ticker alone.
2026-04-14 11:14:52 +03:00
satoshi 01829746ac Persist found blocks in SQLite (Phase 2b.5)
New internal/store package wraps modernc.org/sqlite (pure Go,
no CGO) with a BlockStore that exposes Open/Close/InsertBlock/
Recent. The aggregator now accepts an optional *store.BlockStore;
on Run() it loads up to maxBlockHistory rows from the store before
the first refresh, and each ingested block gets written to the
DB before being appended to the in-memory ring.

main.go opens the store at cfg.DBPath and logs a warning + falls
back to in-memory-only if the file can't be created — a broken
data volume shouldn't stop the pool from running.

InsertBlock uses INSERT OR IGNORE on the height primary key so
replayed log events after a restart are harmless.
2026-04-14 11:06:38 +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 0a40b8f84f Phase 2b: log tailer, block history, stdlib WebSocket push
Adds real-time block detection via ckpool log tailing and a push
channel for the upcoming Svelte UI, all stdlib-only:

- logmon.Tailer follows ckpool.log with rotation/truncation survival
  (inode + size tracking) and parses "Solved and confirmed block N"
  into BlockEvent values.
- state.Aggregator grows a 256-entry block ring, an OnRefresh hook,
  and IngestBlockEvents which best-effort enriches events with the
  block hash via bitcoind getblockhash.
- httpapi.Hub implements RFC 6455 from scratch (SHA1 handshake,
  unmasked text frames out, masked frames in, ping keepalive,
  per-client write mutex, slow-client drop) so we don't pull in a
  ws dependency before we can go mod tidy.
- New routes: GET /api/blocks and GET /api/ws. Snapshot pushes fire
  on every poll tick and immediately on block-solve.

ZMQ hashblock subscription and SQLite persistence are deferred to
Phase 2b.5 once the s9pk packaging repo exists and we have a real
build environment for adding Go deps.
2026-04-13 02:53:27 +03:00
satoshiandClaude Opus 4.6 bd0b1b0318 Phase 2a: kamado-api Go middleware (core MVP)
Go 1.22 module that polls CKPool's Unix socket control API, queries
Bitcoin Core over JSON-RPC, merges both into a thread-safe snapshot, and
serves it over REST. Layout:

  api/
  ├── cmd/kamado-api/main.go              signal-aware entrypoint
  └── internal/
      ├── config/       env var loader with validation
      ├── ckpool/       socket client (4-byte LE length-prefixed wire
      │                 protocol verified against libckpool.c), typed
      │                 response models for poolstats/users/workers/
      │                 clients/uptime, + unit tests using a fake
      │                 unix socket server
      ├── bitcoind/     minimal JSON-RPC client, getblockchaininfo
      │                 and getnetworkhashps
      ├── state/        Aggregator that refreshes a merged Snapshot
      │                 on a ticker; readers get a copy under RWMutex
      └── httpapi/      REST handlers on Go 1.22 ServeMux:
                        /api/health /api/pool /api/users
                        /api/workers /api/clients /api/snapshot

CKPool stores hashrate as "dsps" (diff shares per second); we convert
to H/s via the 2^32 constant used by GoBrrr-Pool and other clients.
Every stat CKPool exposes to its socket API is surfaced — useragent,
IP, per-client diff, per-worker best diff — closing the gap against
Bassin which only reads the 60-second stats files.

Dockerfile does a CGO_ENABLED=0 static build on golang:1.22-bookworm
with -trimpath -ldflags=-s -w. docker-compose now runs both ckpool and
kamado-api, sharing a named volume for /run/ckpool so the API can
dial the stratifier socket directly.

Deferred to Phase 2b (called out in README):
  - Bitcoin Core ZMQ hashblock subscriber
  - WebSocket push for real-time UI updates
  - SQLite persistence (block history, best-share history)
  - CKPool log tailer for "Solved and confirmed block" detection

Tests and build NOT run in this commit — Go isn't installed in the
dev environment. Run `make api-test` or `make api` to verify.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 19:52:27 +03:00