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.

153 lines
3.9 KiB

9 years ago
package stratum
import (
"log"
"regexp"
"strings"
"sync/atomic"
9 years ago
"../util"
)
var noncePattern *regexp.Regexp
const defaultWorkerId = "0"
9 years ago
func init() {
noncePattern, _ = regexp.Compile("^[0-9a-f]{8}$")
}
func (s *StratumServer) handleLoginRPC(cs *Session, e *Endpoint, params *LoginParams) (*JobReply, *ErrorReply) {
if !s.config.BypassAddressValidation && !util.ValidateAddress(params.Login, s.config.Address) {
return nil, &ErrorReply{Code: -1, Message: "Invalid address used for login", Close: true}
9 years ago
}
t := s.currentBlockTemplate()
if t == nil {
return nil, &ErrorReply{Code: -1, Message: "Job not ready", Close: true}
}
id := extractWorkerId(params.Login)
miner, ok := s.miners.Get(id)
if !ok {
miner = NewMiner(id, e.config.Difficulty, cs.ip)
}
log.Printf("Miner connected %s@%s", id, cs.ip)
9 years ago
miner.Session = cs
miner.Endpoint = e
9 years ago
s.registerMiner(miner)
miner.heartbeat()
return &JobReply{Id: id, Job: miner.getJob(t, true), Status: "OK"}, nil
9 years ago
}
func (s *StratumServer) handleGetJobRPC(cs *Session, e *Endpoint, params *GetJobParams) (reply *JobReplyData, errorReply *ErrorReply) {
9 years ago
miner, ok := s.miners.Get(params.Id)
if !ok {
errorReply = &ErrorReply{Code: -1, Message: "Unauthenticated", Close: true}
return
}
t := s.currentBlockTemplate()
if t == nil {
errorReply = &ErrorReply{Code: -1, Message: "Job not ready", Close: true}
return
}
9 years ago
miner.heartbeat()
reply = miner.getJob(t, false)
9 years ago
return
}
func (s *StratumServer) handleSubmitRPC(cs *Session, e *Endpoint, params *SubmitParams) (reply *SubmitReply, errorReply *ErrorReply) {
9 years ago
miner, ok := s.miners.Get(params.Id)
if !ok {
errorReply = &ErrorReply{Code: -1, Message: "Unauthenticated", Close: true}
return
}
miner.heartbeat()
job := miner.findJob(params.JobId)
if job == nil {
errorReply = &ErrorReply{Code: -1, Message: "Invalid job id", Close: true}
atomic.AddUint64(&miner.invalidShares, 1)
9 years ago
return
}
if !noncePattern.MatchString(params.Nonce) {
errorReply = &ErrorReply{Code: -1, Message: "Malformed nonce", Close: true}
atomic.AddUint64(&miner.invalidShares, 1)
9 years ago
return
}
nonce := strings.ToLower(params.Nonce)
exist := job.submit(nonce)
if exist {
errorReply = &ErrorReply{Code: -1, Message: "Duplicate share", Close: true}
atomic.AddUint64(&miner.invalidShares, 1)
9 years ago
return
}
t := s.currentBlockTemplate()
if job.Height != t.Height {
log.Printf("Block expired for height %v %s@%s", job.Height, miner.Id, miner.IP)
9 years ago
errorReply = &ErrorReply{Code: -1, Message: "Block expired", Close: false}
atomic.AddUint64(&miner.staleShares, 1)
9 years ago
return
}
validShare := miner.processShare(s, e, job, t, nonce, params.Result)
9 years ago
if !validShare {
errorReply = &ErrorReply{Code: -1, Message: "Low difficulty share", Close: !ok}
return
}
reply = &SubmitReply{Status: "OK"}
return
}
func (s *StratumServer) handleUnknownRPC(cs *Session, req *JSONRpcReq) *ErrorReply {
log.Printf("Unknown RPC method: %v", req)
return &ErrorReply{Code: -1, Message: "Invalid method", Close: true}
}
func (s *StratumServer) broadcastNewJobs() {
t := s.currentBlockTemplate()
if t == nil {
return
}
9 years ago
log.Printf("Broadcasting new jobs to %v miners", s.miners.Count())
bcast := make(chan int, 1024*16)
9 years ago
n := 0
for m := range s.miners.IterBuffered() {
n++
bcast <- n
go func(miner *Miner) {
reply := miner.getJob(t, true)
9 years ago
err := miner.Session.pushMessage("job", &reply)
<-bcast
if err != nil {
log.Printf("Job transmit error to %v@%v: %v", miner.Id, miner.IP, err)
9 years ago
s.removeMiner(miner.Id)
} else {
s.setDeadline(miner.Session.conn)
9 years ago
}
}(m.Val)
}
}
func (s *StratumServer) refreshBlockTemplate(bcast bool) {
newBlock := s.fetchBlockTemplate()
if newBlock && bcast {
s.broadcastNewJobs()
}
}
func extractWorkerId(loginWorkerPair string) string {
parts := strings.SplitN(loginWorkerPair, ".", 2)
if len(parts) > 1 {
return parts[1]
}
return defaultWorkerId
}