diff --git a/.env.example b/.env.example
index ae6c5f9..6302c37 100644
--- a/.env.example
+++ b/.env.example
@@ -2,5 +2,5 @@
POOL_BTCADDRESS=tb1qexamplereplacewithyourownaddressforrewards
BITCOIN_RPC_USER=kamado
BITCOIN_RPC_PASSWORD=changeme
-# Optional — ZMQ endpoint for instant block notifications from bitcoind
+# Optional, ZMQ endpoint for instant block notifications from bitcoind
# ZMQ_BLOCK=tcp://host.docker.internal:28332
diff --git a/Makefile b/Makefile
index ad0010f..921e1bb 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
# ----------------------------------------------------------------------------
-# Kamado Pool — top-level Makefile
+# Kamado Pool, top-level Makefile
# ----------------------------------------------------------------------------
.PHONY: help ckpool api api-test ckpool-shell ui ui-dev ui-check up down logs clean
diff --git a/README.md b/README.md
index fdff51d..d62e777 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Kamado exists because existing CKPool wrappers like Bassin read only a handful o
| `api/` | Go | Socket client, bitcoind RPC, ZMQ subscriber, log tailer, state aggregator, REST + WebSocket API, SQLite persistence |
| `ui/` | Svelte 5 | Real-time dashboard with pool overview, miner stats, block history, best share tracking, transaction accelerator |
-The Go binary embeds the built Svelte app via `//go:embed` and serves it at `/` — a single static binary with no external web server.
+The Go binary embeds the built Svelte app via `//go:embed` and serves it at `/`, a single static binary with no external web server.
## Improvements Over Upstream CKPool
@@ -50,18 +50,18 @@ Nine patches are applied to upstream commit `cfb0f83` (which itself includes the
The Go API (`kamado-api`) bridges CKPool's Unix socket protocol, bitcoind's JSON-RPC, and ZMQ into a unified HTTP/WebSocket interface:
-- **Socket client** — CKPool uses a 4-byte length-prefixed binary protocol on a Unix domain socket. kamado-api opens a fresh connection per request and queries pool, user, worker, and client state in real time.
-- **State aggregator** — Merges socket responses, bitcoind chain info, ZMQ events, and log-tailed block solves into a single thread-safe snapshot, refreshed on a configurable interval.
-- **ZMQ subscriber** — Listens to bitcoind's `hashblock` topic for sub-second block notifications, triggering an immediate state refresh. Falls back to RPC polling if ZMQ is unavailable.
-- **Log tailer** — Watches CKPool's log file (inotify-based) for "Solved and confirmed block" lines, extracts height and hash, enriches via bitcoind RPC (reward, confirmations), and records to SQLite.
-- **Block reconciliation** — On startup, cross-references in-memory blocks with the database and bitcoind to detect orphans and fill gaps.
-- **Chain reorg detection** — Scoped to the active chain; clears stale state on tip changes.
-- **SQLite persistence** — Blocks, accelerated transactions, and log cursor survive restarts. Non-fatal if unavailable (in-memory ring fallback).
-- **Transaction accelerator** — Inspects the current block template to find the marginal (lowest fee-rate) transaction, calls `prioritisetransaction` to boost a target tx, and reports the displaced fee as revenue impact. Includes a hard cap (2000 sat/vB) and lifecycle cleanup.
+- **Socket client**: CKPool uses a 4-byte length-prefixed binary protocol on a Unix domain socket. kamado-api opens a fresh connection per request and queries pool, user, worker, and client state in real time.
+- **State aggregator**: Merges socket responses, bitcoind chain info, ZMQ events, and log-tailed block solves into a single thread-safe snapshot, refreshed on a configurable interval.
+- **ZMQ subscriber**: Listens to bitcoind's `hashblock` topic for sub-second block notifications, triggering an immediate state refresh. Falls back to RPC polling if ZMQ is unavailable.
+- **Log tailer**: Watches CKPool's log file (inotify-based) for "Solved and confirmed block" lines, extracts height and hash, enriches via bitcoind RPC (reward, confirmations), and records to SQLite.
+- **Block reconciliation**: On startup, cross-references in-memory blocks with the database and bitcoind to detect orphans and fill gaps.
+- **Chain reorg detection**: Scoped to the active chain; clears stale state on tip changes.
+- **SQLite persistence**: Blocks, accelerated transactions, and log cursor survive restarts. Non-fatal if unavailable (in-memory ring fallback).
+- **Transaction accelerator**: Inspects the current block template to find the marginal (lowest fee-rate) transaction, calls `prioritisetransaction` to boost a target tx, and reports the displaced fee as revenue impact. Includes a hard cap (2000 sat/vB) and lifecycle cleanup.
### Stratum TLS
-Optional encrypted stratum via stunnel. CKPool binds a public plaintext socket plus one loopback-only socket per TLS certificate; stunnel terminates TLS on the external port and forwards decrypted traffic to the matching internal bind. CKPool tags each connection with its `serverurl` index, and the deployment declares what those indices mean via the `STRATUM_SERVERS` env — so the dashboard shows a lock icon next to encrypted miners and names the certificate in use on hover, with no source-IP heuristics.
+Optional encrypted stratum via stunnel. CKPool binds a public plaintext socket plus one loopback-only socket per TLS certificate; stunnel terminates TLS on the external port and forwards decrypted traffic to the matching internal bind. CKPool tags each connection with its `serverurl` index, and the deployment declares what those indices mean via the `STRATUM_SERVERS` env, so the dashboard shows a lock icon next to encrypted miners and names the certificate in use on hover, with no source-IP heuristics.
Under StartOS this drives two certificates on one port, chosen per connection by SNI: a CA-issued (Let's Encrypt) certificate for miners connecting over a clearnet domain, and the self-signed one for miners on the LAN, which send no SNI and fall through to it.
@@ -71,33 +71,33 @@ The self-signed certificate is auto-generated on first start with broad SAN cove
The Svelte 5 dashboard connects via WebSocket for real-time push updates (no polling in normal operation) and includes:
-- **Pool overview** — Hashrate (1m/5m/1h/24h), uptime, workers online, shares accepted/rejected, expected time to block, round effort
-- **Hashrate chart** — Interactive multi-window visualization
-- **Miners table** — Per-user and per-worker stats: hashrate, difficulty, latency, best shares, idle detection
-- **Hardware detection** — Parses stratum user-agent strings to identify miner hardware (Bitaxe, Bitaxe Hex, NerdMiner, NerdAxe, NerdQAxe, NerdOCTAXE, NerdEKO, NerdNOS, PiAxe, QAxe, 0xAxe, LeafMiner, and more). Open-source hardware is flagged with a star badge so operators can see their fleet composition at a glance.
-- **TLS badges** — Miners connected via the encrypted stratum port display a lock icon in the miners table, derived from CKPool's `server` field (no IP heuristics).
-- **User/worker detail pages** — Deep dive into individual miner stats, cumulative work, and luck
-- **Block history** — Found blocks with height, hash, reward, solving worker, chain name, orphan status
-- **Best share leaderboard** — "This round" and "all-time" tracking with per-worker breakdown and glowing difficulty range indicators
-- **PoW inspector** — Graphical breakdown of the block header behind the best share (version, prev block, merkle root, time, bits, nonce), the raw 80-byte header hex, and the coinbase + merkle branches that reproduce the merkle root — with one-click in-browser verification that replays the real SHA-256 compression rounds as an animation, ending at the share's block hash — self-contained implementation, no external services, works over plain-HTTP LAN
-- **Transaction accelerator** — Boost transactions via `prioritisetransaction` with marginal fee displacement analysis showing the revenue impact of each boost
-- **Block-found animation** — Celebratory toast on solve events
-- **Health banners** — Live status indicators for CKPool, bitcoind, ZMQ, and submit-gap diagnostics
-- **Block-update latency** — Tracks and displays average, last, and wasted-work latency for block notifications so operators can tune ZMQ and polling
-- **Shares bar** — Accepted/rejected/stale share summary with ratio visualization
-- **Custom mempool explorer** — Transaction and block links can point at a self-hosted mempool instance instead of the public mempool.space
-- **Mobile responsive** — Full breakpoint coverage for phone and tablet
-- **Donation footer** — BTC address with hover QR code
+- **Pool overview**: Hashrate (1m/5m/1h/24h), uptime, workers online, shares accepted/rejected, expected time to block, round effort
+- **Hashrate chart**: Interactive multi-window visualization
+- **Miners table**: Per-user and per-worker stats: hashrate, difficulty, latency, best shares, idle detection
+- **Hardware detection**: Parses stratum user-agent strings to identify miner hardware (Bitaxe, Bitaxe Hex, NerdMiner, NerdAxe, NerdQAxe, NerdOCTAXE, NerdEKO, NerdNOS, PiAxe, QAxe, 0xAxe, LeafMiner, and more). Open-source hardware is flagged with a star badge so operators can see their fleet composition at a glance.
+- **TLS badges**: Miners connected via the encrypted stratum port display a lock icon in the miners table, derived from CKPool's `server` field (no IP heuristics).
+- **User/worker detail pages**: Deep dive into individual miner stats, cumulative work, and luck
+- **Block history**: Found blocks with height, hash, reward, solving worker, chain name, orphan status
+- **Best share leaderboard**: "This round" and "all-time" tracking with per-worker breakdown and glowing difficulty range indicators
+- **PoW inspector**: Graphical breakdown of the block header behind the best share (version, prev block, merkle root, time, bits, nonce), the raw 80-byte header hex, and the coinbase + merkle branches that reproduce the merkle root, with one-click in-browser verification that replays the real SHA-256 compression rounds as an animation, ending at the share's block hash, self-contained implementation, no external services, works over plain-HTTP LAN
+- **Transaction accelerator**: Boost transactions via `prioritisetransaction` with marginal fee displacement analysis showing the revenue impact of each boost
+- **Block-found animation**: Celebratory toast on solve events
+- **Health banners**: Live status indicators for CKPool, bitcoind, ZMQ, and submit-gap diagnostics
+- **Block-update latency**: Tracks and displays average, last, and wasted-work latency for block notifications so operators can tune ZMQ and polling
+- **Shares bar**: Accepted/rejected/stale share summary with ratio visualization
+- **Custom mempool explorer**: Transaction and block links can point at a self-hosted mempool instance instead of the public mempool.space
+- **Mobile responsive**: Full breakpoint coverage for phone and tablet
+- **Donation footer**: BTC address with hover QR code
### Reliability
-- **Dual block notification** — ZMQ hashblock for instant detection + RPC polling as fallback. Both run simultaneously; every second of stale work in solo mode is hashrate burned on a dead block.
-- **ckpool kill-on-failure** — When bitcoind becomes unreachable, kamado-api kills ckpool so miners can failover to another pool. ckpool restarts automatically when bitcoind recovers.
-- **Deferred RPC** — Dashboard RPCs wait until the ckpool notifier completes on tip change to prioritize system resources for block reconstruction and validation.
-- **Log cursor persistence** — The tailer's file offset is stored in SQLite so block-solve detection resumes correctly after restart.
-- **Load before ingest** — Persisted counters are restored *before* the log-tailer goroutines start. They share the same state and persist it on their first event, so starting them against a half-restored aggregator would write a few seconds of new shares over the accumulated all-time history — and then read that back as the new truth. `RebuildShareStatsFromLog` (exposed as `POST /api/admin/rebuild-share-stats`) recounts the all-time difficulty distribution and rejection reasons from the ckpool log if they are ever lost; it only adopts the result when the log accounts for more shares than the stored totals, so it can restore history but never erase it.
-- **Clean restarts for miners** — Upstream ckpool sends a reconnecting miner its first job *before* telling it the difficulty, and resets every client to the pool's `startdiff`, discarding what vardiff had tuned. Both produce a burst of "Above target" rejects on every pool restart. Patch `0009` sends `mining.set_difficulty` first, seeds the starting difficulty from the worker's retained hashrate, and accepts shares for the already-in-flight job down to the pool minimum until the next `mining.notify`.
-- **Warm-up resilience** — The ckpool socket client handles transient EOFs during startup gracefully instead of crashing.
+- **Dual block notification**: ZMQ hashblock for instant detection + RPC polling as fallback. Both run simultaneously; every second of stale work in solo mode is hashrate burned on a dead block.
+- **ckpool kill-on-failure**: When bitcoind becomes unreachable, kamado-api kills ckpool so miners can failover to another pool. ckpool restarts automatically when bitcoind recovers.
+- **Deferred RPC**: Dashboard RPCs wait until the ckpool notifier completes on tip change to prioritize system resources for block reconstruction and validation.
+- **Log cursor persistence**: The tailer's file offset is stored in SQLite so block-solve detection resumes correctly after restart.
+- **Load before ingest**: Persisted counters are restored *before* the log-tailer goroutines start. They share the same state and persist it on their first event, so starting them against a half-restored aggregator would write a few seconds of new shares over the accumulated all-time history, and then read that back as the new truth. `RebuildShareStatsFromLog` (exposed as `POST /api/admin/rebuild-share-stats`) recounts the all-time difficulty distribution and rejection reasons from the ckpool log if they are ever lost; it only adopts the result when the log accounts for more shares than the stored totals, so it can restore history but never erase it.
+- **Clean restarts for miners**: Upstream ckpool sends a reconnecting miner its first job *before* telling it the difficulty, and resets every client to the pool's `startdiff`, discarding what vardiff had tuned. Both produce a burst of "Above target" rejects on every pool restart. Patch `0009` sends `mining.set_difficulty` first, seeds the starting difficulty from the worker's retained hashrate, and accepts shares for the already-in-flight job down to the pool minimum until the next `mining.notify`.
+- **Warm-up resilience**: The ckpool socket client handles transient EOFs during startup gracefully instead of crashing.
## API Reference
@@ -157,9 +157,9 @@ The dashboard is at `http://localhost:8080`. Point a miner at `stratum+tcp://loc
| Variable | Default | Required | Description |
|----------|---------|----------|-------------|
-| `BITCOIN_RPC_URL` | — | yes | bitcoind RPC endpoint (e.g. `http://127.0.0.1:8332`) |
-| `BITCOIN_RPC_USER` | — | yes | RPC username |
-| `BITCOIN_RPC_PASSWORD` | — | yes | RPC password |
+| `BITCOIN_RPC_URL` |, | yes | bitcoind RPC endpoint (e.g. `http://127.0.0.1:8332`) |
+| `BITCOIN_RPC_USER` |, | yes | RPC username |
+| `BITCOIN_RPC_PASSWORD` |, | yes | RPC password |
| `LISTEN_ADDR` | `:8080` | no | HTTP bind address |
| `CKPOOL_SOCKDIR` | `/run/ckpool` | no | CKPool Unix socket directory |
| `CKPOOL_LOGFILE` | `/var/log/ckpool/ckpool.log` | no | CKPool log path for block-solve detection |
@@ -168,7 +168,7 @@ The dashboard is at `http://localhost:8080`. Point a miner at `stratum+tcp://loc
| `BITCOIN_ZMQ_BLOCK` | (disabled) | no | ZMQ hashblock endpoint (e.g. `tcp://127.0.0.1:28332`) |
| `BITCOIN_RPC_TIMEOUT` | `10s` | no | RPC call timeout |
| `MEMPOOL_BASE_URL` | (mempool.space) | no | Custom mempool explorer URL |
-| `STRATUM_SERVERS` | (undeclared) | no | JSON array describing ckpool's `serverurl[]` binds, e.g. `[{"kind":"plain","label":"Plaintext"},{"kind":"tls-local","label":"TLS — self-signed"}]`. Entry N labels clients with `server == N` in the dashboard. Unset falls back to treating index 1 as TLS |
+| `STRATUM_SERVERS` | (undeclared) | no | JSON array describing ckpool's `serverurl[]` binds, e.g. `[{"kind":"plain","label":"Plaintext"},{"kind":"tls-local","label":"TLS, self-signed"}]`. Entry N labels clients with `server == N` in the dashboard. Unset falls back to treating index 1 as TLS |
### UI Development
@@ -176,7 +176,7 @@ The dashboard is at `http://localhost:8080`. Point a miner at `stratum+tcp://loc
make ui-dev
```
-Starts Vite on `:5173` with hot reload. API calls are proxied to `:8080` — run `make up` first so the backend is available.
+Starts Vite on `:5173` with hot reload. API calls are proxied to `:8080`, run `make up` first so the backend is available.
### Testing
@@ -202,8 +202,8 @@ The test suite covers the critical mining path with 38 tests across 5 packages:
The smoke test runs two phases on regtest:
-1. **Log-inject path** — Injects a synthetic block-solve line into ckpool's log, verifies the tailer detects it, enriches it via bitcoind RPC, and surfaces it at `/api/blocks`. Quick, no ckpool binary needed.
-2. **Full stratum path** — Starts ckpool, connects a Python stratum miner, mines a real block, verifies the coinbase contains the "kamado" tag and the configured pool identifier, validates that bitcoind accepted the block with confirmations > 0, and checks that the block validation log shows correct chain acceptance.
+1. **Log-inject path**, Injects a synthetic block-solve line into ckpool's log, verifies the tailer detects it, enriches it via bitcoind RPC, and surfaces it at `/api/blocks`. Quick, no ckpool binary needed.
+2. **Full stratum path**, Starts ckpool, connects a Python stratum miner, mines a real block, verifies the coinbase contains the "kamado" tag and the configured pool identifier, validates that bitcoind accepted the block with confirmations > 0, and checks that the block validation log shows correct chain acceptance.
The smoke test auto-builds ckpool from the pinned upstream source with patches applied if not cached (build cached in `~/.cache/kamado-dev/`).
@@ -243,10 +243,10 @@ The StartOS wrapper packages Kamado Pool as an `.s9pk` for StartOS 0.3.5.1 with
## Upstream
-CKPool by Con Kolivas — https://bitbucket.org/ckolivas/ckpool
+CKPool by Con Kolivas, https://bitbucket.org/ckolivas/ckpool
Pinned commit: `cfb0f83b70d7b382b85d2bd0710cf4cb2dda4007`
## License
-GPL-3.0 — see [LICENSE](LICENSE).
+GPL-3.0, see [LICENSE](LICENSE).
diff --git a/api/Dockerfile b/api/Dockerfile
index 12620f1..46b9bc6 100644
--- a/api/Dockerfile
+++ b/api/Dockerfile
@@ -19,7 +19,7 @@ FROM node:22-bookworm-slim AS ui
WORKDIR /ui
# Copy just the manifest first so npm install is cached across code
-# edits. The build context must be the repo root — docker-compose
+# edits. The build context must be the repo root, docker-compose
# uses `context: .`, and `make api` invokes docker build with the
# repo root as context.
COPY ui/package.json ./
diff --git a/api/cmd/kamado-api/main.go b/api/cmd/kamado-api/main.go
index 3ff12eb..0111683 100644
--- a/api/cmd/kamado-api/main.go
+++ b/api/cmd/kamado-api/main.go
@@ -49,7 +49,7 @@ func main() {
ck := ckpool.New(cfg.CKPoolSockDir)
rpc := bitcoind.NewRPC(cfg.BitcoinRPCURL, cfg.BitcoinRPCUser, cfg.BitcoinRPCPassword, cfg.BitcoinRPCTimeout)
- // Persistent block history. Non-fatal if it can't be opened — the
+ // Persistent block history. Non-fatal if it can't be opened, the
// aggregator falls back to an in-memory ring so the pool keeps
// running even with a broken data volume.
var blockStore *store.BlockStore
@@ -144,7 +144,7 @@ func main() {
// Wait for the aggregator's first refresh before starting the log
// tailer. This guarantees currentChain is set before any block
// events are processed, so blocks are always stamped with the
- // correct chain — preventing false orphans on network switches.
+ // correct chain, preventing false orphans on network switches.
select {
case <-agg.Ready():
log.Info("first snapshot ready")
diff --git a/api/internal/accelerator/accelerator.go b/api/internal/accelerator/accelerator.go
index 032cccb..8a1f320 100644
--- a/api/internal/accelerator/accelerator.go
+++ b/api/internal/accelerator/accelerator.go
@@ -82,7 +82,7 @@ func marginalFeeLost(tpl *bitcoind.BlockTemplate, txid string, txVsize int64) in
}
// The displaced tx's fee is the revenue we lose. We don't add the
- // boosted tx's real fee back because the pool never had it — the tx
+ // boosted tx's real fee back because the pool never had it, the tx
// wasn't in the template before the boost.
lost := marginal.Fee
if lost < 0 {
@@ -261,7 +261,7 @@ func (s *Service) cleanupOnce(ctx context.Context) {
for _, tx := range txs {
_, err := s.RPC.GetMempoolEntry(ctx, tx.Txid)
if err != nil && bitcoind.IsRPCError(err, -5) {
- // Tx no longer in mempool — confirmed or dropped.
+ // Tx no longer in mempool, confirmed or dropped.
if err := s.Store.RemoveBoostedTx(tx.Txid); err != nil {
s.Log.Warn("accelerator cleanup: remove failed", "txid", tx.Txid, "err", err)
} else {
diff --git a/api/internal/bitcoind/rpc.go b/api/internal/bitcoind/rpc.go
index 098a4cd..ede05ff 100644
--- a/api/internal/bitcoind/rpc.go
+++ b/api/internal/bitcoind/rpc.go
@@ -140,7 +140,7 @@ func isRetryable(err error) bool {
if err == nil {
return false
}
- // rpcError values come from bitcoind itself — check for warmup/
+ // rpcError values come from bitcoind itself, check for warmup/
// loading messages that mean "ask again in a moment".
var rerr *rpcError
if errors.As(err, &rerr) {
diff --git a/api/internal/bitcoind/rpc_test.go b/api/internal/bitcoind/rpc_test.go
index d21f661..b5ee802 100644
--- a/api/internal/bitcoind/rpc_test.go
+++ b/api/internal/bitcoind/rpc_test.go
@@ -119,7 +119,7 @@ func TestCall_RetryOnTransient503(t *testing.T) {
}
func TestCall_ExhaustsRetries(t *testing.T) {
- // Server always returns 503 — all 3 attempts should fail.
+ // Server always returns 503, all 3 attempts should fail.
srv, calls := jsonRPCServer(t, 99, nil)
rpc := NewRPC(srv.URL, "u", "p", 5*time.Second)
@@ -134,7 +134,7 @@ func TestCall_ExhaustsRetries(t *testing.T) {
func TestCall_NoRetryOnSemanticRPCError(t *testing.T) {
// Server returns a semantic RPC error (block not found). Should not be
- // retried — the answer is definitive.
+ // retried, the answer is definitive.
var calls int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&calls, 1)
diff --git a/api/internal/ckpool/client.go b/api/internal/ckpool/client.go
index ce4055b..1aebcea 100644
--- a/api/internal/ckpool/client.go
+++ b/api/internal/ckpool/client.go
@@ -53,7 +53,7 @@ var ErrProtocol = errors.New("ckpool: protocol error")
// or SocketConnector.
//
// If the server closes the connection before writing a response (common
-// during ckpool warm-up — the stratifier accepts the socket but its stats
+// during ckpool warm-up, the stratifier accepts the socket but its stats
// aren't populated yet), Send retries once after a short backoff.
func (c *Client) Send(ctx context.Context, sockName, cmd string) ([]byte, error) {
buf, err := c.sendOnce(ctx, sockName, cmd)
diff --git a/api/internal/httpapi/server.go b/api/internal/httpapi/server.go
index 7266d62..42877c8 100644
--- a/api/internal/httpapi/server.go
+++ b/api/internal/httpapi/server.go
@@ -59,7 +59,7 @@ func (s *Server) Handler() http.Handler {
func spaHandler(root fs.FS) http.Handler {
fileServer := http.FileServerFS(root)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Defence-in-depth — /api routes are matched by the mux first
+ // Defence-in-depth, /api routes are matched by the mux first
// with their GET patterns, but a bare POST /api/... would fall
// through here. Return 404 so we don't accidentally shadow
// API semantics with HTML.
diff --git a/api/internal/httpapi/ws.go b/api/internal/httpapi/ws.go
index 2f9ca8f..becc132 100644
--- a/api/internal/httpapi/ws.go
+++ b/api/internal/httpapi/ws.go
@@ -3,7 +3,7 @@
// Scope: push JSON snapshots to subscribed clients. We never expect
// client payloads larger than a ping/pong and we don't negotiate
// extensions (compression, fragmentation). Anything fancier is out of
-// scope for Phase 2b — upgrade to a real ws library once we can run
+// scope for Phase 2b, upgrade to a real ws library once we can run
// `go mod tidy` in a real build environment.
package httpapi
diff --git a/api/internal/logmon/tailer.go b/api/internal/logmon/tailer.go
index 3302e16..2063425 100644
--- a/api/internal/logmon/tailer.go
+++ b/api/internal/logmon/tailer.go
@@ -1,4 +1,4 @@
-// Package logmon tails the ckpool log file looking for notable events —
+// Package logmon tails the ckpool log file looking for notable events,
// primarily block-solve lines, which are the most reliable signal we have
// that a block was found, short of a ZMQ hashblock subscription.
//
@@ -39,7 +39,7 @@ type BlockEvent struct {
}
// AttemptEvent is emitted when ckpool logs a "Possible block solve"
-// or "Submitting possible block solve" line — i.e. ckpool decided a
+// or "Submitting possible block solve" line, i.e. ckpool decided a
// share met network difficulty and is attempting to submit it to
// bitcoind. This precedes the "Solved and confirmed" line that drives
// BlockEvent. Counting attempts vs confirmations surfaces submission
@@ -52,7 +52,7 @@ type AttemptEvent struct {
}
// LatencyEvent is emitted when ckpool logs the "Block update latency"
-// line from our patch 0005 — the milliseconds between ZMQ trigger and
+// line from our patch 0005, the milliseconds between ZMQ trigger and
// mining.notify broadcast completion.
type LatencyEvent struct {
SeenAt time.Time
@@ -156,8 +156,8 @@ var (
// The first number after "diff " is sdiff (share difficulty solved).
acceptedShareRE = regexp.MustCompile(`Accepted client \S+ share diff ([0-9.]+)/[^:]+:\s*([0-9a-fA-F]+)`)
// Rejected shares come in two forms:
- // 1. "dupe diff" / "high diff" — share was valid but duplicate or below target
- // 2. "invalid share " — share was structurally invalid (Stale, etc.)
+ // 1. "dupe diff" / "high diff", share was valid but duplicate or below target
+ // 2. "invalid share ", share was structurally invalid (Stale, etc.)
rejectedDiffRE = regexp.MustCompile(`Rejected client \S+ (\w+) diff [0-9.]+/`)
rejectedInvalRE = regexp.MustCompile(`Rejected client \S+ invalid share (.+)`)
)
@@ -171,7 +171,7 @@ const powDataMarker = "Best share PoW data "
// parsePowLine extracts the PoW record from a log line carrying
// powDataMarker. It decodes exactly one JSON value starting at the first
-// '{' after the marker and deliberately ignores everything after it —
+// '{' after the marker and deliberately ignores everything after it,
// production logs have carried trailing bytes after the closing brace,
// which silently defeated an end-of-line-anchored regex. Returns
// (nil, nil) when the marker is absent, and a non-nil error for a
@@ -368,7 +368,7 @@ func (t *Tailer) handleLine(line string) {
default:
}
}
- // Don't return — an accepted share may also be a block solve,
+ // Don't return, an accepted share may also be a block solve,
// so let the solve-diff regex below get a chance to match too.
}
// Rejected share with diff info: "Rejected client dupe|high diff ..."
@@ -407,7 +407,7 @@ func (t *Tailer) handleLine(line string) {
select {
case t.Attempts <- AttemptEvent{SeenAt: time.Now(), ShareDiff: d, RawLine: line}:
default:
- // Best-effort metric — dropping is fine.
+ // Best-effort metric, dropping is fine.
}
return
}
@@ -484,7 +484,7 @@ func FindBestShareHash(path string) (float64, string) {
// of any length can never abort the scan: an oversized line is passed to
// fn truncated to the buffer size and its remainder is discarded. A
// months-old production log with one pathological line must not silently
-// stop a backfill scan partway through — that failure mode is invisible
+// stop a backfill scan partway through, that failure mode is invisible
// and permanent.
func scanLines(f *os.File, fn func(line string)) error {
r := bufio.NewReaderSize(f, 256*1024)
@@ -513,7 +513,7 @@ var logStampRE = regexp.MustCompile(`^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})`)
// by an "Accepted client" line with the same hash. Used as a one-time
// backfill when kamado-api holds no persisted record but the log
// already contains some (fresh database, or PoW events dropped under
-// load) — without it the PoW inspector would sit empty until a share
+// load), without it the PoW inspector would sit empty until a share
// beat ckpool's in-process best, which after a long uptime can take
// arbitrarily long. The returned time is parsed from the log line's
// timestamp; zero when unparseable. Returns a nil record if no confirmed
diff --git a/api/internal/logmon/tailer_test.go b/api/internal/logmon/tailer_test.go
index 394db6d..5526310 100644
--- a/api/internal/logmon/tailer_test.go
+++ b/api/internal/logmon/tailer_test.go
@@ -77,7 +77,7 @@ func TestHandleLine_DiffResetAfterBlock(t *testing.T) {
tl := New("/unused", discardLog())
tl.handleLine("Possible block solve diff 16384 !\n")
tl.handleLine("Solved and confirmed block 100\n")
- // Second block — no preceding attempt line; diff must NOT carry over.
+ // Second block, no preceding attempt line; diff must NOT carry over.
tl.handleLine("Solved and confirmed block 101\n")
drain := func() BlockEvent {
@@ -217,7 +217,7 @@ func TestFindBestPowData(t *testing.T) {
// Production ckpool logs have carried bytes after the JSON's closing
// brace (observed in the field: an end-of-line-anchored regex matched 0
// of 54 real PoW lines while synthetic lines passed). The parser must
-// decode the JSON value and ignore ANY trailing bytes — NULs, \r,
+// decode the JSON value and ignore ANY trailing bytes, NULs, \r,
// or an interleaved second message on the same line.
func TestHandleLine_PowDataTrailingGarbage(t *testing.T) {
suffixes := []string{
@@ -240,7 +240,7 @@ func TestHandleLine_PowDataTrailingGarbage(t *testing.T) {
}
// A pathologically long line earlier in the log must not abort the scan
-// before it reaches the PoW records — bufio.Scanner did exactly that,
+// before it reaches the PoW records, bufio.Scanner did exactly that,
// silently, which left the backfill permanently empty on real logs.
func TestFindBestPowData_SurvivesOversizedLine(t *testing.T) {
path := tempLog(t)
diff --git a/api/internal/state/aggregator.go b/api/internal/state/aggregator.go
index e558a15..8598aa2 100644
--- a/api/internal/state/aggregator.go
+++ b/api/internal/state/aggregator.go
@@ -37,7 +37,7 @@ type BestSharePow struct {
// StratumServer describes one entry in ckpool's serverurl[] array. ckpool
// tags every client with the index of the bind it arrived on, but the index
-// alone doesn't say what that bind *is* — that depends on how the deployment
+// alone doesn't say what that bind *is*, that depends on how the deployment
// rendered ckpool.conf. Declaring the array lets the dashboard report the
// actual transport (plaintext, TLS with which certificate) instead of
// hardcoding a bind order.
@@ -77,7 +77,7 @@ type Snapshot struct {
// Full PoW reproduction data (raw block header, coinbase, merkle
// branches) for the best share seen since ckpool patch 0008 was
// deployed. May describe a lower-diff share than BestDiff when the
- // all-time best predates the patch — the UI compares hashes and
+ // all-time best predates the patch, the UI compares hashes and
// shows a disclaimer in that case.
BestSharePow *BestSharePow `json:"best_share_pow,omitempty"`
// Last best-diff value acknowledged by the user via the UI.
@@ -129,7 +129,7 @@ type Snapshot struct {
// Counts of share-submit attempts ("Possible/Submitting block solve"
// log lines) and confirmed solves ("Solved and confirmed block").
// A growing gap means bitcoind is rejecting our submissions or
- // dropping the RPC — surface it in the UI as an alert.
+ // dropping the RPC, surface it in the UI as an alert.
BlockSubmitAttempts int64 `json:"block_submit_attempts"`
BlockSubmitsConfirmed int64 `json:"block_submits_confirmed"`
@@ -186,7 +186,7 @@ const (
// kvCumulativeWork is versioned: "cumulative_shares" (v1) accidentally
// summed pool.shares (raw per-share count), which is meaningless for
- // hashrate math. "cumulative_work" (v2) sums pool.accepted — the
+ // hashrate math. "cumulative_work" (v2) sums pool.accepted, the
// diff-1-normalized work, so cumulative_work * 2^32 is real hashes.
// The old key is orphaned in the kv table on upgrade; harmless.
kvCumulativeWork = "cumulative_work"
@@ -264,14 +264,14 @@ type Aggregator struct {
// Cumulative pool work in diff-1-normalized shares, surviving
// ckpool restarts. Maintained by re-reading pool.Shares each
- // refresh and integrating only the positive delta — a decrease
+ // refresh and integrating only the positive delta, a decrease
// means ckpool restarted and its counter reset to 0, so we reset
// our delta baseline without losing the accumulated total.
//
// hasPoolSharesBaseline guards against double-counting on api
// restart: cumulativeShares is loaded from disk, and the first
// post-load observation of pool.Shares only establishes the
- // baseline — the current ckpool counter was already accounted for
+ // baseline, the current ckpool counter was already accounted for
// before we crashed.
cumulativeShares float64
lastPoolShares float64
@@ -395,7 +395,7 @@ type Aggregator struct {
loadOnce sync.Once
// statsLoaded reports that the alltime share statistics have been
// read back from the store. Persisting before that would write the
- // zero-valued in-memory counters over the accumulated history — the
+ // zero-valued in-memory counters over the accumulated history, the
// first share event triggers a save, so this is the difference
// between a restart and permanent data loss.
statsLoaded bool
@@ -419,7 +419,7 @@ func New(ck *ckpool.Client, rpc *bitcoind.RPC, interval time.Duration, log *slog
// Run calls it, but callers should invoke it directly before starting
// any ingest goroutine: those goroutines mutate the same counters and
// persist them on their first event, so a restore still in flight would
-// be overwritten by — and then read back as — near-empty state. Safe to
+// be overwritten by, and then read back as, near-empty state. Safe to
// call repeatedly; only the first call does the work.
func (a *Aggregator) LoadPersisted() {
a.loadOnce.Do(func() {
@@ -429,7 +429,7 @@ func (a *Aggregator) LoadPersisted() {
}
// Ready returns a channel that's closed once the aggregator has
-// completed its first refresh — used by main to delay HTTP serving
+// completed its first refresh, used by main to delay HTTP serving
// until /api/snapshot reflects real state instead of zeros.
func (a *Aggregator) Ready() <-chan struct{} {
return a.ready
@@ -580,7 +580,7 @@ func (a *Aggregator) refresh(ctx context.Context) {
// Measuring to wall-clock time instead adds the partial, in-progress
// interval to the elapsed span without adding an interval to divide
// it by. That skews the estimate toward "difficulty will fall" by
- // however long it has been since the last block — which averages a
+ // however long it has been since the last block, which averages a
// full 600s and, a handful of blocks into an epoch, is most of the
// sample. It washes out only once enough blocks accumulate to dwarf
// it, which is why the estimate used to settle down around mid-epoch.
@@ -651,12 +651,12 @@ func (a *Aggregator) refresh(ctx context.Context) {
// --- cumulative work tracking ---
// Integrate only positive deltas on pool.Accepted, which is ckpool's
- // accounted_diff_shares — the sum of each accepted share's
+ // accounted_diff_shares, the sum of each accepted share's
// difficulty, in diff-1-normalized units. pool.Shares is the raw
// per-share count and is useless for hashrate math. A decrease
// means ckpool restarted (or its state reset); we reset the
// baseline without touching the accumulated total. The first
- // observation after load only establishes the baseline — the
+ // observation after load only establishes the baseline, the
// current counter was already integrated before we shut down.
if next.Pool != nil {
cur := float64(next.Pool.Accepted)
@@ -687,7 +687,7 @@ func (a *Aggregator) refresh(ctx context.Context) {
if curAcc >= a.lastPoolAcceptedRaw {
a.allTimeAccepted += curAcc - a.lastPoolAcceptedRaw
} else {
- // ckpool restarted — reset session share stats.
+ // ckpool restarted, reset session share stats.
a.resetSessionShareStats()
}
if curRej >= a.lastPoolRejectedRaw {
@@ -866,8 +866,8 @@ func estimateRetargetPercent(inEpoch int64, elapsed float64) float64 {
return 0
}
// Consensus budgets retargetInterval*600 seconds for an epoch but
- // spans them across retargetInterval-1 inter-block intervals — the
- // long-standing off-by-one — so on-target pace is fractionally above
+ // spans them across retargetInterval-1 inter-block intervals, the
+ // long-standing off-by-one, so on-target pace is fractionally above
// 600s per block, and a perfectly on-time epoch retargets by ~0%
// rather than a hair negative.
targetPerInterval := float64(retargetInterval*targetBlockSeconds) / float64(retargetInterval-1)
@@ -1000,7 +1000,7 @@ func (a *Aggregator) IngestShareEvents(ctx context.Context, events <-chan logmon
// stats that were lost or truncated: the log is the only other record of
// per-share difficulty, and ckpool keeps its full history there.
//
-// Deliberately never automatic — the scan walks every line of a log that
+// Deliberately never automatic, the scan walks every line of a log that
// can hold millions, and it was a long startup scan that made the race
// this repairs so easy to hit in the first place.
func (a *Aggregator) RebuildShareStatsFromLog() (adopted bool, scanned int64, err error) {
@@ -1085,7 +1085,7 @@ func (a *Aggregator) persistShareStats() {
// loadPersistedState restores cumulative work and hashrate history from
// the store so they survive process restarts. Safe to call with a nil
-// Store — becomes a no-op.
+// Store, becomes a no-op.
func (a *Aggregator) loadPersistedState() {
if a.Store == nil {
return
@@ -1173,7 +1173,7 @@ func (a *Aggregator) loadPersistedState() {
if logDiff, h := logmon.FindBestShareHash(a.LogFilePath); h != "" {
a.mu.Lock()
a.bestShareHash = h
- // The log may contain the true best — adopt it if higher.
+ // The log may contain the true best, adopt it if higher.
if logDiff > a.bestDiff {
a.bestDiff = logDiff
if a.Store != nil {
diff --git a/api/internal/state/blocks.go b/api/internal/state/blocks.go
index 1ea5d62..494515d 100644
--- a/api/internal/state/blocks.go
+++ b/api/internal/state/blocks.go
@@ -168,7 +168,7 @@ func (a *Aggregator) IngestBlockEvents(ctx context.Context, events <-chan logmon
// Best-effort enrich with hash + coinbase reward + miner via bitcoind.
// We look up the hash from height, then fetch the full block
// (verbosity 2) to sum the coinbase outputs and extract the
- // payout address. Both are fire-and-forget — if bitcoind is
+ // payout address. Both are fire-and-forget, if bitcoind is
// down we still record the block with whatever we have.
if a.RPC != nil {
lookupCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
@@ -200,7 +200,7 @@ func (a *Aggregator) IngestBlockEvents(ctx context.Context, events <-chan logmon
if err != nil {
a.Log.Warn("block persist failed", "height", rec.Height, "err", err)
} else if !inserted {
- a.Log.Warn("block at this height already recorded — duplicate or pre-existing entry, not counting as new",
+ a.Log.Warn("block at this height already recorded, duplicate or pre-existing entry, not counting as new",
"height", rec.Height, "hash", rec.Hash)
isNew = false
}
@@ -257,7 +257,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
// We need the current chain for both enrichment and reorg detection.
// If the first refresh hasn't completed yet, currentChain is "" and
- // any chain comparison would be meaningless — skip until the next tick.
+ // any chain comparison would be meaningless, skip until the next tick.
a.mu.RLock()
currentChain := ""
if a.snap.Chain != nil {
@@ -269,7 +269,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
}
// Pass 1: enrichment. Fetch hash + reward for any missing-data rows.
- // Skip blocks from a different chain — enriching a testnet block
+ // Skip blocks from a different chain, enriching a testnet block
// against mainnet RPC would overwrite its hash with the wrong value.
missing, err := a.Store.BlocksNeedingEnrichment(since)
if err != nil {
@@ -350,10 +350,10 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
canonical, herr := a.RPC.GetBlockHash(lookupCtx, b.Height)
cancel()
if herr != nil {
- continue // RPC transient error — retry next sweep
+ continue // RPC transient error, retry next sweep
}
if canonical == b.Hash {
- continue // matches the canonical chain — all good
+ continue // matches the canonical chain, all good
}
// Hash mismatch. Determine whether it's a real reorg or a
// cross-network block by checking if our hash exists on this chain.
@@ -391,7 +391,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
// Pass 3: un-orphan blocks that were previously false-positived.
// This covers two cases:
// a) Blocks with Chain != currentChain that got orphaned before this
- // fix was deployed — un-orphan them now.
+ // fix was deployed, un-orphan them now.
// b) Same-chain blocks whose hash now matches the canonical chain
// (e.g. the "reorg" reverted before we checked again).
fixedAny := false
@@ -403,7 +403,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
continue
}
if b.Chain != "" && b.Chain != currentChain {
- // Cross-network orphan from before the fix — un-orphan.
+ // Cross-network orphan from before the fix, un-orphan.
if err := a.Store.UnmarkOrphaned(b.Height); err != nil {
a.Log.Warn("reconcile: unmark cross-network orphan failed", "height", b.Height, "err", err)
continue
@@ -418,7 +418,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
_, berr := a.RPC.GetBlock(lookupCtx, b.Hash)
cancel()
if berr != nil {
- // Block doesn't exist on this chain — cross-network.
+ // Block doesn't exist on this chain, cross-network.
otherChain := b.Chain
if otherChain == "" || otherChain == currentChain {
otherChain = inferOtherChain(currentChain)
@@ -432,7 +432,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
fixedAny = true
continue
}
- // Block exists on this chain — check if it's canonical again.
+ // Block exists on this chain, check if it's canonical again.
lookupCtx2, cancel2 := context.WithTimeout(ctx, 3*time.Second)
canonical, herr := a.RPC.GetBlockHash(lookupCtx2, b.Height)
cancel2()
@@ -444,7 +444,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
a.Log.Warn("reconcile: unmark reverted-reorg failed", "height", b.Height, "err", err)
continue
}
- a.Log.Info("reconcile: cleared orphan — hash matches canonical again",
+ a.Log.Info("reconcile: cleared orphan, hash matches canonical again",
"height", b.Height, "hash", b.Hash)
fixedAny = true
}
@@ -452,9 +452,9 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
// Pass 4: stamp chain on legacy/mis-stamped blocks that have a hash.
// Handles Chain="" (never stamped), "other" (old fallback), and "test"
- // (wrong identifier — testnet4 reports as "testnet4", not "test").
+ // (wrong identifier, testnet4 reports as "testnet4", not "test").
// Uses GetBlock(hash) to determine if the block belongs to the current
- // network or a different one. No lookback restriction — this is a
+ // network or a different one. No lookback restriction, this is a
// one-time migration for pre-existing rows.
for _, b := range recent {
if (b.Chain != "" && b.Chain != "other" && b.Chain != "test") || b.Hash == "" {
@@ -464,7 +464,7 @@ func (a *Aggregator) reconcileOnce(ctx context.Context) {
_, berr := a.RPC.GetBlock(lookupCtx, b.Hash)
cancel()
if berr != nil {
- // Block not found on this network — infer the other chain.
+ // Block not found on this network, infer the other chain.
inferredChain := inferOtherChain(currentChain)
if err := a.Store.StampChain(b.Height, inferredChain); err != nil {
a.Log.Warn("reconcile: stamp legacy block failed", "height", b.Height, "err", err)
@@ -545,7 +545,7 @@ func (a *Aggregator) appendBlock(rec BlockRecord) Snapshot {
}
// loadPersistedBlocks seeds a.blocks from the store so history survives
-// restarts. Safe to call with a nil Store — becomes a no-op.
+// restarts. Safe to call with a nil Store, becomes a no-op.
func (a *Aggregator) loadPersistedBlocks() {
if a.Store == nil {
return
diff --git a/api/internal/state/blocks_test.go b/api/internal/state/blocks_test.go
index fdb8eb9..32da791 100644
--- a/api/internal/state/blocks_test.go
+++ b/api/internal/state/blocks_test.go
@@ -90,7 +90,7 @@ func setChain(agg *Aggregator, chain string) {
// ---- reconcileOnce tests ----
// TestReconcileOnce_ChainFilter verifies that a block recorded on testnet
-// is NOT orphaned when the node is now on mainnet — even if the canonical
+// is NOT orphaned when the node is now on mainnet, even if the canonical
// hash at that height on mainnet is different.
func TestReconcileOnce_ChainFilter_NoFalseOrphan(t *testing.T) {
st := openTempStore(t)
@@ -109,7 +109,7 @@ func TestReconcileOnce_ChainFilter_NoFalseOrphan(t *testing.T) {
}
// Mock RPC: getblockhash returns a different mainnet hash; getblock
- // for our testnet hash returns "Block not found" — it doesn't exist
+ // for our testnet hash returns "Block not found", it doesn't exist
// on mainnet at all, confirming it's cross-network not a reorg.
rpc := mockRPC(t, func(method string) (any, *rpcErrResp) {
switch method {
@@ -132,7 +132,7 @@ func TestReconcileOnce_ChainFilter_NoFalseOrphan(t *testing.T) {
}
for _, b := range blocks {
if b.Height == 100 && !b.OrphanedAt.IsZero() {
- t.Error("testnet block was falsely orphaned after switching to mainnet — chain filter broken")
+ t.Error("testnet block was falsely orphaned after switching to mainnet, chain filter broken")
}
}
}
@@ -156,7 +156,7 @@ func TestReconcileOnce_GenuineReorg(t *testing.T) {
}
// getblockhash returns a different canonical hash (reorg happened).
- // getblock for our hash SUCCEEDS — the block still exists in bitcoind's
+ // getblock for our hash SUCCEEDS, the block still exists in bitcoind's
// block store after a reorg, it's just no longer on the active chain.
// This distinguishes a real reorg from a cross-network block.
rpc := mockRPC(t, func(method string) (any, *rpcErrResp) {
@@ -223,7 +223,7 @@ func TestReconcileOnce_RPCError_NoOrphan(t *testing.T) {
blocks, _ := st.Recent(10)
for _, b := range blocks {
if b.Height == 300 && !b.OrphanedAt.IsZero() {
- t.Error("block was orphaned on RPC error — should only orphan on definitive hash mismatch")
+ t.Error("block was orphaned on RPC error, should only orphan on definitive hash mismatch")
}
}
}
@@ -240,7 +240,7 @@ func TestReconcileOnce_LegacyBlock_StillChecked(t *testing.T) {
RewardBT: 3.125,
FoundAt: time.Now(),
Source: "test",
- Chain: "", // legacy — no chain recorded
+ Chain: "", // legacy, no chain recorded
})
// Canonical hash differs AND getblock succeeds (block exists on this
@@ -250,7 +250,7 @@ func TestReconcileOnce_LegacyBlock_StillChecked(t *testing.T) {
case "getblockhash":
return "different_canonical", nil
case "getblock":
- // Block exists in this chain's store — genuine reorg
+ // Block exists in this chain's store, genuine reorg
return map[string]any{"hash": "legacy_hash", "height": 400, "confirmations": -1}, nil
}
return nil, nil
@@ -281,11 +281,11 @@ func TestReconcileOnce_LegacyBlock_CrossNetwork(t *testing.T) {
RewardBT: 50,
FoundAt: time.Now(),
Source: "test",
- Chain: "", // legacy — no chain recorded
+ Chain: "", // legacy, no chain recorded
})
// Canonical hash differs AND getblock fails (block doesn't exist on
- // this network at all). Should NOT orphan — should stamp as "other".
+ // this network at all). Should NOT orphan, should stamp as "other".
rpc := mockRPC(t, func(method string) (any, *rpcErrResp) {
switch method {
case "getblockhash":
@@ -436,6 +436,6 @@ func TestIngestBlockEvents_ChainStamping(t *testing.T) {
t.Fatal("block not found in store")
}
if blocks[0].Chain != "test" {
- t.Errorf("Chain = %q, want test — chain stamping broken", blocks[0].Chain)
+ t.Errorf("Chain = %q, want test, chain stamping broken", blocks[0].Chain)
}
}
diff --git a/api/internal/state/pow_e2e_test.go b/api/internal/state/pow_e2e_test.go
index e7ffb66..bd8946c 100644
--- a/api/internal/state/pow_e2e_test.go
+++ b/api/internal/state/pow_e2e_test.go
@@ -75,7 +75,7 @@ func TestPowEndToEnd_LogToSnapshot(t *testing.T) {
}
// The snapshot must expose it under best_share_pow with the flattened
- // PowData fields — this is the contract the UI reads.
+ // PowData fields, this is the contract the UI reads.
snap := Snapshot{BestSharePow: func() *BestSharePow {
a.mu.RLock()
defer a.mu.RUnlock()
diff --git a/api/internal/state/pow_test.go b/api/internal/state/pow_test.go
index bad5295..13af25c 100644
--- a/api/internal/state/pow_test.go
+++ b/api/internal/state/pow_test.go
@@ -103,8 +103,8 @@ func TestPowPairing_UnmatchedIsNotPromoted(t *testing.T) {
// The all-time best share may hugely outrank anything recent (found
// before header capture existed). The PoW record must still populate
-// from the first confirmed share — it tracks "best since the feature
-// went live", never waiting for the all-time best to be beaten — while
+// from the first confirmed share, it tracks "best since the feature
+// went live", never waiting for the all-time best to be beaten, while
// leaving the all-time bestDiff/bestShareHash untouched.
func TestPowPairing_BelowAllTimeBestStillPopulates(t *testing.T) {
a := New(nil, nil, time.Minute, discardLog())
@@ -210,7 +210,7 @@ func TestPowPairing_LowerDiffDoesNotReplace(t *testing.T) {
// statistics: the ingest goroutine runs concurrently with the restore,
// and its first event triggers a persist. If that persist is allowed to
// run before the counters are loaded, it writes the zero-valued
-// in-memory histogram over the stored history — which the restore then
+// in-memory histogram over the stored history, which the restore then
// reads back, making the loss permanent.
func TestShareStats_NotPersistedBeforeLoad(t *testing.T) {
s := openTempStore(t)
diff --git a/api/internal/state/retarget_test.go b/api/internal/state/retarget_test.go
index 3317a2c..da3602d 100644
--- a/api/internal/state/retarget_test.go
+++ b/api/internal/state/retarget_test.go
@@ -8,7 +8,7 @@ import (
// The estimate must be stable from the very first blocks of an epoch:
// three blocks on target pace should read the same ~0% as a thousand.
// The old wall-clock measurement made early readings swing wildly
-// because the time since the last block — 600s on average — was counted
+// because the time since the last block, 600s on average, was counted
// into the elapsed span without a matching interval to divide it by.
func TestEstimateRetargetPercent_StableEarlyInEpoch(t *testing.T) {
// Exactly on-pace: each interval takes the consensus target.
@@ -82,7 +82,7 @@ func TestEstimateRetargetPercent_WallClockBiasWasWorstEarly(t *testing.T) {
t.Errorf("mid-epoch wall-clock error = %+.2f%%, expected it to have washed out", mid)
}
// And with block timestamps, that same on-pace epoch reads ~0 at
- // both points — which is the actual fix.
+ // both points, which is the actual fix.
if got := estimateRetargetPercent(3, 3*onPace); math.Abs(got) > 0.01 {
t.Errorf("block-timestamp measurement at 3 blocks = %+.4f%%, want ~0%%", got)
}
diff --git a/api/internal/store/blocks.go b/api/internal/store/blocks.go
index 3659495..3e4d651 100644
--- a/api/internal/store/blocks.go
+++ b/api/internal/store/blocks.go
@@ -126,7 +126,7 @@ func (s *BlockStore) Close() error {
return s.db.Close()
}
-// InsertBlock is idempotent — duplicate heights are ignored so replayed
+// InsertBlock is idempotent, duplicate heights are ignored so replayed
// log events after a restart don't trip the primary key constraint.
// Returns (true, nil) if a new row was actually inserted, (false, nil)
// if the height was already present (replay or duplicate).
@@ -247,7 +247,7 @@ func (s *BlockStore) Recent(limit int) ([]Block, error) {
// BlocksNeedingEnrichment returns blocks whose hash or reward is still
// unset and that were found within the lookback window. Older entries
-// are ignored — if bitcoind couldn't tell us about a day-old block,
+// are ignored, if bitcoind couldn't tell us about a day-old block,
// retrying will keep failing.
func (s *BlockStore) BlocksNeedingEnrichment(since time.Time) ([]Block, error) {
rows, err := s.db.Query(
@@ -300,7 +300,7 @@ func (s *BlockStore) MarkOrphaned(height int64, at time.Time) error {
// UnmarkOrphaned clears the orphaned_at stamp set by MarkOrphaned.
// Used to correct false positives caused by a network switch: a block
-// found on testnet is not "orphaned" when the node switches to mainnet —
+// found on testnet is not "orphaned" when the node switches to mainnet,
// it simply belongs to a different chain and must not be compared against
// mainnet's canonical history.
func (s *BlockStore) UnmarkOrphaned(height int64) error {
@@ -326,7 +326,7 @@ func (s *BlockStore) UnmarkOrphanedStampChain(height int64, chain string) error
// StampChain sets the chain field on a block without touching orphaned_at.
// Used when the reconcile loop determines a block belongs to a different
-// network — the block is not orphaned (a reorg), it just doesn't belong
+// network, the block is not orphaned (a reorg), it just doesn't belong
// to the current chain.
func (s *BlockStore) StampChain(height int64, chain string) error {
_, err := s.db.Exec(
diff --git a/api/internal/store/blocks_test.go b/api/internal/store/blocks_test.go
index 449def2..d2e4079 100644
--- a/api/internal/store/blocks_test.go
+++ b/api/internal/store/blocks_test.go
@@ -201,14 +201,14 @@ func TestMigrate_AddsChainColumn(t *testing.T) {
}
db.Close()
- // Open via our store — migrate() must add the chain column.
+ // Open via our store, migrate() must add the chain column.
s, err := Open(f.Name())
if err != nil {
t.Fatalf("Open after migration: %v", err)
}
defer s.Close()
- // Insert a block with a chain value — would fail if migration didn't add
+ // Insert a block with a chain value, would fail if migration didn't add
// the column.
inserted, err := s.InsertBlock(Block{
Height: 1000,
diff --git a/api/internal/webui/embed.go b/api/internal/webui/embed.go
index e8f0e21..9b52169 100644
--- a/api/internal/webui/embed.go
+++ b/api/internal/webui/embed.go
@@ -9,7 +9,7 @@
// inside the image build.
//
// A placeholder index.html is committed so `go build` succeeds on a
-// fresh checkout without anyone having run `make ui` — it just shows
+// fresh checkout without anyone having run `make ui`, it just shows
// a "UI not built" notice instead of the real dashboard.
package webui
diff --git a/api/internal/zmqmon/zmqmon.go b/api/internal/zmqmon/zmqmon.go
index 41f7507..ef48822 100644
--- a/api/internal/zmqmon/zmqmon.go
+++ b/api/internal/zmqmon/zmqmon.go
@@ -1,7 +1,7 @@
// Package zmqmon subscribes to bitcoind's ZMQ `hashblock` topic and
// emits a lightweight event each time a new chain tip appears.
//
-// This is strictly an "immediate refresh trigger" — it does NOT replace
+// This is strictly an "immediate refresh trigger", it does NOT replace
// the ckpool log tailer, which still handles our own solved-block
// detection. ZMQ just shortens the latency between bitcoind seeing a
// new tip and the Kamado dashboard reflecting it (otherwise we'd wait
@@ -34,7 +34,7 @@ type Monitor struct {
}
// New returns a Monitor with a buffered event channel. Endpoint may be
-// empty, in which case Run exits immediately as a no-op — useful for
+// empty, in which case Run exits immediately as a no-op, useful for
// deployments where ZMQ isn't configured on bitcoind.
func New(endpoint string, log *slog.Logger) *Monitor {
return &Monitor{
diff --git a/ckpool/Dockerfile b/ckpool/Dockerfile
index 435f4e2..a3c7ff2 100644
--- a/ckpool/Dockerfile
+++ b/ckpool/Dockerfile
@@ -2,7 +2,7 @@
# CKPool-solo build stage
# ============================================================================
# Builds a patched ckpool binary from upstream source at a pinned commit.
-# Binaries land in /build/ckpool/src/ — we copy `ckpool` and `ckpmsg` into
+# Binaries land in /build/ckpool/src/, we copy `ckpool` and `ckpmsg` into
# the runtime image. We do NOT `make install` because upstream's
# install-exec-hook calls setcap which fails inside a docker build.
#
diff --git a/ckpool/entrypoint.sh b/ckpool/entrypoint.sh
index 5c7b3c3..d4520b6 100644
--- a/ckpool/entrypoint.sh
+++ b/ckpool/entrypoint.sh
@@ -61,7 +61,7 @@ TEMPLATE=/etc/ckpool/ckpool.conf.template
CONF=/etc/ckpool/ckpool.conf
# Simple placeholder substitution (no envsubst dependency).
-# We intentionally don't use eval/printf tricks — sed is safe and predictable.
+# We intentionally don't use eval/printf tricks, sed is safe and predictable.
sed \
-e "s|\${BITCOIN_RPC_HOST}|${BITCOIN_RPC_HOST}|g" \
-e "s|\${BITCOIN_RPC_PORT}|${BITCOIN_RPC_PORT}|g" \
diff --git a/ckpool/patches/0008-log-best-share-pow-data.patch b/ckpool/patches/0008-log-best-share-pow-data.patch
index f1834b3..ccf08fc 100644
--- a/ckpool/patches/0008-log-best-share-pow-data.patch
+++ b/ckpool/patches/0008-log-best-share-pow-data.patch
@@ -5,15 +5,15 @@ index 7940d4fa..a856b3eb 100644
@@ -5865,6 +5865,65 @@ out_nouserwb:
return wb->coinb2bin;
}
-
+
+/* Kamado (patches/0008): highest share diff for which PoW reproduction
-+ * data has been logged in this process run. Deliberately unlocked — a
++ * data has been logged in this process run. Deliberately unlocked, a
+ * race between share-processing threads can at worst emit a duplicate
+ * or out-of-order log line, which kamado-api deduplicates by diff. */
+static double best_pow_diff;
+
+/* Kamado (patches/0008): log everything needed to independently
-+ * reproduce and verify the proof of work of a best share — the
++ * reproduce and verify the proof of work of a best share, the
+ * serialized 80-byte block header exactly as it was hashed, plus the
+ * assembled coinbase transaction and merkle branch hashes that
+ * reproduce the merkle root committed in that header. kamado-api
@@ -56,7 +56,7 @@ index 7940d4fa..a856b3eb 100644
+ if (likely(s)) {
+ /* Call logmsg directly rather than LOGNOTICE: the LOGMSG
+ * macro splits messages into DEFLOGBUFSIZ (512-byte) chunks,
-+ * each written as a separate timestamped log line — and its
++ * each written as a separate timestamped log line, and its
+ * length arithmetic drops everything past ~3 chunks. This
+ * record is ~2.5KB of JSON that kamado-api must read back as
+ * one intact line. logmsg itself is unbounded. */
@@ -71,13 +71,13 @@ index 7940d4fa..a856b3eb 100644
@@ -5937,6 +5996,19 @@ static double submission_diff(sdata_t *sdata, const stratum_instance_t *client,
/* Calculate the diff of the share here */
ret = diff_from_target(hash);
-
+
+ /* Kamado (patches/0008): if this is the highest-diff share this
+ * process has seen, log full PoW reproduction data for the
+ * dashboard's best-share header inspector. Skip shares that will
+ * be rejected: stale ones, and those outside the ntime roll
+ * window (mirrors the check in parse_submit). Duplicates cannot
-+ * trigger this — an identical share has an equal, not greater,
++ * trigger this, an identical share has an equal, not greater,
+ * diff. */
+ if (unlikely(ret > best_pow_diff && !stale &&
+ ntime32 >= wb->ntime32 && ntime32 <= wb->ntime32 + 7000)) {
@@ -87,4 +87,4 @@ index 7940d4fa..a856b3eb 100644
+
/* Test we haven't solved a block regardless of share status */
test_blocksolve(client, wb, swap, hash, ret, coinbase, cblen, nonce2, nonce, ntime32, version_mask, stale);
-
+
diff --git a/ckpool/patches/0009-resume-client-diff-on-reconnect.patch b/ckpool/patches/0009-resume-client-diff-on-reconnect.patch
index d133d2d..20e4eb5 100644
--- a/ckpool/patches/0009-resume-client-diff-on-reconnect.patch
+++ b/ckpool/patches/0009-resume-client-diff-on-reconnect.patch
@@ -5,7 +5,7 @@ index a856b3eb..fcb9eb94 100644
@@ -5477,6 +5477,69 @@ static void update_solo_client(sdata_t *sdata, workbase_t *wb, const int64_t cli
stratum_add_send(sdata, json_msg, client_id, SM_UPDATE);
}
-
+
+/* Kamado (patches/0009): restore a reconnecting miner's difficulty and
+ * grant the work it already holds a difficulty grace window.
+ *
@@ -75,17 +75,17 @@ index a856b3eb..fcb9eb94 100644
@@ -5558,6 +5621,19 @@ out:
sdata_t *sdata = ckp->sdata;
workbase_t *wb;
-
+
+ /* Kamado (patches/0009): pick up where vardiff left off and
+ * arm the first-job grace window. Called before any lock is
-+ * taken here — it acquires the workbase lock itself. */
++ * taken here, it acquires the workbase lock itself. */
+ resume_client_diff(ckp, sdata, client);
+
+ /* Kamado (patches/0009): mining.set_difficulty must precede
+ * the first mining.notify. Upstream sent the job first, so a
+ * miner started hashing it at whatever difficulty its firmware
+ * defaults to and its early shares came back under the
-+ * difficulty we had assigned — a burst of "high diff" rejects
++ * difficulty we had assigned, a burst of "high diff" rejects
+ * from every miner on every pool restart. */
+ stratum_send_diff(sdata, client);
+
@@ -104,7 +104,7 @@ index a856b3eb..fcb9eb94 100644
@@ -6567,9 +6641,12 @@ static void update_client(const stratum_instance_t *client, const int64_t client
{
sdata_t *sdata = client->sdata;
-
+
+ /* Kamado (patches/0009): difficulty before work, so a client never
+ * starts a job without knowing the difficulty it will be judged
+ * against. Upstream sent the update first. */
@@ -113,5 +113,5 @@ index a856b3eb..fcb9eb94 100644
stratum_send_update(sdata, client_id, true);
- stratum_send_diff(sdata, client);
}
-
+
static json_params_t
diff --git a/ckpool/patches/README.md b/ckpool/patches/README.md
index 888a97d..5f3f991 100644
--- a/ckpool/patches/README.md
+++ b/ckpool/patches/README.md
@@ -25,7 +25,7 @@ Upstream tracks `best_ever` internally in `user_instance_t` / `worker_instance_t
and zeroes `best_diff` on every block solve via `reset_bestshares()`. That
is correct: `bestdiff` is "best share in the current round". But the runtime
socket API (`userinfo()` / `workerinfo()` in `stratifier.c`) only emits
-`bestdiff`, so any consumer that talks to the socket — like `kamado-api` —
+`bestdiff`, so any consumer that talks to the socket, like `kamado-api`,
sees the best share reset to 0 after every block and has no all-time field
to fall back on. The on-disk `users.json` / `workers.json` persistence files
do include `bestever`, but polling those is racy and lags the socket.
@@ -46,7 +46,7 @@ backport, plus several improvements:
| `66db3aa3` | Better vardiff for bursty hashers |
| `130c755d` | Fix unlikely fopen segfault |
| `0bd3d751` | Consistent error field in mining.submit rejections |
-| `988b2687` | Version 1.0 — longstanding stability bump |
+| `988b2687` | Version 1.0, longstanding stability bump |
Kamado therefore starts from a CKPool that is strictly ahead of what Bassin
ships today.
@@ -57,7 +57,7 @@ Use this directory for:
1. Fixes needed before they land upstream (and only after attempting
to submit upstream first).
-2. Kamado-specific behavior changes that would not be accepted upstream —
+2. Kamado-specific behavior changes that would not be accepted upstream,
e.g. tighter integration hooks with `kamado-api`.
3. Temporary workarounds with a clear removal plan, documented in the
patch commit message.
diff --git a/docker-compose.yml b/docker-compose.yml
index 22c0334..22c25ac 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,5 @@
# ----------------------------------------------------------------------------
-# Kamado Pool — development compose
+# Kamado Pool, development compose
#
# Spins up ckpool-solo and kamado-api pointing at a bitcoind. Expects
# bitcoind to be reachable at the host/port set in the env blocks below.
diff --git a/test/regtest_smoke.sh b/test/regtest_smoke.sh
index 5eba73d..e770b54 100755
--- a/test/regtest_smoke.sh
+++ b/test/regtest_smoke.sh
@@ -1,12 +1,12 @@
#!/usr/bin/env bash
-# Regtest smoke test for kamado-api — two phases:
+# Regtest smoke test for kamado-api, two phases:
#
# Phase 1 (always): log-inject path
# Injects a "Solved and confirmed block N" line directly into the ckpool log
# and verifies the full tailer → aggregator → bitcoind RPC enrichment → /api/blocks
# chain. Quick, no ckpool binary needed.
#
-# Phase 2 (auto): full stratum path — the complete revenue-critical path
+# Phase 2 (auto): full stratum path, the complete revenue-critical path
# A Python stratum miner connects to ckpool, mines a real diff-1 SHA256d hash
# using multiprocessing, submits the winning share, ckpool calls bitcoind
# submitblock, logs "Solved and confirmed block N+1", and the kamado-api
@@ -73,7 +73,7 @@ fi
[[ -n "${WORK_DIR:-}" ]] || WORK_DIR=$(mktemp -d)
-# Locate or build ckpool — required for Phase 2.
+# Locate or build ckpool, required for Phase 2.
# Priority: CKPOOL_BIN env var → PATH → cached build → build from source.
CKPOOL_AVAILABLE=false
CKPOOL_DIR="$SCRIPT_DIR/../ckpool"
@@ -119,12 +119,12 @@ elif command -v gcc autoconf automake libtool pkg-config git >/dev/null 2>&1 \
CKPOOL_AVAILABLE=true
log "ckpool: build complete → $CKPOOL_BIN"
else
- log "ckpool not found and build prerequisites missing — Phase 2 will be skipped"
+ log "ckpool not found and build prerequisites missing, Phase 2 will be skipped"
log " Missing one or more of: gcc, autoconf, automake, libtool, pkg-config, git, libzmq-dev"
log " Or set CKPOOL_BIN=/path/to/ckpool to skip the build"
fi
-# Write the Python stratum miner — no build step needed; python3 is in PATH (checked above).
+# Write the Python stratum miner, no build step needed; python3 is in PATH (checked above).
# The miner uses hashlib SHA256 midstate optimization + multiprocessing so it scales linearly
# with CPU count. Diff-1 requires on average 2^32 ≈ 4.3B hashes; at ~1.5 MH/s per core this
# takes ~43 minutes on a single core but ~3 minutes on 16 cores.
@@ -268,7 +268,7 @@ def main():
while True:
# Snapshot the job and en2 used to build this round's header.
- # These must match exactly what we submit to ckpool — if job is
+ # These must match exactly what we submit to ckpool, if job is
# updated mid-round we still submit against the header we actually mined.
cur_job = job
cur_en2 = en2_int.to_bytes(en2_size, 'little')
@@ -427,7 +427,7 @@ touch "$CKPOOL_LOG"
# Kill any stale processes from a previous run that bypassed cleanup (e.g.
# killed with SIGKILL). Without this they hold the ports, the new processes
# fail to bind, but the readiness health-checks see the old processes
-# and report "ready" — causing very confusing test failures.
+# and report "ready", causing very confusing test failures.
log "clearing test ports from any prior stale run…"
for _port in $BTC_RPC_PORT $KAMADO_PORT $STRATUM_PORT; do
fuser -k -TERM "${_port}/tcp" 2>/dev/null || true
@@ -470,7 +470,7 @@ for i in $(seq 1 30); do
done
$ready || die "bitcoind did not become ready within 30s (see $WORK_DIR/bitcoind.log)"
kill -0 "$BITCOIN_PID" 2>/dev/null \
- || die "bitcoind (pid $BITCOIN_PID) is not running — another process may have answered on port $BTC_RPC_PORT"
+ || die "bitcoind (pid $BITCOIN_PID) is not running, another process may have answered on port $BTC_RPC_PORT"
log "bitcoind ready (pid $BITCOIN_PID)"
# Create a descriptor wallet and mine enough blocks for coinbase maturity.
@@ -511,7 +511,7 @@ for i in $(seq 1 30); do
done
$ready || die "kamado-api did not become ready within 30s (see $WORK_DIR/kamado.log)"
kill -0 "$KAMADO_PID" 2>/dev/null \
- || die "kamado-api (pid $KAMADO_PID) is not running — port $KAMADO_PORT may already be in use"
+ || die "kamado-api (pid $KAMADO_PID) is not running, port $KAMADO_PORT may already be in use"
log "kamado-api ready (pid $KAMADO_PID)"
# ============================================================
@@ -619,7 +619,7 @@ for i in $(seq 1 30); do
done
$ready || die "ckpool stratum port did not open within 30s (see $WORK_DIR/ckpool-stderr.log)"
kill -0 "$CKPOOL_PID" 2>/dev/null \
- || die "ckpool (pid $CKPOOL_PID) is not running — check $WORK_DIR/ckpool-stderr.log"
+ || die "ckpool (pid $CKPOOL_PID) is not running, check $WORK_DIR/ckpool-stderr.log"
log "ckpool ready (pid $CKPOOL_PID)"
# ---- run Python stratum miner in background ---------------------------------
diff --git a/ui/package.json b/ui/package.json
index 3481fde..2d76ac6 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"type": "module",
- "description": "Kamado Pool dashboard — Svelte 5 + Vite, talks to kamado-api over REST and WebSocket.",
+ "description": "Kamado Pool dashboard, Svelte 5 + Vite, talks to kamado-api over REST and WebSocket.",
"scripts": {
"dev": "vite",
"build": "vite build",
diff --git a/ui/src/format.ts b/ui/src/format.ts
index caa82d6..c5e6f09 100644
--- a/ui/src/format.ts
+++ b/ui/src/format.ts
@@ -17,7 +17,7 @@ export function formatHashrate(hs: number): string {
return `${v.toFixed(digits)} ${HASHRATE_UNITS[i]}`;
}
-// Difficulty is a share-diff value — scale with k/M/G/T suffixes.
+// Difficulty is a share-diff value, scale with k/M/G/T suffixes.
export function formatDifficulty(d: number): string {
if (!d || d <= 0 || !isFinite(d)) return "0";
const units = ["", "k", "M", "G", "T", "P"];
@@ -31,7 +31,7 @@ export function formatDifficulty(d: number): string {
return `${v.toFixed(digits)}${units[i]}`;
}
-// Cumulative hashes — scale up to ZH / YH since pools accumulate fast.
+// Cumulative hashes, scale up to ZH / YH since pools accumulate fast.
// Input is plain hashes (a diff-1 share is 2^32 hashes).
export function formatWork(hashes: number): string {
if (!hashes || hashes <= 0 || !isFinite(hashes)) return "0 H";
@@ -46,7 +46,7 @@ export function formatWork(hashes: number): string {
}
export function formatUptime(seconds: number): string {
- if (!seconds || seconds < 0) return "—";
+ if (!seconds || seconds < 0) return "-";
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
@@ -87,7 +87,7 @@ export function btcAddressOf(workername: string): string {
// Build the explorer base URL for the active network. If the user
// configured a custom mempool instance via StartOS, we trust that
-// verbatim and don't append a /testnet4 / /signet path — the user's
+// verbatim and don't append a /testnet4 / /signet path, the user's
// instance presumably runs on a single network already. Otherwise
// fall back to mempool.space with the appropriate network prefix.
export function explorerBaseFor(
@@ -105,8 +105,8 @@ export function explorerBaseFor(
// Resolve how a miner is connected from its ckpool serverurl[] index.
//
// The deployment declares the bind array via STRATUM_SERVERS (the StartOS
-// wrapper renders one entry per bind it configures). When it hasn't — the
-// dev compose stack, or an older wrapper — fall back to the historical
+// wrapper renders one entry per bind it configures). When it hasn't, the
+// dev compose stack, or an older wrapper, fall back to the historical
// layout, where index 0 is the plaintext bind and anything else is the
// loopback bind that stunnel forwards TLS traffic to.
export function connectionOf(
@@ -124,13 +124,13 @@ export function connectionOf(
// Detect common Bitcoin mining hardware from the stratum useragent
// string. This is a best-effort heuristic; unknown agents fall back
-// to a stripped version of the raw string. Order matters — check
+// to a stripped version of the raw string. Order matters, check
// more specific tokens before generic ones.
export function detectHardware(ua: string): string {
if (!ua) return "unknown";
const s = ua.toLowerCase();
const rules: Array<[RegExp, string]> = [
- // Bitaxe and Nerd-family open-source ASICs. Order matters — check
+ // Bitaxe and Nerd-family open-source ASICs. Order matters, check
// narrower variants before the broader Nerd* / Bitaxe roots.
[/bitaxehex/, "Bitaxe Hex"],
[/bitaxe/, "Bitaxe"],
diff --git a/ui/src/global.d.ts b/ui/src/global.d.ts
index 4078e74..2713500 100644
--- a/ui/src/global.d.ts
+++ b/ui/src/global.d.ts
@@ -1,2 +1,34 @@
///
///
+
+// `qrcode` ships no types and has no @types package. Declare the surface we
+// actually use, the donation QR in App.svelte and the proof QR on the share
+// card, rather than leaving both call sites as implicit `any`.
+declare module "qrcode" {
+ export type QRCodeErrorCorrectionLevel = "L" | "M" | "Q" | "H";
+
+ export interface QRCodeRenderOptions {
+ width?: number;
+ margin?: number;
+ scale?: number;
+ errorCorrectionLevel?: QRCodeErrorCorrectionLevel;
+ color?: { dark?: string; light?: string };
+ }
+
+ export function toCanvas(
+ canvas: HTMLCanvasElement,
+ text: string,
+ options?: QRCodeRenderOptions,
+ ): Promise;
+
+ export function toDataURL(
+ text: string,
+ options?: QRCodeRenderOptions,
+ ): Promise;
+
+ const QRCode: {
+ toCanvas: typeof toCanvas;
+ toDataURL: typeof toDataURL;
+ };
+ export default QRCode;
+}
diff --git a/ui/src/lib/AcceleratorPage.svelte b/ui/src/lib/AcceleratorPage.svelte
index fdf9892..ef7b291 100644
--- a/ui/src/lib/AcceleratorPage.svelte
+++ b/ui/src/lib/AcceleratorPage.svelte
@@ -69,7 +69,7 @@
} else if (feeLostSats > 0) {
success = `Boosted from ${from} to ${to} sat/vB. Revenue impact: -${feeLostSats.toLocaleString()} sats (${(feeLostSats / 1e8).toFixed(8)} BTC) per block mined.`;
} else {
- success = `Boosted from ${from} to ${to} sat/vB. No revenue impact — the transaction was already in the template or the mempool fits in one block.`;
+ success = `Boosted from ${from} to ${to} sat/vB. No revenue impact, the transaction was already in the template or the mempool fits in one block.`;
}
txid = "";
feerateInput = "";
@@ -170,13 +170,13 @@
Revenue impact: The server inspects the current block template to find
- the marginal transaction — the lowest fee-rate tx that would be displaced by the
+ the marginal transaction, the lowest fee-rate tx that would be displaced by the
boosted one. Its fee is the revenue you sacrifice per block mined, shown after each boost.
Your pool: {formatHashrate(poolHashrate)} /
{formatHashrate(networkHashrate)} network
- ({poolShare > 0 ? `${(poolShare * 100).toFixed(6)}%` : "—"} of blocks).
+ ({poolShare > 0 ? `${(poolShare * 100).toFixed(6)}%` : "-"} of blocks).
@@ -202,7 +202,7 @@
-
+
To mine a block, you must find a hash whose numeric value
- is less than the network's target. It's not just about leading zeros —
+ is less than the network's target. It's not just about leading zeros,
the entire hash must be smaller than the target. Think of it like a lottery:
you need to roll a number below a threshold, and the threshold gets lower as
difficulty rises.
@@ -244,7 +244,7 @@
The current network difficulty of {formatDifficulty(netDiff)}
requires a hash with at least {networkZeroBits} leading zero bits.
{#if diffRatio <= 1}
- Your share meets the network target — this would be a valid block!
+ Your share meets the network target, this would be a valid block!
{:else}
The network target is {diffRatio.toFixed(1)}x harder than your
best share.
@@ -341,7 +341,7 @@
{#if networkZeroBits > shareZeroBits}
The gap of {networkZeroBits - shareZeroBits} bits means the target is roughly
2{networkZeroBits - shareZeroBits} ≈ {Math.round(Math.pow(2, networkZeroBits - shareZeroBits)).toLocaleString()}x
- harder — matching the {diffRatio.toFixed(1)}x difficulty ratio.
+ harder, matching the {diffRatio.toFixed(1)}x difficulty ratio.
{/if}
diff --git a/ui/src/lib/BestShares.svelte b/ui/src/lib/BestShares.svelte
index 81419d9..20e00eb 100644
--- a/ui/src/lib/BestShares.svelte
+++ b/ui/src/lib/BestShares.svelte
@@ -8,7 +8,7 @@
// disconnect (the instance is freed) and on pool restart (in-memory
// state, not persisted). worker.best_diff is a separate persistent
// counter that survives both events, so we deliberately don't fall
- // back to it — that fallback was the bug. Offline workers have no
+ // back to it, that fallback was the bug. Offline workers have no
// current session, so their session-best is 0.
const rows = $derived.by(() => {
const ws = snap.data?.workers ?? [];
diff --git a/ui/src/lib/BlockFoundAnimation.svelte b/ui/src/lib/BlockFoundAnimation.svelte
index 1255d21..b21b1ad 100644
--- a/ui/src/lib/BlockFoundAnimation.svelte
+++ b/ui/src/lib/BlockFoundAnimation.svelte
@@ -142,7 +142,7 @@
{/each}
{isBlock ? "Share your block" : "Share your best share"}
+
+ {isBlock
+ ? "The card names the chain and links the block, so anyone can look it up for themselves."
+ : "The card carries the raw header, so anyone who sees it can verify the work themselves."}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if note}
+
{note}
+ {/if}
+
+
+
+
diff --git a/ui/src/lib/SharePowPage.svelte b/ui/src/lib/SharePowPage.svelte
index 4e4001e..e750589 100644
--- a/ui/src/lib/SharePowPage.svelte
+++ b/ui/src/lib/SharePowPage.svelte
@@ -3,6 +3,7 @@
import { snap } from "../stores/snapshot.svelte";
import { formatDifficulty, explorerBaseFor } from "../format";
import PowVerifyOverlay from "./PowVerifyOverlay.svelte";
+ import ShareCard from "./ShareCard.svelte";
import type { CheckResult } from "./PowVerifyOverlay.svelte";
import { revHex } from "./sha256";
@@ -11,7 +12,7 @@
// The all-time best share may predate ckpool patch 0008 (which logs
// header data). When the hashes differ, this page is showing the best
- // share *since* the patch went live — disclose that.
+ // share *since* the patch went live, disclose that.
const isNewerThanAllTime = $derived(
!!pow && !!data.best_share_hash && pow.hash !== data.best_share_hash,
);
@@ -73,7 +74,7 @@
bytes: "68–71",
raw: h.slice(136, 144),
value: new Date(time * 1000).toLocaleString(),
- sub: `unix ${time} — the miner may roll this forward slightly`,
+ sub: `unix ${time}, the miner may roll this forward slightly`,
},
{
key: "bits",
@@ -89,7 +90,7 @@
bytes: "76–79",
raw: h.slice(152, 160),
value: nonce.toLocaleString(),
- sub: `0x${nonce.toString(16).padStart(8, "0")} — the value the miner iterated`,
+ sub: `0x${nonce.toString(16).padStart(8, "0")}, the value the miner iterated`,
},
];
});
@@ -113,6 +114,7 @@
// ── Verification: handed to the full-screen overlay ─────────────────
let showOverlay = $state(false);
+ let showShare = $state(false);
let checks: CheckResult[] = $state([]);
let verified = $state(false);
let finalHash = $state("");
@@ -171,7 +173,7 @@
const foundAt = $derived(pow ? new Date(pow.seen_at).toLocaleString() : "");
function onKey(ev: KeyboardEvent): void {
- if (ev.key === "Escape" && !showOverlay) selectBestShare();
+ if (ev.key === "Escape" && !showOverlay && !showShare) selectBestShare();
}
@@ -194,13 +196,13 @@
Header capture was not active when the current best share was found, and CKPool
- discards job data once work moves on — so the block header of that share can
+ discards job data once work moves on, so the block header of that share can
no longer be reconstructed.
The pool now records the full header, coinbase, and merkle branches as shares come
in. The next accepted share will populate this page, which then tracks the best
- share found since header capture was enabled — it does not wait for the
+ share found since header capture was enabled, it does not wait for the
all-time record to be beaten. Once a share does beat the all-time best, the two
converge and this page shows the overall best share.
@@ -230,14 +232,14 @@
Found By
-
{pow.workername || "—"}
+
{pow.workername || "-"}
{foundAt}
-
Block Header — 80 bytes
+
Block Header, 80 bytes
This is the block this share was building, drawn as it exists on the wire. The
header's six fields are serialized little-endian into 80 bytes; the
@@ -280,7 +282,7 @@
{/each}
- + transactions — not part of the header; committed by the
+ + transactions, not part of the header; committed by the
merkle root above
@@ -289,7 +291,7 @@
-
Raw Header — verify it yourself
+
Raw Header, verify it yourself
Hash these 160 hex characters with double SHA-256 and reverse the result: you get the
share hash. No trust in this dashboard required.
@@ -297,14 +299,14 @@
This is what turns a claim into proof.
- A screenshot of a big difficulty number proves nothing — anyone can edit one, and
+ A screenshot of a big difficulty number proves nothing, anyone can edit one, and
no one can tell from a picture whether the work behind it was ever done. These 80 bytes
can't be faked that way. They are the exact preimage of the share hash, so anyone can
hash them and see for themselves both that the hash is genuine and that finding it took
the work claimed. The header also commits to the
previous block, fixing when the work happened, and
through the merkle root to the coinbase
- transaction, fixing who it pays — so the same proof can't be lifted from another
+ transaction, fixing who it pays, so the same proof can't be lifted from another
miner's share or replayed from a different block. Publish this, and the claim stands on
its own.
@@ -325,11 +327,18 @@
class:ok={allOk}
onclick={() => (showOverlay = true)}
>
- {verified ? (allOk ? "Verified \u2713 — watch again" : "Verification failed — replay") : "Verify in this browser"}
+ {verified ? (allOk ? "Verified \u2713, watch again" : "Verification failed, replay") : "Verify in this browser"}
+
+
- takes over the screen and replays the whole verification — merkle proof,
- padding, every SHA-256 round — from the real computation, locally
+ takes over the screen and replays the whole verification, merkle proof,
+ padding, every SHA-256 round, from the real computation, locally
@@ -341,7 +350,7 @@
{finalHash === pow.hash
- ? "= the recorded share hash — proof of work is genuine"
+ ? "= the recorded share hash, proof of work is genuine"
: "\u2260 the recorded share hash!"}
@@ -374,7 +383,7 @@
Reproducing the Merkle Root
The header's merkle root commits to every transaction in the block, but reproducing
- it doesn't require the full transaction list — only the coinbase transaction
+ it doesn't require the full transaction list, only the coinbase transaction
this pool built and one branch hash per tree level. Each branch hash cryptographically
summarizes the other transactions in its subtree, so the proof below is exactly as
strong as storing all of them. (CKPool discards the full template once work moves on,
@@ -395,7 +404,7 @@
{s.name}
- — {s.desc}
+ , {s.desc}
{/each}
@@ -406,7 +415,7 @@
{#if branches.length === 0}
- None — the block template contained only the coinbase transaction, so the
+ None, the block template contained only the coinbase transaction, so the
merkle root is simply the coinbase txid.