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) }