Bump version to 0.2.3 with new upstream features and bug fixes

This commit is contained in:
satoshi committed 2026-09-02 00:16:47 +03:00
1 parent eb5f4cbf5a
commit 862ebc88f1
5 files changed
+216 -13

No files matched your search

+160 -10
View File
@@ -2,6 +2,8 @@ import { storeJson } from '../fileModels/store.json'
import { i18n } from '../i18n'
import { sdk } from '../sdk'
import {
ckpoolLogFile,
ckpoolRoot,
curlJson,
defaultStratumPort,
defaultStratumPublicTlsPort,
@@ -112,6 +114,17 @@ interface Snapshot {
hashrate_hs_1h: number
hashrate_hs_24h: number
best_diff: number
best_share_hash?: string
best_share_pow?: {
sdiff: number
height: number
hash: string
seen_at: string
}
// Log-derived (tailer) session stats — nonzero proves kamado-api is
// actually reading ckpool's log, as opposed to the socket-derived
// share counters above.
diff_dist_session: number[]
cumulative_shares: number
next_block_reward_btc: number
next_difficulty_percent: number
@@ -183,19 +196,59 @@ export const poolStatus = sdk.Action.withoutInput(
const stratumPort = ports[0].assigned ?? ports[0].requested
// Fetch from inside the service's network namespace: temp subcontainers
// share it, so curl reaches kamado-api on 127.0.0.1.
const { snap, dbg } = await sdk.SubContainer.withTemp(
// share it, so curl reaches kamado-api on 127.0.0.1. The ckpool volume
// is mounted read-only so the PoW diagnostics below can inspect the
// ckpool log directly.
const { snap, dbg, pow } = await sdk.SubContainer.withTemp(
effects,
{ imageId: 'main' },
null,
'pool-status',
async (sub) => ({
snap: await curlJson<Snapshot>(sub, `${API_BASE}/api/snapshot`),
dbg: await curlJson<DebugBlocks>(
sub,
`${API_BASE}/api/admin/debug-blocks`,
),
sdk.Mounts.of().mountVolume({
volumeId: 'ckpool',
subpath: null,
mountpoint: ckpoolRoot,
readonly: true,
}),
'pool-status',
async (sub) => {
// PoW capture pipeline (ckpool patch 0008 → log → kamado-api):
// check each link so a break is attributable. grep -a treats the
// ckpool binary and log as text; counts are "0" on no match.
const countIn = async (target: string): Promise<number> => {
const res = await sub.exec([
'sh',
'-c',
`grep -ac "Best share PoW data" ${target} 2>/dev/null || true`,
])
return parseInt((res.stdout || '').toString().trim(), 10) || 0
}
const acceptedRes = await sub.exec([
'sh',
'-c',
`grep -ac "Accepted client" ${ckpoolLogFile} 2>/dev/null || true`,
])
// The newest PoW line verbatim (bounded), so the action can run
// the exact same regex + JSON.parse kamado-api uses and report
// WHERE ingestion breaks on real data rather than guessing.
const lastPowRes = await sub.exec([
'sh',
'-c',
`grep -a "Best share PoW data" ${ckpoolLogFile} 2>/dev/null | tail -1 | cut -c1-8000 || true`,
])
return {
snap: await curlJson<Snapshot>(sub, `${API_BASE}/api/snapshot`),
dbg: await curlJson<DebugBlocks>(
sub,
`${API_BASE}/api/admin/debug-blocks`,
),
pow: {
binaryPatched: (await countIn('/usr/local/bin/ckpool')) > 0,
logPowLines: await countIn(ckpoolLogFile),
logAcceptedLines:
parseInt((acceptedRes.stdout || '').toString().trim(), 10) || 0,
lastPowLine: (lastPowRes.stdout || '').toString().trim(),
},
}
},
)
const lines: string[] = []
@@ -372,6 +425,103 @@ export const poolStatus = sdk.Action.withoutInput(
log(' (debug endpoint unavailable)')
}
// ── 1c. PoW capture pipeline ────────────────────────────────────────────
// Traces best-share header capture (ckpool patch 0008) link by link:
// patched binary → "Best share PoW data" log lines → kamado-api ingest.
log('')
log('● PoW capture (best-share header inspector)')
log(
` ckpool binary: ${pow.binaryPatched ? 'patched (0008) ✓' : 'NOT PATCHED — image predates patch 0008'}`,
)
log(
` ckpool log: ${pow.logPowLines} PoW data line${pow.logPowLines === 1 ? '' : 's'}, ${pow.logAcceptedLines} accepted-share lines`,
)
const snapPow = snap?.best_share_pow
log(
` kamado-api: ${
snapPow
? `record held — diff ${fmtDiff(snapPow.sdiff)} at height ${snapPow.height} (${snapPow.seen_at.slice(0, 19)})`
: 'no PoW record in snapshot'
}`,
)
// Log-derived session stats prove the tailer reads the log at all —
// the share counters above come from ckpool's socket instead.
const tailerShares = (snap?.diff_dist_session ?? []).reduce(
(a, b) => a + b,
0,
)
log(
` log tailer: ${tailerShares} accepted shares ingested this session`,
)
// Replay kamado-api's parse on the newest real PoW line: decode one
// brace-balanced JSON object after the marker, ignoring any trailing
// bytes — the same tolerance the Go parser applies.
if (pow.lastPowLine) {
let parseVerdict: string
const start = pow.lastPowLine.indexOf('{', pow.lastPowLine.indexOf('Best share PoW data'))
if (start < 0) {
parseVerdict = 'NO JSON OBJECT after marker'
} else {
// Walk to the matching close brace, honouring strings/escapes.
let depth = 0
let inStr = false
let esc = false
let end = -1
for (let i = start; i < pow.lastPowLine.length; i++) {
const c = pow.lastPowLine[i]
if (esc) esc = false
else if (inStr) {
if (c === '\\') esc = true
else if (c === '"') inStr = false
} else if (c === '"') inStr = true
else if (c === '{') depth++
else if (c === '}' && --depth === 0) {
end = i
break
}
}
if (end < 0) {
parseVerdict = 'UNTERMINATED JSON — line truncated mid-record'
} else {
try {
const j = JSON.parse(pow.lastPowLine.slice(start, end + 1))
parseVerdict =
typeof j.header === 'string' && j.header.length === 160
? `parses ✓ (sdiff ${fmtDiff(j.sdiff ?? 0)}, height ${j.height}, header 160 hex)`
: `JSON ok but header field is ${typeof j.header === 'string' ? `${j.header.length} chars` : 'missing'}`
} catch (e) {
parseVerdict = `JSON PARSE FAILED — ${String(e).slice(0, 120)}`
}
}
}
log(` last PoW line: ${parseVerdict}`)
log(` head: ${pow.lastPowLine.slice(0, 200)}…`)
// Tail rendered via JSON.stringify so control bytes (\r, ,
// an interleaved second message…) are visible, not invisible.
log(` tail: ${JSON.stringify(pow.lastPowLine.slice(-80))}`)
}
if (!pow.binaryPatched) {
log(' → verdict: installed image is stale. Sideload the current s9pk.')
allPass = false
} else if (pow.logAcceptedLines === 0) {
log(
' → verdict: no accepted shares in the current log file — ckpool logging problem or log freshly rotated. Wait for shares, then re-run.',
)
allPass = false
} else if (pow.logPowLines === 0) {
log(
' → verdict: binary is patched but has never logged PoW data — the running ckpool process likely predates the update. Restart the service.',
)
allPass = false
} else if (!snapPow) {
log(
' → verdict: ckpool logged PoW data but kamado-api holds no record — restart the service so the startup backfill adopts it from the log.',
)
allPass = false
} else {
log(' → verdict: pipeline healthy ✓')
}
// ── 2. Stratum ───────────────────────────────────────────────────────────
log('')
sep()