219 lines
5.2 KiB
Go
Raw Normal View History

2015-07-05 14:49:07 +05:00
package stratum
import (
"bytes"
"encoding/binary"
"encoding/hex"
"log"
2016-12-07 13:42:41 +05:00
"strconv"
2015-07-05 14:49:07 +05:00
"sync"
"sync/atomic"
2016-12-06 19:25:07 +05:00
"time"
2015-07-05 14:49:07 +05:00
2019-03-09 00:24:32 -08:00
"../cnutil"
"../util"
2015-07-05 14:49:07 +05:00
)
type Job struct {
height int64
2015-07-05 14:49:07 +05:00
sync.RWMutex
2016-12-07 01:54:26 +05:00
id string
extraNonce uint32
submissions map[string]struct{}
2015-07-05 14:49:07 +05:00
}
type Miner struct {
2016-12-07 11:55:29 +05:00
lastBeat int64
2016-12-06 19:25:07 +05:00
startedAt int64
validShares int64
invalidShares int64
staleShares int64
accepts int64
rejects int64
2016-12-06 19:25:07 +05:00
shares map[int64]int64
sync.RWMutex
id string
ip string
2015-07-05 14:49:07 +05:00
}
func (job *Job) submit(nonce string) bool {
job.Lock()
defer job.Unlock()
2016-12-07 12:02:38 +05:00
if _, exist := job.submissions[nonce]; exist {
2015-07-05 14:49:07 +05:00
return true
}
2016-12-07 01:54:26 +05:00
job.submissions[nonce] = struct{}{}
2015-07-05 14:49:07 +05:00
return false
}
2016-12-07 01:29:59 +05:00
func NewMiner(id string, ip string) *Miner {
2016-12-06 20:03:42 +05:00
shares := make(map[int64]int64)
2016-12-07 11:55:29 +05:00
return &Miner{id: id, ip: ip, shares: shares}
2015-07-05 14:49:07 +05:00
}
2016-12-07 01:29:59 +05:00
func (cs *Session) getJob(t *BlockTemplate) *JobReplyData {
2016-12-07 12:00:18 +05:00
height := atomic.SwapInt64(&cs.lastBlockHeight, t.height)
2015-07-05 14:49:07 +05:00
2016-12-07 12:00:18 +05:00
if height == t.height {
2016-12-07 01:29:59 +05:00
return &JobReplyData{}
2015-07-05 14:49:07 +05:00
}
2016-12-07 01:29:59 +05:00
extraNonce := atomic.AddUint32(&cs.endpoint.extraNonce, 1)
blob := t.nextBlob(extraNonce, cs.endpoint.instanceId)
2016-12-07 13:42:41 +05:00
id := atomic.AddUint64(&cs.endpoint.jobSequence, 1)
2016-12-07 16:49:50 +05:00
job := &Job{
id: strconv.FormatUint(id, 10),
extraNonce: extraNonce,
height: t.height,
}
2016-12-07 01:54:26 +05:00
job.submissions = make(map[string]struct{})
2016-12-07 01:29:59 +05:00
cs.pushJob(job)
majorVersion, err := strconv.ParseInt(blob[0:2], 16, 32)
if err != nil {
panic("Failed to get major version")
}
variant := majorVersion - 6
reply := &JobReplyData{
JobId: job.id,
Blob: blob,
Target: cs.endpoint.targetHex,
Height: t.height,
Variant: variant,
}
2016-12-07 01:29:59 +05:00
return reply
2015-07-05 14:49:07 +05:00
}
2016-12-07 01:29:59 +05:00
func (cs *Session) pushJob(job *Job) {
cs.Lock()
defer cs.Unlock()
cs.validJobs = append(cs.validJobs, job)
2015-07-05 14:49:07 +05:00
2016-12-07 01:29:59 +05:00
if len(cs.validJobs) > 4 {
cs.validJobs = cs.validJobs[1:]
2015-07-05 14:49:07 +05:00
}
2016-12-07 01:29:59 +05:00
}
2015-07-05 14:49:07 +05:00
2016-12-07 01:29:59 +05:00
func (cs *Session) findJob(id string) *Job {
cs.Lock()
defer cs.Unlock()
for _, job := range cs.validJobs {
2016-12-07 01:54:26 +05:00
if job.id == id {
2016-12-07 01:29:59 +05:00
return job
}
}
return nil
2015-07-05 14:49:07 +05:00
}
func (m *Miner) heartbeat() {
now := util.MakeTimestamp()
2016-12-07 11:55:29 +05:00
atomic.StoreInt64(&m.lastBeat, now)
2015-07-05 14:49:07 +05:00
}
2016-12-06 19:25:07 +05:00
func (m *Miner) getLastBeat() int64 {
2016-12-07 11:55:29 +05:00
return atomic.LoadInt64(&m.lastBeat)
2016-12-06 19:25:07 +05:00
}
func (m *Miner) storeShare(diff int64) {
2016-12-06 20:03:42 +05:00
now := util.MakeTimestamp() / 1000
2016-12-06 19:25:07 +05:00
m.Lock()
m.shares[now] += diff
m.Unlock()
}
2016-12-06 20:03:42 +05:00
func (m *Miner) hashrate(estimationWindow time.Duration) float64 {
now := util.MakeTimestamp() / 1000
2016-12-06 19:25:07 +05:00
totalShares := int64(0)
2016-12-06 20:03:42 +05:00
window := int64(estimationWindow / time.Second)
2016-12-06 19:25:07 +05:00
boundary := now - m.startedAt
if boundary > window {
boundary = window
}
m.Lock()
for k, v := range m.shares {
2016-12-06 20:03:42 +05:00
if k < now-86400 {
2016-12-06 19:25:07 +05:00
delete(m.shares, k)
} else if k >= now-window {
totalShares += v
}
}
m.Unlock()
return float64(totalShares) / float64(boundary)
}
2016-12-10 18:10:49 +05:00
func (m *Miner) processShare(s *StratumServer, cs *Session, job *Job, t *BlockTemplate, nonce string, result string) bool {
2016-12-06 22:16:57 +05:00
r := s.rpc()
2016-12-07 12:00:18 +05:00
shareBuff := make([]byte, len(t.buffer))
copy(shareBuff, t.buffer)
2016-12-10 18:10:49 +05:00
copy(shareBuff[t.reservedOffset+4:t.reservedOffset+7], cs.endpoint.instanceId)
2015-07-05 14:49:07 +05:00
extraBuff := new(bytes.Buffer)
2016-12-07 01:54:26 +05:00
binary.Write(extraBuff, binary.BigEndian, job.extraNonce)
2016-12-07 12:00:18 +05:00
copy(shareBuff[t.reservedOffset:], extraBuff.Bytes())
2015-07-05 14:49:07 +05:00
nonceBuff, _ := hex.DecodeString(nonce)
copy(shareBuff[39:], nonceBuff)
2016-12-05 21:28:58 +05:00
var hashBytes, convertedBlob []byte
2015-07-05 14:49:07 +05:00
2016-12-05 21:28:58 +05:00
if s.config.BypassShareValidation {
hashBytes, _ = hex.DecodeString(result)
} else {
convertedBlob = cnutil.ConvertBlob(shareBuff)
hashBytes = cnutil.Hash(convertedBlob, false, int(t.height))
2016-12-05 21:28:58 +05:00
}
if !s.config.BypassShareValidation && hex.EncodeToString(hashBytes) != result {
2016-12-10 18:10:49 +05:00
log.Printf("Bad hash from miner %v@%v", m.id, cs.ip)
atomic.AddInt64(&m.invalidShares, 1)
2015-07-05 14:49:07 +05:00
return false
}
2018-03-14 21:28:07 +05:00
hashDiff, ok := util.GetHashDifficulty(hashBytes)
2018-02-10 05:08:43 +05:00
if !ok {
log.Printf("Bad hash from miner %v@%v", m.id, cs.ip)
atomic.AddInt64(&m.invalidShares, 1)
return false
}
block := hashDiff.Cmp(t.difficulty) >= 0
2016-12-06 20:03:42 +05:00
2015-07-05 14:49:07 +05:00
if block {
2016-12-06 22:16:57 +05:00
_, err := r.SubmitBlock(hex.EncodeToString(shareBuff))
2015-07-05 14:49:07 +05:00
if err != nil {
atomic.AddInt64(&m.rejects, 1)
atomic.AddInt64(&r.Rejects, 1)
2016-12-08 13:27:42 +05:00
log.Printf("Block rejected at height %d: %v", t.height, err)
2015-07-05 14:49:07 +05:00
} else {
2016-12-05 21:28:58 +05:00
if len(convertedBlob) == 0 {
convertedBlob = cnutil.ConvertBlob(shareBuff)
2016-12-05 21:28:58 +05:00
}
blockFastHash := hex.EncodeToString(cnutil.FastHash(convertedBlob))
2016-12-07 13:02:35 +05:00
now := util.MakeTimestamp()
roundShares := atomic.SwapInt64(&s.roundShares, 0)
2018-02-10 05:08:43 +05:00
ratio := float64(roundShares) / float64(t.diffInt64)
2016-12-07 13:02:35 +05:00
s.blocksMu.Lock()
2017-03-27 03:54:04 +05:00
s.blockStats[now] = blockEntry{height: t.height, hash: blockFastHash, variance: ratio}
2016-12-07 13:02:35 +05:00
s.blocksMu.Unlock()
atomic.AddInt64(&m.accepts, 1)
atomic.AddInt64(&r.Accepts, 1)
2016-12-07 13:02:35 +05:00
atomic.StoreInt64(&r.LastSubmissionAt, now)
2016-12-10 18:10:49 +05:00
log.Printf("Block %s found at height %d by miner %v@%v with ratio %.4f", blockFastHash[0:6], t.height, m.id, cs.ip, ratio)
2016-12-07 13:02:35 +05:00
// Immediately refresh current BT and send new jobs
s.refreshBlockTemplate(true)
2015-07-05 14:49:07 +05:00
}
2018-02-10 05:08:43 +05:00
} else if hashDiff.Cmp(cs.endpoint.difficulty) < 0 {
2016-12-10 18:10:49 +05:00
log.Printf("Rejected low difficulty share of %v from %v@%v", hashDiff, m.id, cs.ip)
atomic.AddInt64(&m.invalidShares, 1)
2015-07-05 14:49:07 +05:00
return false
}
2017-08-30 09:30:47 +05:00
atomic.AddInt64(&s.roundShares, cs.endpoint.config.Difficulty)
atomic.AddInt64(&m.validShares, 1)
2017-08-30 09:30:47 +05:00
m.storeShare(cs.endpoint.config.Difficulty)
2016-12-10 18:10:49 +05:00
log.Printf("Valid share at difficulty %v/%v", cs.endpoint.config.Difficulty, hashDiff)
2015-07-05 14:49:07 +05:00
return true
}