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
+211 -8

No files matched your search

+7 -1
View File
@@ -52,8 +52,14 @@ $(BASE_NAME)_aarch64.s9pk: kamado-src
# One-time (per boot) cross-arch build setup: register qemu binfmt
# handlers so the emulated aarch64 ckpool stage can run, and create a
# 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:
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 create --name $(BUILDER) --driver docker-container \
--driver-opt network=host \
+155 -5
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,
sdk.Mounts.of().mountVolume({
volumeId: 'ckpool',
subpath: null,
mountpoint: ckpoolRoot,
readonly: true,
}),
'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`),
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()
+3 -2
View File
@@ -2,11 +2,12 @@ import { VersionGraph } from '@start9labs/start-sdk'
import { v0_2_0 } from './v0_2_0'
import { v0_2_1 } from './v0_2_1'
import { v0_2_2 } from './v0_2_2'
import { v0_2_3 } from './v0_2_3'
// The current version must be listed first; `other` carries the rest of the
// graph so StartOS can find a migration path from whatever version an existing
// install is currently on.
export const versionGraph = VersionGraph.of({
current: v0_2_2,
other: [v0_2_0, v0_2_1],
current: v0_2_3,
other: [v0_2_0, v0_2_1, v0_2_2],
})
+23
View File
@@ -0,0 +1,23 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v0_2_2 = VersionInfo.of({
version: '0.2.2:1',
releaseNotes: {
en_US:
'Let’s Encrypt now actually works. Certificates for a public domain are issued and served by StartOS itself on a new "Stratum (TLS, Public Domain)" interface — attach a domain to it and the Let’s Encrypt option appears, which it never did before. The previous approach could not work: StartOS only issues certificates for endpoints it terminates TLS for, so miners on a domain were served the self-signed certificate instead and rejected it. The plaintext and local self-signed TLS endpoints are unchanged, so nothing already working needs touching, and the dashboard padlock still names which certificate each miner used. The port fields are now named “Preferred”, because StartOS assigns a different port when the one you ask for is already taken — when that happens you now get a notification naming both numbers, and the Pool Status action lists the ports actually in use. Pool Status now also lists any domain attached to an interface with the port it is published on.',
es_ES:
'Let’s Encrypt ya funciona de verdad. Los certificados para un dominio público los emite y sirve StartOS en una nueva interfaz «Stratum (TLS, dominio público)»: adjúntele un dominio y aparecerá la opción de Let’s Encrypt, que antes nunca salía. El enfoque anterior no podía funcionar: StartOS solo emite certificados para los extremos en los que él mismo termina TLS, así que a los mineros conectados por dominio se les servía el certificado autofirmado y lo rechazaban. Los extremos de texto plano y de TLS autofirmado local no cambian, así que no hay que tocar nada que ya funcione, y el candado del panel sigue indicando qué certificado usó cada minero. Los campos de puerto ahora se llaman «preferido», porque StartOS asigna otro puerto cuando el solicitado ya está ocupado: en ese caso recibirá una notificación con ambos números, y la acción Estado del pool lista los puertos realmente en uso. Estado del pool ahora también lista los dominios adjuntos a cada interfaz con el puerto en el que se publican.',
de_DE:
'Let’s Encrypt funktioniert jetzt wirklich. Zertifikate für eine öffentliche Domain stellt und liefert StartOS selbst über die neue Schnittstelle „Stratum (TLS, öffentliche Domain)“ — hängen Sie eine Domain daran an, und die Let’s-Encrypt-Option erscheint, was zuvor nie der Fall war. Der bisherige Ansatz konnte nicht funktionieren: StartOS stellt Zertifikate nur für Endpunkte aus, deren TLS es selbst terminiert, sodass Miner über eine Domain das selbstsignierte Zertifikat erhielten und es ablehnten. Die Klartext- und die lokale selbstsignierte TLS-Schnittstelle bleiben unverändert, es muss also nichts angefasst werden, was bereits funktioniert, und das Schloss im Dashboard benennt weiterhin das verwendete Zertifikat. Die Port-Felder heißen jetzt „bevorzugt“, weil StartOS einen anderen Port zuweist, wenn der gewünschte bereits belegt ist — in diesem Fall erhalten Sie jetzt eine Benachrichtigung mit beiden Nummern, und die Aktion „Pool-Status“ listet die tatsächlich verwendeten Ports auf. „Pool-Status“ listet jetzt auch jede an eine Schnittstelle angehängte Domain mit dem Port auf, unter dem sie veröffentlicht wird.',
pl_PL:
'Let’s Encrypt wreszcie działa. Certyfikaty dla domeny publicznej wystawia i udostępnia sam StartOS w nowym interfejsie „Stratum (TLS, domena publiczna)” — podłącz do niego domenę, a pojawi się opcja Let’s Encrypt, której wcześniej nigdy nie było. Poprzednie podejście nie mogło działać: StartOS wystawia certyfikaty wyłącznie dla punktów końcowych, dla których sam kończy TLS, więc górnicy łączący się przez domenę otrzymywali certyfikat samopodpisany i go odrzucali. Punkty końcowe nieszyfrowany i lokalny TLS samopodpisany pozostają bez zmian, więc nie trzeba ruszać niczego, co już działa, a kłódka w panelu nadal wskazuje użyty certyfikat. Pola portów nazywają się teraz „preferowany”, ponieważ StartOS przydziela inny port, gdy żądany jest już zajęty — w takim przypadku otrzymasz powiadomienie z obydwoma numerami, a akcja Stan puli wypisuje faktycznie używane porty. Stan puli wypisuje teraz również domeny podłączone do każdego interfejsu wraz z portem, na którym są publikowane.',
fr_FR:
'Let’s Encrypt fonctionne enfin. Les certificats pour un domaine public sont émis et présentés par StartOS lui-même sur une nouvelle interface « Stratum (TLS, domaine public) » : rattachez-y un domaine et l’option Let’s Encrypt apparaît, ce qui n’était jamais le cas auparavant. L’approche précédente ne pouvait pas fonctionner : StartOS n’émet des certificats que pour les points de terminaison dont il termine lui-même le TLS, si bien que les mineurs passant par un domaine recevaient le certificat auto-signé et le rejetaient. Les points de terminaison en clair et TLS auto-signé local sont inchangés, rien de ce qui fonctionne déjà n’est à modifier, et le cadenas du tableau de bord indique toujours quel certificat chaque mineur a utilisé. Les champs de port s’appellent désormais « préféré », car StartOS attribue un autre port lorsque celui demandé est déjà pris — dans ce cas vous recevez une notification indiquant les deux numéros, et l’action État du pool liste les ports réellement utilisés. État du pool liste désormais aussi les domaines rattachés à chaque interface avec le port sur lequel ils sont publiés.',
},
// No data migration. The new `stratumPublicTlsPort` store field materializes
// from its `.catch()` default on first read, and the new interface is
// registered by setInterfaces on init. Nothing that existed before changes
// shape: the self-signed certificate, its port, and both existing bindings
// are untouched, so a downgrade needs no undo either.
migrations: {},
})
+23
View File
@@ -0,0 +1,23 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v0_2_3 = VersionInfo.of({
version: '0.2.3:0',
releaseNotes: {
en_US:
'The best-share page can now show you the proof of work itself. A new "Show PoW" button opens the block header behind your best share, drawn as a block with every field in place — version, previous block, merkle root, time, bits and nonce — next to the raw 80 bytes the miner hashed. Press Verify and the page replays the whole verification in front of you: it rebuilds the merkle root from the coinbase transaction and its branch hashes, pads the header into message blocks, runs the real SHA-256 compression rounds, hashes the result a second time, reverses the bytes and compares the hash against the share and network targets. Every value comes from your own share and is computed in your browser, so you can confirm the pool is telling the truth without having to trust it — and the raw header plus a one-line terminal command are there to check it elsewhere. Headers are only recorded from this version onward: a best share found earlier cannot be reconstructed, so until a new record is set the page shows the best share since the update and says so. Restarting the service no longer produces a burst of rejected shares either. Miners were being sent work before they were told which difficulty to work at, and every miner was reset to the pool’s starting difficulty, so the first shares after each restart came back rejected. The difficulty is now sent first, miners resume at the difficulty they had already been tuned to instead of starting over, and work they had already begun is accepted rather than rejected. Pool Status also reports the state of best-share header capture.',
es_ES:
'La página de mejores participaciones ya puede mostrarle la prueba de trabajo en sí. Un nuevo botón «Mostrar PoW» abre la cabecera de bloque de su mejor participación, dibujada como un bloque con cada campo en su sitio —versión, bloque anterior, raíz de Merkle, hora, bits y nonce—, junto a los 80 bytes en bruto que el minero pasó por el hash. Pulse Verificar y la página reproduce toda la verificación ante usted: reconstruye la raíz de Merkle a partir de la transacción coinbase y sus hashes de rama, rellena la cabecera en bloques de mensaje, ejecuta las rondas de compresión reales de SHA-256, vuelve a aplicar el hash al resultado, invierte los bytes y compara el hash con el objetivo de la participación y con el de la red. Todos los valores proceden de su propia participación y se calculan en su navegador, así que puede comprobar que el pool dice la verdad sin tener que confiar en él; además dispone de la cabecera en bruto y de una orden de terminal de una sola línea para verificarlo por fuera. Las cabeceras solo se registran a partir de esta versión: una mejor participación encontrada antes no puede reconstruirse, así que hasta que se establezca un nuevo récord la página muestra la mejor participación desde la actualización y lo indica. Reiniciar el servicio tampoco provoca ya una ráfaga de participaciones rechazadas. A los mineros se les enviaba trabajo antes de indicarles con qué dificultad debían trabajar, y todos se reiniciaban con la dificultad inicial del pool, de modo que las primeras participaciones tras cada reinicio se rechazaban. Ahora la dificultad se envía primero, los mineros retoman la dificultad a la que ya estaban ajustados en lugar de empezar de cero, y el trabajo ya iniciado se acepta en vez de rechazarse. Estado del pool informa además del estado del registro de cabeceras.',
de_DE:
'Die Bestwert-Seite kann Ihnen jetzt den Arbeitsnachweis selbst zeigen. Eine neue Schaltfläche „PoW anzeigen“ öffnet den Blockheader hinter Ihrem besten Share, gezeichnet als Block mit jedem Feld an seinem Platz — Version, vorheriger Block, Merkle-Wurzel, Zeit, Bits und Nonce — daneben die rohen 80 Bytes, die der Miner gehasht hat. Klicken Sie auf „Überprüfen“, und die Seite spielt die gesamte Überprüfung vor Ihren Augen ab: Sie baut die Merkle-Wurzel aus der Coinbase-Transaktion und ihren Zweig-Hashes neu auf, füllt den Header zu Nachrichtenblöcken auf, durchläuft die echten SHA-256-Kompressionsrunden, hasht das Ergebnis ein zweites Mal, dreht die Bytes um und vergleicht den Hash mit dem Share-Ziel und dem Netzwerk-Ziel. Alle angezeigten Werte stammen aus Ihrem eigenen Share und werden in Ihrem Browser berechnet, sodass Sie überprüfen können, ob der Pool die Wahrheit sagt, ohne ihm vertrauen zu müssen — und der rohe Header sowie ein einzeiliger Terminalbefehl stehen bereit, um es anderswo nachzurechnen. Header werden erst ab dieser Version aufgezeichnet: ein früher gefundener bester Share lässt sich nicht rekonstruieren, daher zeigt die Seite bis zum nächsten Rekord den besten Share seit dem Update und weist darauf hin. Auch ein Neustart des Dienstes führt nicht mehr zu einer Welle abgelehnter Shares. Miner erhielten Arbeit, bevor ihnen die Schwierigkeit mitgeteilt wurde, und jeder Miner wurde auf die Startschwierigkeit des Pools zurückgesetzt, sodass die ersten Shares nach jedem Neustart abgelehnt wurden. Nun wird zuerst die Schwierigkeit gesendet, Miner setzen mit der Schwierigkeit fort, auf die sie bereits eingestellt waren, statt von vorn zu beginnen, und bereits begonnene Arbeit wird angenommen statt abgelehnt. „Pool-Status“ meldet zudem den Zustand der Header-Aufzeichnung.',
pl_PL:
'Strona najlepszych udziałów może teraz pokazać sam dowód pracy. Nowy przycisk „Pokaż PoW” otwiera nagłówek bloku stojący za najlepszym udziałem, narysowany jako blok z każdym polem na swoim miejscu — wersja, poprzedni blok, korzeń Merkle, czas, bity i nonce — obok surowych 80 bajtów, które górnik poddał haszowaniu. Naciśnij „Zweryfikuj”, a strona odtworzy całą weryfikację na Twoich oczach: odbuduje korzeń Merkle z transakcji coinbase i jej haszy gałęzi, uzupełni nagłówek do bloków wiadomości, przejdzie przez prawdziwe rundy kompresji SHA-256, zahaszuje wynik po raz drugi, odwróci bajty i porówna hasz z celem udziału oraz z celem sieci. Wszystkie pokazane wartości pochodzą z Twojego własnego udziału i są obliczane w przeglądarce, więc możesz sprawdzić, czy pula mówi prawdę, nie musząc jej ufać — a surowy nagłówek i jednolinijkowe polecenie terminala pozwalają zweryfikować to gdzie indziej. Nagłówki są zapisywane dopiero od tej wersji: najlepszego udziału znalezionego wcześniej nie da się odtworzyć, więc do czasu pobicia rekordu strona pokazuje najlepszy udział od czasu aktualizacji i wyraźnie o tym informuje. Ponowne uruchomienie usługi nie powoduje już również fali odrzuconych udziałów. Górnicy otrzymywali pracę, zanim powiedziano im, z jaką trudnością mają pracować, a każdy był resetowany do początkowej trudności puli, więc pierwsze udziały po każdym restarcie były odrzucane. Teraz trudność wysyłana jest jako pierwsza, górnicy wracają do trudności, do której byli już dostrojeni, zamiast zaczynać od nowa, a rozpoczęta już praca jest przyjmowana zamiast odrzucana. Stan puli podaje ponadto stan zapisu nagłówków.',
fr_FR:
'La page des meilleures parts peut désormais vous montrer la preuve de travail elle-même. Un nouveau bouton « Afficher la PoW » ouvre l’en-tête de bloc de votre meilleure part, dessiné comme un bloc avec chaque champ à sa place — version, bloc précédent, racine de Merkle, heure, bits et nonce — à côté des 80 octets bruts que le mineur a hachés. Appuyez sur Vérifier et la page rejoue toute la vérification sous vos yeux : elle reconstruit la racine de Merkle à partir de la transaction coinbase et de ses hachages de branche, complète l’en-tête en blocs de message, exécute les véritables tours de compression SHA-256, hache le résultat une seconde fois, inverse les octets et compare le hachage à la cible de la part et à celle du réseau. Toutes les valeurs proviennent de votre propre part et sont calculées dans votre navigateur : vous pouvez donc vérifier que le pool dit vrai sans avoir à lui faire confiance — et l’en-tête brut ainsi qu’une commande de terminal d’une ligne permettent de le recalculer ailleurs. Les en-têtes ne sont enregistrés qu’à partir de cette version : une meilleure part trouvée auparavant ne peut pas être reconstituée, donc jusqu’au prochain record la page affiche la meilleure part depuis la mise à jour et le signale. Le redémarrage du service ne provoque plus non plus de vague de parts rejetées. Les mineurs recevaient du travail avant qu’on leur indique à quelle difficulté travailler, et chacun était réinitialisé à la difficulté de départ du pool, si bien que les premières parts après chaque redémarrage étaient rejetées. La difficulté est désormais envoyée en premier, les mineurs reprennent à la difficulté à laquelle ils étaient déjà ajustés au lieu de repartir de zéro, et le travail déjà entamé est accepté plutôt que rejeté. État du pool indique également l’état de l’enregistrement des en-têtes.',
},
// No data migration in either direction. The proof-of-work record is a new
// row in the existing kv table of the same SQLite database, written the
// first time a share sets one — nothing that existed before changes shape,
// so a downgrade has nothing to undo (an older build simply ignores the
// row). The ckpool difficulty fix lives in the binary, not in stored data.
migrations: {},
})