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