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
+8 -1
View File
@@ -12,11 +12,12 @@ import (
type Server struct {
Agg *state.Aggregator
Hub *Hub
Log *slog.Logger
}
func New(agg *state.Aggregator, log *slog.Logger) *Server {
return &Server{Agg: agg, Log: log}
return &Server{Agg: agg, Hub: NewHub(), Log: log}
}
// Handler returns an http.Handler with all kamado routes mounted under /api.
@@ -27,7 +28,9 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("GET /api/users", s.users)
mux.HandleFunc("GET /api/workers", s.workers)
mux.HandleFunc("GET /api/clients", s.clients)
mux.HandleFunc("GET /api/blocks", s.blocks)
mux.HandleFunc("GET /api/snapshot", s.snapshot)
mux.HandleFunc("GET /api/ws", s.handleWS)
return mux
}
@@ -83,3 +86,7 @@ func (s *Server) workers(w http.ResponseWriter, r *http.Request) {
func (s *Server) clients(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, s.Agg.Snapshot().Clients)
}
func (s *Server) blocks(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, s.Agg.Blocks())
}