Bump version to 0.2.3 with new upstream features and bug fixes
This commit is contained in:
5 files changed
+211
-8
No files matched your search
@@ -52,8 +52,14 @@ $(BASE_NAME)_aarch64.s9pk: kamado-src
|
|||||||
# One-time (per boot) cross-arch build setup: register qemu binfmt
|
# One-time (per boot) cross-arch build setup: register qemu binfmt
|
||||||
# handlers so the emulated aarch64 ckpool stage can run, and create a
|
# handlers so the emulated aarch64 ckpool stage can run, and create a
|
||||||
# buildx builder that advertises both platforms.
|
# buildx builder that advertises both platforms.
|
||||||
|
#
|
||||||
|
# tonistiigi/binfmt, NOT multiarch/qemu-user-static: the latter ships
|
||||||
|
# qemu 7.2, whose emulated gcc segfaults intermittently on real builds
|
||||||
|
# (seen killing both the jansson compile and the test/sha256 link in the
|
||||||
|
# ckpool stage). The uninstall clears any older same-named handler first.
|
||||||
setup:
|
setup:
|
||||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
-docker run --rm --privileged tonistiigi/binfmt --uninstall qemu-aarch64 2>/dev/null
|
||||||
|
docker run --rm --privileged tonistiigi/binfmt --install arm64
|
||||||
-docker buildx rm $(BUILDER) 2>/dev/null
|
-docker buildx rm $(BUILDER) 2>/dev/null
|
||||||
docker buildx create --name $(BUILDER) --driver docker-container \
|
docker buildx create --name $(BUILDER) --driver docker-container \
|
||||||
--driver-opt network=host \
|
--driver-opt network=host \
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { storeJson } from '../fileModels/store.json'
|
|||||||
import { i18n } from '../i18n'
|
import { i18n } from '../i18n'
|
||||||
import { sdk } from '../sdk'
|
import { sdk } from '../sdk'
|
||||||
import {
|
import {
|
||||||
|
ckpoolLogFile,
|
||||||
|
ckpoolRoot,
|
||||||
curlJson,
|
curlJson,
|
||||||
defaultStratumPort,
|
defaultStratumPort,
|
||||||
defaultStratumPublicTlsPort,
|
defaultStratumPublicTlsPort,
|
||||||
@@ -112,6 +114,17 @@ interface Snapshot {
|
|||||||
hashrate_hs_1h: number
|
hashrate_hs_1h: number
|
||||||
hashrate_hs_24h: number
|
hashrate_hs_24h: number
|
||||||
best_diff: 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
|
cumulative_shares: number
|
||||||
next_block_reward_btc: number
|
next_block_reward_btc: number
|
||||||
next_difficulty_percent: number
|
next_difficulty_percent: number
|
||||||
@@ -183,19 +196,59 @@ export const poolStatus = sdk.Action.withoutInput(
|
|||||||
const stratumPort = ports[0].assigned ?? ports[0].requested
|
const stratumPort = ports[0].assigned ?? ports[0].requested
|
||||||
|
|
||||||
// Fetch from inside the service's network namespace: temp subcontainers
|
// Fetch from inside the service's network namespace: temp subcontainers
|
||||||
// share it, so curl reaches kamado-api on 127.0.0.1.
|
// share it, so curl reaches kamado-api on 127.0.0.1. The ckpool volume
|
||||||
const { snap, dbg } = await sdk.SubContainer.withTemp(
|
// is mounted read-only so the PoW diagnostics below can inspect the
|
||||||
|
// ckpool log directly.
|
||||||
|
const { snap, dbg, pow } = await sdk.SubContainer.withTemp(
|
||||||
effects,
|
effects,
|
||||||
{ imageId: 'main' },
|
{ imageId: 'main' },
|
||||||
null,
|
sdk.Mounts.of().mountVolume({
|
||||||
|
volumeId: 'ckpool',
|
||||||
|
subpath: null,
|
||||||
|
mountpoint: ckpoolRoot,
|
||||||
|
readonly: true,
|
||||||
|
}),
|
||||||
'pool-status',
|
'pool-status',
|
||||||
async (sub) => ({
|
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`),
|
snap: await curlJson<Snapshot>(sub, `${API_BASE}/api/snapshot`),
|
||||||
dbg: await curlJson<DebugBlocks>(
|
dbg: await curlJson<DebugBlocks>(
|
||||||
sub,
|
sub,
|
||||||
`${API_BASE}/api/admin/debug-blocks`,
|
`${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[] = []
|
const lines: string[] = []
|
||||||
@@ -372,6 +425,103 @@ export const poolStatus = sdk.Action.withoutInput(
|
|||||||
log(' (debug endpoint unavailable)')
|
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, | ||||||