Complete private Monero miner control stack.

Implement Windows agent with RandomX mining and WebSocket fleet reporting, wire dashboard settings into the builder with saved exe paths, and add project README.
This commit is contained in:
drjones
2026-05-26 22:51:47 -07:00
commit 6c42f2b600
48 changed files with 10001 additions and 0 deletions

146
agent/miner/pool.go Normal file
View File

@@ -0,0 +1,146 @@
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)
}