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.
This commit is contained in:
satoshi
2026-04-13 02:53:27 +03:00
parent bd0b1b0318
commit 0a40b8f84f
9 changed files with 633 additions and 14 deletions
+12 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/kamadopool/kamado-api/internal/ckpool"
"github.com/kamadopool/kamado-api/internal/config"
"github.com/kamadopool/kamado-api/internal/httpapi"
"github.com/kamadopool/kamado-api/internal/logmon"
"github.com/kamadopool/kamado-api/internal/state"
)
@@ -45,9 +46,19 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
api := httpapi.New(agg, log)
// Wire snapshot refreshes into the WebSocket hub so subscribers get
// real-time updates without polling.
agg.OnRefresh = api.Hub.Broadcast
go agg.Run(ctx)
api := httpapi.New(agg, log)
// Tail the ckpool log for block-solve events.
tailer := logmon.New(cfg.CKPoolLogFile, log)
go tailer.Run(ctx)
go agg.IngestBlockEvents(ctx, tailer.Events)
srv := &http.Server{
Addr: cfg.ListenAddr,
Handler: api.Handler(),