Kevacoin stratum server for solo-mining
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

143 lines
3.5 KiB

package stratum
import (
"bytes"
"encoding/binary"
"encoding/hex"
"log"
"sync"
"sync/atomic"
"../../cnutil"
"../../hashing"
"../util"
)
type Job struct {
sync.RWMutex
Id string
ExtraNonce uint32
Height int64
Difficulty int64
Submissions map[string]bool
}
type Miner struct {
sync.RWMutex
Id string
Login string
Pass string
IP string
Difficulty int64
ValidJobs []*Job
LastBlockHeight int64
Target uint32
TargetHex string
LastBeat int64
Session *Session
}
func (job *Job) submit(nonce string) bool {
job.Lock()
defer job.Unlock()
_, exist := job.Submissions[nonce]
if exist {
return true
}
job.Submissions[nonce] = true
return false
}
func NewMiner(login, pass string, diff int64, ip string) *Miner {
id := util.Random()
miner := &Miner{Id: id, Login: login, Pass: pass, Difficulty: diff, IP: ip}
target, targetHex := util.GetTargetHex(diff)
miner.Target = target
miner.TargetHex = targetHex
return miner
}
func (m *Miner) pushJob(job *Job) {
m.Lock()
defer m.Unlock()
m.ValidJobs = append(m.ValidJobs, job)
if len(m.ValidJobs) > 4 {
m.ValidJobs = m.ValidJobs[1:]
}
}
func (m *Miner) getJob(s *StratumServer) *JobReplyData {
t := s.currentBlockTemplate()
height := atomic.SwapInt64(&m.LastBlockHeight, t.Height)
if height == t.Height {
return &JobReplyData{}
}
blob, extraNonce := t.nextBlob()
job := &Job{Id: util.Random(), ExtraNonce: extraNonce, Height: t.Height, Difficulty: m.Difficulty}
job.Submissions = make(map[string]bool)
m.pushJob(job)
reply := &JobReplyData{JobId: job.Id, Blob: blob, Target: m.TargetHex}
return reply
}
func (m *Miner) heartbeat() {
now := util.MakeTimestamp()
atomic.StoreInt64(&m.LastBeat, now)
}
func (m *Miner) findJob(id string) *Job {
m.RLock()
defer m.RUnlock()
for _, job := range m.ValidJobs {
if job.Id == id {
return job
}
}
return nil
}
func (m *Miner) processShare(s *StratumServer, job *Job, t *BlockTemplate, nonce string, result string) bool {
shareBuff := make([]byte, len(t.Buffer))
copy(shareBuff, t.Buffer)
extraBuff := new(bytes.Buffer)
binary.Write(extraBuff, binary.BigEndian, job.ExtraNonce)
copy(shareBuff[t.ReservedOffset:], extraBuff.Bytes())
nonceBuff, _ := hex.DecodeString(nonce)
copy(shareBuff[39:], nonceBuff)
convertedBlob := cnutil.ConvertBlob(shareBuff)
hashBytes := hashing.Hash(convertedBlob, false)
if hex.EncodeToString(hashBytes) != result {
log.Printf("Bad hash from miner %v@%v", m.Login, m.IP)
return false
}
hashDiff := util.GetHashDifficulty(hashBytes).Int64() // FIXME: Will return max int64 value if overflows
block := hashDiff >= t.Difficulty
if block {
_, err := s.rpc.SubmitBlock(hex.EncodeToString(shareBuff))
if err != nil {
log.Printf("Block submission failure at height %v: %v", t.Height, err)
} else {
blockFastHash := hex.EncodeToString(hashing.FastHash(convertedBlob))
s.storage.WriteBlock(m.Login, job.Difficulty, t.Difficulty, t.Height, blockFastHash)
// Immediately refresh current BT and send new jobs
s.refreshBlockTemplate(true)
log.Printf("Block %v found at height %v by miner %v@%v", blockFastHash[0:6], t.Height, m.Login, m.IP)
}
} else if hashDiff < job.Difficulty {
log.Printf("Rejected low difficulty share of %v from %v@%v", hashDiff, m.Login, m.IP)
return false
} else {
s.storage.WriteShare(m.Login, job.Difficulty)
}
log.Printf("Valid share at difficulty %v/%v", s.port.Difficulty, hashDiff)
return true
}