Kill ckpool on bitcoind failure so miners can failover

When bitcoind is unreachable for 3+ consecutive polls and no block
submission is pending, SIGTERM the ckpool process so miners disconnect
and failover to backup pools. Adds a red dashboard banner when
Bitcoin Core is down.
This commit is contained in:
satoshi
2026-05-19 01:29:49 +03:00
parent c6f0ba5f4d
commit daa87e2352
3 changed files with 93 additions and 2 deletions
+39 -1
View File
@@ -190,6 +190,12 @@ type Aggregator struct {
// refresh. Used by the WebSocket hub to push updates to clients.
OnRefresh func(Snapshot)
// KillCKPool, if set, is called when bitcoind has been unreachable
// for several consecutive polls and no block submission is pending.
// Killing ckpool disconnects miners so they can failover to other
// pools instead of mining stale work.
KillCKPool func() error
mu sync.RWMutex
snap Snapshot
blocks []BlockRecord
@@ -231,6 +237,12 @@ type Aggregator struct {
// escalate to WARN.
ckFailStreak int
// btcFailStreak counts consecutive refreshes where bitcoind was
// unreachable. After a threshold, if no block submission is pending,
// we kill the ckpool process so miners can failover to other pools.
btcFailStreak int
ckpoolKilled bool // true after we sent SIGTERM, reset on bitcoind recovery
// readyOnce + ready closes the Ready() channel exactly once after
// the first refresh completes. main blocks briefly on this so the
// HTTP server doesn't serve a never-refreshed (all-zeros) snapshot.
@@ -424,6 +436,11 @@ func (a *Aggregator) refresh(ctx context.Context) {
if bi, err := a.RPC.GetBlockchainInfo(ctx); err == nil {
next.Chain = bi
next.BitcoinOK = true
if a.btcFailStreak > 0 {
a.Log.Info("bitcoind recovered", "after_failures", a.btcFailStreak)
}
a.btcFailStreak = 0
a.ckpoolKilled = false
// Track when the tip height last changed.
if bi.Blocks != a.lastTipHeight {
a.lastTipHeight = bi.Blocks
@@ -436,7 +453,8 @@ func (a *Aggregator) refresh(ctx context.Context) {
next.NetworkHashrateHs = nh
}
} else {
a.Log.Warn("bitcoind getblockchaininfo failed", "err", err)
a.btcFailStreak++
a.Log.Warn("bitcoind getblockchaininfo failed", "err", err, "streak", a.btcFailStreak)
if next.LastError == "" {
next.LastError = err.Error()
}
@@ -675,6 +693,26 @@ func (a *Aggregator) refresh(ctx context.Context) {
cb(pushed)
}
a.markReady()
// Kill ckpool when bitcoind is unreachable so miners can failover.
// Conditions: 3+ consecutive failures, no pending block submission,
// haven't already killed it, and a kill callback is configured.
const btcFailThreshold = 3
submitGap := next.BlockSubmitAttempts - next.BlockSubmitsConfirmed
if a.btcFailStreak >= btcFailThreshold && !a.ckpoolKilled && a.KillCKPool != nil {
if submitGap > 0 {
a.Log.Warn("bitcoind down but block submission pending, keeping ckpool alive",
"submit_gap", submitGap, "streak", a.btcFailStreak)
} else {
a.Log.Warn("bitcoind unreachable, killing ckpool so miners can failover",
"streak", a.btcFailStreak)
if err := a.KillCKPool(); err != nil {
a.Log.Error("failed to kill ckpool", "err", err)
} else {
a.ckpoolKilled = true
}
}
}
}
// AckBestDiff records the current best_diff as acknowledged so the UI