Add a page which proves the best share PoW
This commit is contained in:
13 files changed
+2708
-10
No files matched your search
@@ -0,0 +1,121 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kamadopool/kamado-api/internal/logmon"
|
||||
)
|
||||
|
||||
// The two lines below are the verbatim output of ckpool patch 0008's
|
||||
// jansson code (replicated against ckpool's in-tree jansson 2.14 with
|
||||
// identical calls and JSON_COMPACT), using Bitcoin block 125552's real
|
||||
// header and hash. If ckpool's encoding and this pipeline ever drift,
|
||||
// this test is the tripwire.
|
||||
const (
|
||||
ckpoolPowLine = `[2026-08-31 18:00:01.123] Best share PoW data {"sdiff":62953.415063916997,"netdiff":136597951737045.02,"height":911234,"hash":"00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d","header":"0100000081cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122bc7f5d74df2b9441a42a14695","coinbase":"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2703e2e60d04858ee468086b616d61646f2f04a1b2c3d40011223344556677ffffffff0100f2052a010000001600140123456789abcdef0123456789abcdef0123456700000000","cb1len":41,"merklebranches":["fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4","6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4"],"enonce1":"a1b2c3d4","nonce2":"0011223344556677","workername":"bc1qexample.axe1"}` + "\n"
|
||||
ckpoolAcceptLine = `[2026-08-31 18:00:01.124] Accepted client 271 share diff 62953.4/512/1.02K: 00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d` + "\n"
|
||||
)
|
||||
|
||||
// End to end: real ckpool log bytes -> real Tailer.Run -> aggregator
|
||||
// ingest -> snapshot JSON. Exercises the exact production path.
|
||||
func TestPowEndToEnd_LogToSnapshot(t *testing.T) {
|
||||
f, err := os.CreateTemp("", "ckpool_e2e*.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
t.Cleanup(func() { _ = os.Remove(f.Name()) })
|
||||
|
||||
tl := logmon.New(f.Name(), discardLog())
|
||||
tl.PollWait = 10 * time.Millisecond
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go tl.Run(ctx)
|
||||
|
||||
a := New(nil, nil, time.Minute, discardLog())
|
||||
go a.IngestShareEvents(ctx, tl.Shares)
|
||||
|
||||
// Give the tailer a moment to open the (empty) file and seek to the
|
||||
// end, then append what ckpool would write.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
lf, err := os.OpenFile(f.Name(), os.O_APPEND|os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := lf.WriteString(ckpoolPowLine + ckpoolAcceptLine); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lf.Close()
|
||||
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
a.mu.RLock()
|
||||
pow := a.bestPow
|
||||
a.mu.RUnlock()
|
||||
if pow != nil {
|
||||
if pow.Hash != "00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d" {
|
||||
t.Fatalf("hash = %s", pow.Hash)
|
||||
}
|
||||
if pow.SDiff != 62953.415063916997 || pow.NetDiff != 136597951737045.02 {
|
||||
t.Fatalf("sdiff/netdiff = %v/%v", pow.SDiff, pow.NetDiff)
|
||||
}
|
||||
if len(pow.Header) != 160 || len(pow.MerkleBranches) != 2 {
|
||||
t.Fatalf("header len %d, branches %d", len(pow.Header), len(pow.MerkleBranches))
|
||||
}
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("bestPow never populated from the tailed log")
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// The snapshot must expose it under best_share_pow with the flattened
|
||||
// PowData fields — this is the contract the UI reads.
|
||||
snap := Snapshot{BestSharePow: func() *BestSharePow {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.bestPow
|
||||
}()}
|
||||
data, err := json.Marshal(snap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var out map[string]any
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
powJSON, ok := out["best_share_pow"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("snapshot JSON missing best_share_pow: %s", data)
|
||||
}
|
||||
for _, key := range []string{"sdiff", "netdiff", "height", "hash", "header", "coinbase", "cb1len", "merklebranches", "enonce1", "nonce2", "workername", "seen_at"} {
|
||||
if _, ok := powJSON[key]; !ok {
|
||||
t.Errorf("best_share_pow JSON missing %q", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The startup backfill must recover the same record from an existing
|
||||
// log file (the path taken when live events were missed).
|
||||
func TestPowEndToEnd_Backfill(t *testing.T) {
|
||||
path := tempLogFile(t, ckpoolPowLine, ckpoolAcceptLine)
|
||||
pow, at, _, _, err := logmon.FindBestPowData(path)
|
||||
if err != nil {
|
||||
t.Fatalf("scan error: %v", err)
|
||||
}
|
||||
if pow == nil {
|
||||
t.Fatal("FindBestPowData found nothing in verbatim ckpool output")
|
||||
}
|
||||
if pow.SDiff != 62953.415063916997 || len(pow.MerkleBranches) != 2 {
|
||||
t.Fatalf("got %+v", pow)
|
||||
}
|
||||
want := time.Date(2026, 8, 31, 18, 0, 1, 0, time.Local)
|
||||
if !at.Equal(want) {
|
||||
t.Errorf("timestamp = %v, want %v", at, want)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user