go-proxy/splitter/nicehash/mapper_start_test.go
Virgil 1f8ff58b20 fix(login): defer login events until assignment succeeds
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-05 02:48:03 +00:00

243 lines
6.3 KiB
Go

package nicehash
import (
"bufio"
"encoding/json"
"net"
"sync"
"testing"
"time"
"dappco.re/go/proxy"
)
type startCountingStrategy struct {
mu sync.Mutex
connect int
}
func (s *startCountingStrategy) Connect() {
s.mu.Lock()
defer s.mu.Unlock()
s.connect++
}
func (s *startCountingStrategy) Submit(jobID, nonce, result, algo string) int64 {
return 0
}
func (s *startCountingStrategy) Disconnect() {}
func (s *startCountingStrategy) IsActive() bool {
s.mu.Lock()
defer s.mu.Unlock()
return s.connect > 0
}
type discardConn struct{}
func (discardConn) Read([]byte) (int, error) { return 0, nil }
func (discardConn) Write(p []byte) (int, error) { return len(p), nil }
func (discardConn) Close() error { return nil }
func (discardConn) LocalAddr() net.Addr { return nil }
func (discardConn) RemoteAddr() net.Addr { return nil }
func (discardConn) SetDeadline(time.Time) error { return nil }
func (discardConn) SetReadDeadline(time.Time) error { return nil }
func (discardConn) SetWriteDeadline(time.Time) error { return nil }
func TestMapper_Start_Good(t *testing.T) {
strategy := &startCountingStrategy{}
mapper := NewNonceMapper(1, &proxy.Config{}, strategy)
mapper.Start()
if strategy.connect != 1 {
t.Fatalf("expected one connect call, got %d", strategy.connect)
}
}
func TestMapper_Start_Bad(t *testing.T) {
mapper := NewNonceMapper(1, &proxy.Config{}, nil)
mapper.Start()
}
func TestMapper_Start_Ugly(t *testing.T) {
strategy := &startCountingStrategy{}
mapper := NewNonceMapper(1, &proxy.Config{}, strategy)
mapper.Start()
mapper.Start()
if strategy.connect != 1 {
t.Fatalf("expected Start to be idempotent, got %d connect calls", strategy.connect)
}
}
func TestMapper_Submit_InvalidJob_Good(t *testing.T) {
minerConn, clientConn := net.Pipe()
defer minerConn.Close()
defer clientConn.Close()
miner := proxy.NewMiner(minerConn, 3333, nil)
miner.SetID(7)
strategy := &startCountingStrategy{}
mapper := NewNonceMapper(1, &proxy.Config{}, strategy)
mapper.storage.job = proxy.Job{JobID: "job-1", Blob: "blob", Target: "b88d0600"}
done := make(chan struct{})
go func() {
mapper.Submit(&proxy.SubmitEvent{
Miner: miner,
JobID: "job-missing",
Nonce: "deadbeef",
Result: "hash",
RequestID: 42,
})
close(done)
}()
line, err := bufio.NewReader(clientConn).ReadBytes('\n')
if err != nil {
t.Fatalf("read error reply: %v", err)
}
<-done
var payload struct {
ID float64 `json:"id"`
Error struct {
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(line, &payload); err != nil {
t.Fatalf("unmarshal error reply: %v", err)
}
if payload.ID != 42 {
t.Fatalf("expected request id 42, got %v", payload.ID)
}
if payload.Error.Message != "Invalid job id" {
t.Fatalf("expected invalid job error, got %q", payload.Error.Message)
}
if len(mapper.pending) != 0 {
t.Fatalf("expected invalid submit not to create a pending entry")
}
}
func TestMapper_OnResultAccepted_ExpiredUsesPreviousJob(t *testing.T) {
bus := proxy.NewEventBus()
events := make(chan proxy.Event, 1)
bus.Subscribe(proxy.EventAccept, func(e proxy.Event) {
events <- e
})
miner := proxy.NewMiner(discardConn{}, 3333, nil)
miner.SetID(7)
mapper := NewNonceMapper(1, &proxy.Config{}, &startCountingStrategy{})
mapper.events = bus
mapper.storage.job = proxy.Job{JobID: "job-new", Blob: "blob-new", Target: "b88d0600"}
mapper.storage.prevJob = proxy.Job{JobID: "job-old", Blob: "blob-old", Target: "b88d0600"}
mapper.storage.miners[miner.ID()] = miner
if !mapper.storage.IsValidJobID("job-old") {
t.Fatal("expected previous job to validate before result handling")
}
mapper.pending[9] = SubmitContext{
RequestID: 42,
MinerID: miner.ID(),
JobID: "job-old",
StartedAt: time.Now(),
}
mapper.OnResultAccepted(9, true, "")
if got := mapper.storage.expired; got != 1 {
t.Fatalf("expected one expired validation, got %d", got)
}
select {
case event := <-events:
if !event.Expired {
t.Fatalf("expected expired share to be flagged")
}
if event.Job == nil || event.Job.JobID != "job-old" {
t.Fatalf("expected previous job to be attached, got %+v", event.Job)
}
case <-time.After(time.Second):
t.Fatal("expected accept event")
}
}
func TestMapper_Submit_ExpiredJobUsesPreviousDifficulty(t *testing.T) {
miner := proxy.NewMiner(discardConn{}, 3333, nil)
miner.SetID(9)
strategy := &submitCaptureStrategy{}
mapper := NewNonceMapper(1, &proxy.Config{}, strategy)
mapper.storage.job = proxy.Job{JobID: "job-new", Blob: "blob-new", Target: "ffffffff"}
mapper.storage.prevJob = proxy.Job{JobID: "job-old", Blob: "blob-old", Target: "b88d0600"}
mapper.storage.miners[miner.ID()] = miner
mapper.Submit(&proxy.SubmitEvent{
Miner: miner,
JobID: "job-old",
Nonce: "deadbeef",
Result: "hash",
RequestID: 88,
})
ctx, ok := mapper.pending[strategy.seq]
if !ok {
t.Fatal("expected pending submit context for expired job")
}
want := mapper.storage.prevJob.DifficultyFromTarget()
if ctx.Diff != want {
t.Fatalf("expected previous-job difficulty %d, got %d", want, ctx.Diff)
}
}
type submitCaptureStrategy struct {
seq int64
}
func (s *submitCaptureStrategy) Connect() {}
func (s *submitCaptureStrategy) Submit(jobID, nonce, result, algo string) int64 {
s.seq++
return s.seq
}
func (s *submitCaptureStrategy) Disconnect() {}
func (s *submitCaptureStrategy) IsActive() bool { return true }
func TestMapper_OnResultAccepted_CustomDiffUsesEffectiveDifficulty(t *testing.T) {
bus := proxy.NewEventBus()
events := make(chan proxy.Event, 1)
bus.Subscribe(proxy.EventAccept, func(e proxy.Event) {
events <- e
})
miner := proxy.NewMiner(discardConn{}, 3333, nil)
miner.SetID(8)
mapper := NewNonceMapper(1, &proxy.Config{}, &startCountingStrategy{})
mapper.events = bus
mapper.storage.job = proxy.Job{JobID: "job-new", Blob: "blob-new", Target: "b88d0600"}
mapper.storage.miners[miner.ID()] = miner
mapper.pending[10] = SubmitContext{
RequestID: 77,
MinerID: miner.ID(),
JobID: "job-new",
Diff: 25000,
StartedAt: time.Now(),
}
mapper.OnResultAccepted(10, true, "")
select {
case event := <-events:
if event.Diff != 25000 {
t.Fatalf("expected effective difficulty 25000, got %d", event.Diff)
}
case <-time.After(time.Second):
t.Fatal("expected accept event")
}
}