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:
@@ -36,6 +36,9 @@ type Snapshot struct {
|
||||
Chain *bitcoind.BlockchainInfo `json:"chain"`
|
||||
NetworkHashrateHs float64 `json:"network_hashrate_hs"`
|
||||
|
||||
// Recent found blocks (in-memory history; persisted in Phase 2b.5).
|
||||
RecentBlocks []BlockRecord `json:"recent_blocks,omitempty"`
|
||||
|
||||
// Health
|
||||
CKPoolOK bool `json:"ckpool_ok"`
|
||||
BitcoinOK bool `json:"bitcoin_ok"`
|
||||
@@ -49,8 +52,13 @@ type Aggregator struct {
|
||||
Interval time.Duration
|
||||
Log *slog.Logger
|
||||
|
||||
mu sync.RWMutex
|
||||
snap Snapshot
|
||||
// OnRefresh, if set, is called (non-blocking) after each snapshot
|
||||
// refresh. Used by the WebSocket hub to push updates to clients.
|
||||
OnRefresh func(Snapshot)
|
||||
|
||||
mu sync.RWMutex
|
||||
snap Snapshot
|
||||
blocks []BlockRecord
|
||||
}
|
||||
|
||||
func New(ck *ckpool.Client, rpc *bitcoind.RPC, interval time.Duration, log *slog.Logger) *Aggregator {
|
||||
@@ -132,6 +140,18 @@ func (a *Aggregator) refresh(ctx context.Context) {
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
// Attach current block history so /api/snapshot and WebSocket
|
||||
// pushes carry the same view.
|
||||
if len(a.blocks) > 0 {
|
||||
next.RecentBlocks = make([]BlockRecord, len(a.blocks))
|
||||
copy(next.RecentBlocks, a.blocks)
|
||||
}
|
||||
a.snap = next
|
||||
cb := a.OnRefresh
|
||||
pushed := next
|
||||
a.mu.Unlock()
|
||||
|
||||
if cb != nil {
|
||||
cb(pushed)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user