package proxy import ( "bufio" "encoding/json" "net" "strings" "testing" "time" ) func TestMiner_Login_Good(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) before := miner.LastActivityAt() miner.Start() defer miner.Close() time.Sleep(5 * time.Millisecond) encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 1, "jsonrpc": "2.0", "method": "login", "params": map[string]interface{}{ "login": "wallet", "pass": "x", "agent": "xmrig", }, }); err != nil { t.Fatal(err) } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } if response["jsonrpc"] != "2.0" { t.Fatalf("unexpected response: %#v", response) } if !miner.LastActivityAt().After(before) { t.Fatalf("expected login to refresh last activity timestamp, got before=%s after=%s", before, miner.LastActivityAt()) } result := response["result"].(map[string]interface{}) id, _ := result["id"].(string) if result["status"] != "OK" || len(id) != 36 || id[8] != '-' || id[13] != '-' || id[18] != '-' || id[23] != '-' || id[14] != '4' || !strings.ContainsAny(string(id[19]), "89ab") { t.Fatalf("unexpected login response: %#v", response) } } func TestMiner_Keepalived_Bad(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.Start() defer miner.Close() encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 2, "jsonrpc": "2.0", "method": "keepalived", }); err != nil { t.Fatal(err) } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } result := response["result"].(map[string]interface{}) if result["status"] != "KEEPALIVED" { t.Fatalf("unexpected keepalived response: %#v", response) } } func TestMiner_Submit_Ugly(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.Start() defer miner.Close() miner.SetRPCID("session") miner.SetState(MinerStateReady) encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 3, "jsonrpc": "2.0", "method": "submit", "params": map[string]interface{}{ "id": "session", "job_id": "job-1", "nonce": "ABC123", "result": "abc", "algo": "cn/r", }, }); err != nil { t.Fatal(err) } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } if response["error"] == nil { t.Fatalf("expected invalid nonce error, got %#v", response) } } func TestMiner_Login_Ugly(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.algoExtension = true miner.Start() defer miner.Close() encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 4, "jsonrpc": "2.0", "method": "login", "params": map[string]interface{}{ "login": "wallet", "pass": "x", "agent": "xmrig", "algo": []string{"cn/r"}, }, }); err != nil { t.Fatal(err) } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } result := response["result"].(map[string]interface{}) extensions, ok := result["extensions"].([]interface{}) if !ok || len(extensions) != 1 || extensions[0] != "algo" { t.Fatalf("expected algo extension to be advertised, got %#v", response) } } func TestMiner_Login_NiceHashPatchedJob_Good(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.algoExtension = true miner.SetCustomDiff(10000) miner.events = NewEventBus() miner.events.Subscribe(EventLogin, func(event Event) { if event.Miner == nil { return } event.Miner.SetNiceHashEnabled(true) event.Miner.SetFixedByte(0x2a) event.Miner.PrimeJob(Job{ Blob: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", JobID: "job-1", Target: "b88d0600", Algo: "cn/r", Height: 42, SeedHash: "seed-hash", }) }) miner.Start() defer miner.Close() encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 5, "jsonrpc": "2.0", "method": "login", "params": map[string]interface{}{ "login": "wallet", "pass": "x", "agent": "xmrig", "algo": []string{"cn/r"}, }, }); err != nil { t.Fatal(err) } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } result := response["result"].(map[string]interface{}) job := result["job"].(map[string]interface{}) if blob, _ := job["blob"].(string); blob[78:80] != "2a" { t.Fatalf("expected patched NiceHash blob, got %q", blob) } if target, _ := job["target"].(string); target != TargetForDifficulty(10000) { t.Fatalf("expected custom diff target, got %q", target) } if height, _ := job["height"].(float64); height != 42 { t.Fatalf("expected job height to be forwarded, got %#v", job) } if seedHash, _ := job["seed_hash"].(string); seedHash != "seed-hash" { t.Fatalf("expected job seed_hash to be forwarded, got %#v", job) } } func TestMiner_ForwardJob_Ugly(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.algoExtension = true miner.extAlgo = true miner.SetRPCID("session") miner.SetCustomDiff(10000) go miner.ForwardJob(Job{ Blob: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", JobID: "job-2", Target: "b88d0600", Algo: "rx/0", Height: 99, SeedHash: "seed", }, "rx/0") clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } params := response["params"].(map[string]interface{}) if params["target"] != TargetForDifficulty(10000) { t.Fatalf("expected custom diff target, got %#v", params) } if params["algo"] != "rx/0" || params["seed_hash"] != "seed" || params["height"] != float64(99) { t.Fatalf("expected extended job fields to be forwarded, got %#v", params) } if miner.Diff() != 10000 { t.Fatalf("expected miner diff to track the effective target, got %d", miner.Diff()) } } func TestMiner_Submit_Good(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.events = NewEventBus() miner.algoExtension = true miner.extAlgo = true miner.SetRPCID("session") miner.SetState(MinerStateReady) submitSeen := make(chan Event, 1) miner.events.Subscribe(EventSubmit, func(event Event) { submitSeen <- event miner.Success(event.RequestID, "OK") }) miner.Start() defer miner.Close() encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 6, "jsonrpc": "2.0", "method": "submit", "params": map[string]interface{}{ "id": "session", "job_id": "job-1", "nonce": "deadbeef", "result": "abc", "algo": "cn/r", }, }); err != nil { t.Fatal(err) } select { case event := <-submitSeen: if event.JobID != "job-1" || event.Nonce != "deadbeef" || event.Algo != "cn/r" { t.Fatalf("unexpected submit event: %+v", event) } case <-time.After(time.Second): t.Fatal("expected submit event to be dispatched") } clientConn.SetReadDeadline(time.Now().Add(time.Second)) line, err := bufio.NewReader(clientConn).ReadBytes('\n') if err != nil { t.Fatal(err) } var response map[string]interface{} if err := json.Unmarshal(line, &response); err != nil { t.Fatal(err) } result := response["result"].(map[string]interface{}) if result["status"] != "OK" { t.Fatalf("unexpected submit response: %#v", response) } } func TestMiner_Submit_AlgoExtension_Bad(t *testing.T) { serverConn, clientConn := net.Pipe() defer clientConn.Close() miner := NewMiner(serverConn, 3333, nil) miner.events = NewEventBus() miner.SetRPCID("session") miner.SetState(MinerStateReady) submitSeen := make(chan Event, 1) miner.events.Subscribe(EventSubmit, func(event Event) { submitSeen <- event miner.Success(event.RequestID, "OK") }) miner.Start() defer miner.Close() encoder := json.NewEncoder(clientConn) if err := encoder.Encode(map[string]interface{}{ "id": 7, "jsonrpc": "2.0", "method": "submit", "params": map[string]interface{}{ "id": "session", "job_id": "job-1", "nonce": "deadbeef", "result": "abc", "algo": "cn/r", }, }); err != nil { t.Fatal(err) } select { case event := <-submitSeen: if event.Algo != "" { t.Fatalf("expected algo to be suppressed when extension is disabled, got %+v", event) } case <-time.After(time.Second): t.Fatal("expected submit event to be dispatched") } }