Implement Windows agent with RandomX mining and WebSocket fleet reporting, wire dashboard settings into the builder with saved exe paths, and add project README.
147 lines
2.7 KiB
Go
147 lines
2.7 KiB
Go
package miner
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"log"
|
|
"math/big"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"crypto-miner-agent/job"
|
|
)
|
|
|
|
type ShareHandler func(jobID, nonce, hash string)
|
|
|
|
type Pool struct {
|
|
threads int
|
|
engine *Engine
|
|
handler ShareHandler
|
|
|
|
mu sync.RWMutex
|
|
currentJob *job.Job
|
|
stopCh chan struct{}
|
|
wg sync.WaitGroup
|
|
|
|
hashesTotal atomic.Uint64
|
|
sharesFound atomic.Uint64
|
|
}
|
|
|
|
func NewPool(threads int, handler ShareHandler) *Pool {
|
|
if threads <= 0 {
|
|
threads = 1
|
|
}
|
|
return &Pool{
|
|
threads: threads,
|
|
engine: NewEngine(),
|
|
handler: handler,
|
|
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)
|
|
}
|
|
}
|
|
|
|
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) worker(id int) {
|
|
defer p.wg.Done()
|
|
|
|
var nonce uint32 = uint32(id * 1000000)
|
|
|
|
for {
|
|
select {
|
|
case <-p.stopCh:
|
|
return
|
|
default:
|
|
}
|
|
|
|
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:
|
|
}
|
|
|
|
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 hex.EncodeToString(b)
|
|
}
|
|
|
|
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)
|
|
}
|