feat: Add dual CPU+GPU mining support with separate pools/algos
- Add GPU config fields: GPUEnabled, GPUPool, GPUWallet, GPUAlgo, CUDA, OpenCL - XMRig config now supports separate pool/algo for GPU vs CPU mining - CPU can mine RandomX while GPU mines KawPow on different pools - Add xmrig_gpu_test.go with tests for dual, GPU-only, and CPU-only configs - Make getXMRigConfigPath a variable for test overriding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ab47bae0a3
commit
876253f194
4 changed files with 313 additions and 15 deletions
|
|
@ -115,10 +115,19 @@ type Config struct {
|
||||||
Seed string `json:"seed,omitempty"`
|
Seed string `json:"seed,omitempty"`
|
||||||
Hash string `json:"hash,omitempty"`
|
Hash string `json:"hash,omitempty"`
|
||||||
NoDMI bool `json:"noDMI,omitempty"`
|
NoDMI bool `json:"noDMI,omitempty"`
|
||||||
// GPU-specific options
|
// GPU-specific options (for XMRig dual CPU+GPU mining)
|
||||||
Devices string `json:"devices,omitempty"` // GPU device selection (e.g., "0,1,2")
|
GPUEnabled bool `json:"gpuEnabled,omitempty"` // Enable GPU mining
|
||||||
Intensity int `json:"intensity,omitempty"` // Mining intensity for GPU miners
|
GPUPool string `json:"gpuPool,omitempty"` // Separate pool for GPU (can differ from CPU)
|
||||||
CLIArgs string `json:"cliArgs,omitempty"` // Additional CLI arguments
|
GPUWallet string `json:"gpuWallet,omitempty"` // Wallet for GPU pool (defaults to main Wallet)
|
||||||
|
GPUAlgo string `json:"gpuAlgo,omitempty"` // Algorithm for GPU (e.g., "kawpow", "ethash")
|
||||||
|
GPUPassword string `json:"gpuPassword,omitempty"` // Password for GPU pool
|
||||||
|
GPUIntensity int `json:"gpuIntensity,omitempty"` // GPU mining intensity (0-100)
|
||||||
|
GPUThreads int `json:"gpuThreads,omitempty"` // GPU threads per card
|
||||||
|
Devices string `json:"devices,omitempty"` // GPU device selection (e.g., "0,1,2")
|
||||||
|
OpenCL bool `json:"opencl,omitempty"` // Enable OpenCL (AMD/Intel GPUs)
|
||||||
|
CUDA bool `json:"cuda,omitempty"` // Enable CUDA (NVIDIA GPUs)
|
||||||
|
Intensity int `json:"intensity,omitempty"` // Mining intensity for GPU miners
|
||||||
|
CLIArgs string `json:"cliArgs,omitempty"` // Additional CLI arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerformanceMetrics represents the performance metrics for a miner.
|
// PerformanceMetrics represents the performance metrics for a miner.
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ func NewXMRigMiner() *XMRigMiner {
|
||||||
|
|
||||||
// getXMRigConfigPath returns the platform-specific path for the xmrig.json file.
|
// getXMRigConfigPath returns the platform-specific path for the xmrig.json file.
|
||||||
// If instanceName is provided, it creates an instance-specific config file.
|
// If instanceName is provided, it creates an instance-specific config file.
|
||||||
func getXMRigConfigPath(instanceName string) (string, error) {
|
// This is a variable so it can be overridden in tests.
|
||||||
|
var getXMRigConfigPath = func(instanceName string) (string, error) {
|
||||||
configFileName := "xmrig.json"
|
configFileName := "xmrig.json"
|
||||||
if instanceName != "" && instanceName != "xmrig" {
|
if instanceName != "" && instanceName != "xmrig" {
|
||||||
// Use instance-specific config file (e.g., xmrig-78.json)
|
// Use instance-specific config file (e.g., xmrig-78.json)
|
||||||
|
|
|
||||||
237
pkg/mining/xmrig_gpu_test.go
Normal file
237
pkg/mining/xmrig_gpu_test.go
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
package mining
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXMRigDualMiningConfig(t *testing.T) {
|
||||||
|
// Create a temp directory for the config
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
miner := &XMRigMiner{
|
||||||
|
BaseMiner: BaseMiner{
|
||||||
|
Name: "xmrig-dual-test",
|
||||||
|
API: &API{
|
||||||
|
Enabled: true,
|
||||||
|
ListenHost: "127.0.0.1",
|
||||||
|
ListenPort: 12345,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily override config path
|
||||||
|
origGetPath := getXMRigConfigPath
|
||||||
|
getXMRigConfigPath = func(name string) (string, error) {
|
||||||
|
return filepath.Join(tmpDir, name+".json"), nil
|
||||||
|
}
|
||||||
|
defer func() { getXMRigConfigPath = origGetPath }()
|
||||||
|
|
||||||
|
// Config with CPU mining rx/0 and GPU mining kawpow on different pools
|
||||||
|
config := &Config{
|
||||||
|
// CPU config
|
||||||
|
Pool: "stratum+tcp://pool.supportxmr.com:3333",
|
||||||
|
Wallet: "cpu_wallet_address",
|
||||||
|
Algo: "rx/0",
|
||||||
|
CPUMaxThreadsHint: 50,
|
||||||
|
|
||||||
|
// GPU config - separate pool and algo
|
||||||
|
GPUEnabled: true,
|
||||||
|
GPUPool: "stratum+tcp://ravencoin.pool.com:3333",
|
||||||
|
GPUWallet: "gpu_wallet_address",
|
||||||
|
GPUAlgo: "kawpow",
|
||||||
|
CUDA: true, // NVIDIA
|
||||||
|
OpenCL: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := miner.createConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and parse the generated config
|
||||||
|
data, err := os.ReadFile(miner.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedConfig map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &generatedConfig); err != nil {
|
||||||
|
t.Fatalf("Failed to parse config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify pools
|
||||||
|
pools, ok := generatedConfig["pools"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("pools not found in config")
|
||||||
|
}
|
||||||
|
if len(pools) != 2 {
|
||||||
|
t.Errorf("Expected 2 pools (CPU + GPU), got %d", len(pools))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify CPU pool
|
||||||
|
cpuPool := pools[0].(map[string]interface{})
|
||||||
|
if cpuPool["url"] != "stratum+tcp://pool.supportxmr.com:3333" {
|
||||||
|
t.Errorf("CPU pool URL mismatch: %v", cpuPool["url"])
|
||||||
|
}
|
||||||
|
if cpuPool["user"] != "cpu_wallet_address" {
|
||||||
|
t.Errorf("CPU wallet mismatch: %v", cpuPool["user"])
|
||||||
|
}
|
||||||
|
if cpuPool["algo"] != "rx/0" {
|
||||||
|
t.Errorf("CPU algo mismatch: %v", cpuPool["algo"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify GPU pool
|
||||||
|
gpuPool := pools[1].(map[string]interface{})
|
||||||
|
if gpuPool["url"] != "stratum+tcp://ravencoin.pool.com:3333" {
|
||||||
|
t.Errorf("GPU pool URL mismatch: %v", gpuPool["url"])
|
||||||
|
}
|
||||||
|
if gpuPool["user"] != "gpu_wallet_address" {
|
||||||
|
t.Errorf("GPU wallet mismatch: %v", gpuPool["user"])
|
||||||
|
}
|
||||||
|
if gpuPool["algo"] != "kawpow" {
|
||||||
|
t.Errorf("GPU algo mismatch: %v", gpuPool["algo"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify CUDA enabled, OpenCL disabled
|
||||||
|
cuda := generatedConfig["cuda"].(map[string]interface{})
|
||||||
|
if cuda["enabled"] != true {
|
||||||
|
t.Error("CUDA should be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
opencl := generatedConfig["opencl"].(map[string]interface{})
|
||||||
|
if opencl["enabled"] != false {
|
||||||
|
t.Error("OpenCL should be disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify CPU config
|
||||||
|
cpu := generatedConfig["cpu"].(map[string]interface{})
|
||||||
|
if cpu["enabled"] != true {
|
||||||
|
t.Error("CPU should be enabled")
|
||||||
|
}
|
||||||
|
if cpu["max-threads-hint"] != float64(50) {
|
||||||
|
t.Errorf("CPU max-threads-hint mismatch: %v", cpu["max-threads-hint"])
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Generated dual-mining config:\n%s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXMRigGPUOnlyConfig(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
miner := &XMRigMiner{
|
||||||
|
BaseMiner: BaseMiner{
|
||||||
|
Name: "xmrig-gpu-only",
|
||||||
|
API: &API{
|
||||||
|
Enabled: true,
|
||||||
|
ListenHost: "127.0.0.1",
|
||||||
|
ListenPort: 12346,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
origGetPath := getXMRigConfigPath
|
||||||
|
getXMRigConfigPath = func(name string) (string, error) {
|
||||||
|
return filepath.Join(tmpDir, name+".json"), nil
|
||||||
|
}
|
||||||
|
defer func() { getXMRigConfigPath = origGetPath }()
|
||||||
|
|
||||||
|
// GPU-only config using same pool for simplicity
|
||||||
|
config := &Config{
|
||||||
|
Pool: "stratum+tcp://pool.supportxmr.com:3333",
|
||||||
|
Wallet: "test_wallet",
|
||||||
|
Algo: "rx/0",
|
||||||
|
NoCPU: true, // Disable CPU
|
||||||
|
GPUEnabled: true,
|
||||||
|
OpenCL: true, // AMD GPU
|
||||||
|
CUDA: true, // Also NVIDIA
|
||||||
|
}
|
||||||
|
|
||||||
|
err := miner.createConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(miner.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedConfig map[string]interface{}
|
||||||
|
json.Unmarshal(data, &generatedConfig)
|
||||||
|
|
||||||
|
// Both GPU backends should be enabled
|
||||||
|
cuda := generatedConfig["cuda"].(map[string]interface{})
|
||||||
|
opencl := generatedConfig["opencl"].(map[string]interface{})
|
||||||
|
|
||||||
|
if cuda["enabled"] != true {
|
||||||
|
t.Error("CUDA should be enabled")
|
||||||
|
}
|
||||||
|
if opencl["enabled"] != true {
|
||||||
|
t.Error("OpenCL should be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Generated GPU config:\n%s", string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXMRigCPUOnlyConfig(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
miner := &XMRigMiner{
|
||||||
|
BaseMiner: BaseMiner{
|
||||||
|
Name: "xmrig-cpu-only",
|
||||||
|
API: &API{
|
||||||
|
Enabled: true,
|
||||||
|
ListenHost: "127.0.0.1",
|
||||||
|
ListenPort: 12347,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
origGetPath := getXMRigConfigPath
|
||||||
|
getXMRigConfigPath = func(name string) (string, error) {
|
||||||
|
return filepath.Join(tmpDir, name+".json"), nil
|
||||||
|
}
|
||||||
|
defer func() { getXMRigConfigPath = origGetPath }()
|
||||||
|
|
||||||
|
// CPU-only config (GPUEnabled defaults to false)
|
||||||
|
config := &Config{
|
||||||
|
Pool: "stratum+tcp://pool.supportxmr.com:3333",
|
||||||
|
Wallet: "test_wallet",
|
||||||
|
Algo: "rx/0",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := miner.createConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(miner.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatedConfig map[string]interface{}
|
||||||
|
json.Unmarshal(data, &generatedConfig)
|
||||||
|
|
||||||
|
// GPU backends should be disabled
|
||||||
|
cuda := generatedConfig["cuda"].(map[string]interface{})
|
||||||
|
opencl := generatedConfig["opencl"].(map[string]interface{})
|
||||||
|
|
||||||
|
if cuda["enabled"] != false {
|
||||||
|
t.Error("CUDA should be disabled for CPU-only config")
|
||||||
|
}
|
||||||
|
if opencl["enabled"] != false {
|
||||||
|
t.Error("OpenCL should be disabled for CPU-only config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should only have 1 pool
|
||||||
|
pools := generatedConfig["pools"].([]interface{})
|
||||||
|
if len(pools) != 1 {
|
||||||
|
t.Errorf("Expected 1 pool for CPU-only, got %d", len(pools))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Generated CPU-only config:\n%s", string(data))
|
||||||
|
}
|
||||||
|
|
@ -166,22 +166,73 @@ func (m *XMRigMiner) createConfig(config *Config) error {
|
||||||
cpuConfig["priority"] = config.CPUPriority
|
cpuConfig["priority"] = config.CPUPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build pools array - CPU pool first
|
||||||
|
pools := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"url": config.Pool,
|
||||||
|
"user": config.Wallet,
|
||||||
|
"pass": "x",
|
||||||
|
"keepalive": true,
|
||||||
|
"tls": config.TLS,
|
||||||
|
"algo": config.Algo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add separate GPU pool if configured
|
||||||
|
if config.GPUEnabled && config.GPUPool != "" {
|
||||||
|
gpuWallet := config.GPUWallet
|
||||||
|
if gpuWallet == "" {
|
||||||
|
gpuWallet = config.Wallet // Default to main wallet
|
||||||
|
}
|
||||||
|
gpuPass := config.GPUPassword
|
||||||
|
if gpuPass == "" {
|
||||||
|
gpuPass = "x"
|
||||||
|
}
|
||||||
|
pools = append(pools, map[string]interface{}{
|
||||||
|
"url": config.GPUPool,
|
||||||
|
"user": gpuWallet,
|
||||||
|
"pass": gpuPass,
|
||||||
|
"keepalive": true,
|
||||||
|
"algo": config.GPUAlgo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build OpenCL (AMD/Intel GPU) config
|
||||||
|
openclConfig := map[string]interface{}{
|
||||||
|
"enabled": config.GPUEnabled && config.OpenCL,
|
||||||
|
}
|
||||||
|
if config.GPUEnabled && config.OpenCL {
|
||||||
|
if config.GPUIntensity > 0 {
|
||||||
|
openclConfig["intensity"] = config.GPUIntensity
|
||||||
|
}
|
||||||
|
if config.GPUThreads > 0 {
|
||||||
|
openclConfig["threads"] = config.GPUThreads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build CUDA (NVIDIA GPU) config
|
||||||
|
cudaConfig := map[string]interface{}{
|
||||||
|
"enabled": config.GPUEnabled && config.CUDA,
|
||||||
|
}
|
||||||
|
if config.GPUEnabled && config.CUDA {
|
||||||
|
if config.GPUIntensity > 0 {
|
||||||
|
cudaConfig["intensity"] = config.GPUIntensity
|
||||||
|
}
|
||||||
|
if config.GPUThreads > 0 {
|
||||||
|
cudaConfig["threads"] = config.GPUThreads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c := map[string]interface{}{
|
c := map[string]interface{}{
|
||||||
"api": map[string]interface{}{
|
"api": map[string]interface{}{
|
||||||
"enabled": m.API != nil && m.API.Enabled,
|
"enabled": m.API != nil && m.API.Enabled,
|
||||||
"listen": apiListen,
|
"listen": apiListen,
|
||||||
"restricted": true,
|
"restricted": true,
|
||||||
},
|
},
|
||||||
"pools": []map[string]interface{}{
|
"pools": pools,
|
||||||
{
|
"cpu": cpuConfig,
|
||||||
"url": config.Pool,
|
"opencl": openclConfig,
|
||||||
"user": config.Wallet,
|
"cuda": cudaConfig,
|
||||||
"pass": "x",
|
|
||||||
"keepalive": true,
|
|
||||||
"tls": config.TLS,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"cpu": cpuConfig,
|
|
||||||
"pause-on-active": config.PauseOnActive,
|
"pause-on-active": config.PauseOnActive,
|
||||||
"pause-on-battery": config.PauseOnBattery,
|
"pause-on-battery": config.PauseOnBattery,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue