Browse Source

"EthereumStratum/1.0.0 + ASIC

asic tested A10 Pro 500mh 5G/ A10 Pro 720mh 7G/ and A11 1500mh 8G

=)
master
yuriy0803 3 years ago
parent
commit
3411635ec5
  1. 65
      proxy/handlers.go
  2. 73
      proxy/miner.go
  3. 12
      proxy/proto.go
  4. 25
      proxy/proxy.go
  5. 395
      proxy/stratum.go
  6. 22
      storage/redis.go
  7. 14
      util/util.go

65
proxy/handlers.go

@ -1,7 +1,6 @@
package proxy package proxy
import ( import (
"errors"
"log" "log"
"regexp" "regexp"
"strings" "strings"
@ -21,16 +20,19 @@ func (s *ProxyServer) handleLoginRPC(cs *Session, params []string, id string) (b
return false, &ErrorReply{Code: -1, Message: "Invalid params"} return false, &ErrorReply{Code: -1, Message: "Invalid params"}
} }
//If login contain information about workers name "walletId.workerName"
login := params[0] login := params[0]
if strings.Contains(login, ".") { // WORKER NAME 0x1234.WORKERNAME
var workerParams = strings.Split(login, ".") if strings.ContainsAny(login, ".") {
login = workerParams[0] var param = strings.Split(login, ".")
id = workerParams[1] login = param[0]
id = param[1]
} }
login = strings.ToLower(login) login = strings.ToLower(login)
if !workerPattern.MatchString(id) {
id = "0"
}
if !util.IsValidHexAddress(login) { if !util.IsValidHexAddress(login) {
return false, &ErrorReply{Code: -1, Message: "Invalid login"} return false, &ErrorReply{Code: -1, Message: "Invalid login"}
} }
@ -38,13 +40,8 @@ func (s *ProxyServer) handleLoginRPC(cs *Session, params []string, id string) (b
return false, &ErrorReply{Code: -1, Message: "You are blacklisted"} return false, &ErrorReply{Code: -1, Message: "You are blacklisted"}
} }
if !workerPattern.MatchString(id) {
id = "0"
}
cs.worker = id
cs.login = login cs.login = login
cs.worker = id
s.registerSession(cs) s.registerSession(cs)
log.Printf("Stratum miner connected %v@%v", login, cs.ip) log.Printf("Stratum miner connected %v@%v", login, cs.ip)
return true, nil return true, nil
@ -55,7 +52,7 @@ func (s *ProxyServer) handleGetWorkRPC(cs *Session) ([]string, *ErrorReply) {
if t == nil || len(t.Header) == 0 || s.isSick() { if t == nil || len(t.Header) == 0 || s.isSick() {
return nil, &ErrorReply{Code: 0, Message: "Work not ready"} return nil, &ErrorReply{Code: 0, Message: "Work not ready"}
} }
return []string{t.Header, t.Seed, s.diff}, nil return []string{t.Header, t.Seed, s.diff, util.ToHex(int64(t.Height))}, nil
} }
// Stratum // Stratum
@ -78,52 +75,48 @@ func (s *ProxyServer) handleSubmitRPC(cs *Session, login, id string, params []st
return false, &ErrorReply{Code: -1, Message: "Invalid params"} return false, &ErrorReply{Code: -1, Message: "Invalid params"}
} }
stratumMode := cs.stratumMode()
if stratumMode == NiceHash {
for i := 0; i <= 2; i++ {
if params[i][0:2] != "0x" {
params[i] = "0x" + params[i]
}
}
}
if !noncePattern.MatchString(params[0]) || !hashPattern.MatchString(params[1]) || !hashPattern.MatchString(params[2]) { if !noncePattern.MatchString(params[0]) || !hashPattern.MatchString(params[1]) || !hashPattern.MatchString(params[2]) {
s.policy.ApplyMalformedPolicy(cs.ip) s.policy.ApplyMalformedPolicy(cs.ip)
log.Printf("Malformed PoW result from %s@%s %v", login, cs.ip, params) log.Printf("Malformed PoW result from %s@%s %v", login, cs.ip, params)
return false, &ErrorReply{Code: -1, Message: "Malformed PoW result"} return false, &ErrorReply{Code: -1, Message: "Malformed PoW result"}
} }
go func(s *ProxyServer, cs *Session, login, id string, params []string) {
t := s.currentBlockTemplate() t := s.currentBlockTemplate()
exist, validShare := s.processShare(login, id, cs.ip, t, params, stratumMode != EthProxy)
//MFO: This function (s.processShare) will process a share as per hasher.Verify function of github.com/ethereum/ethash
// output of this function is either:
// true,true (Exists) which means share already exists and it is validShare
// true,false (Exists & invalid)which means share already exists and it is invalidShare or it is a block <-- should not ever happen
// false,false (stale/invalid)which means share is new, and it is not a block, might be a stale share or invalidShare
// false,true (valid)which means share is new, and it is a block or accepted share
// false,false,false (blacklisted wallet attached to share) see json file
// When this function finishes, the results is already recorded in the db for valid shares or blocks.
exist, validShare := s.processShare(login, id, cs.ip, t, params)
ok := s.policy.ApplySharePolicy(cs.ip, !exist && validShare) ok := s.policy.ApplySharePolicy(cs.ip, !exist && validShare)
// if true,true or true,false
if exist { if exist {
log.Printf("Duplicate share from %s@%s %v", login, cs.ip, params) log.Printf("Duplicate share from %s@%s %v", login, cs.ip, params)
cs.lastErr = errors.New("Duplicate share") // see https://github.com/sammy007/open-ethereum-pool/compare/master...nicehashdev:patch-1
if !ok {
return false, &ErrorReply{Code: 23, Message: "Invalid share"}
}
return false, nil
} }
// if false, false
if !validShare { if !validShare {
//MFO: Here we have an invalid share
log.Printf("Invalid share from %s@%s", login, cs.ip) log.Printf("Invalid share from %s@%s", login, cs.ip)
// Bad shares limit reached, return error and close // Bad shares limit reached, return error and close
if !ok { if !ok {
cs.lastErr = errors.New("Invalid share") return false, &ErrorReply{Code: 23, Message: "Invalid share"}
} }
return false, nil
} }
//MFO: Here we have a valid share and it is already recorded in DB by miner.go
// if false, true
if s.config.Proxy.Debug { if s.config.Proxy.Debug {
log.Printf("Valid share from %s@%s", login, cs.ip) log.Printf("Valid share from %s@%s", login, cs.ip)
} }
if !ok { if !ok {
cs.lastErr = errors.New("High rate of invalid shares") return true, &ErrorReply{Code: -1, Message: "High rate of invalid shares"}
} }
}(s, cs, login, id, params)
return true, nil return true, nil
} }

73
proxy/miner.go

@ -8,6 +8,7 @@ import (
"github.com/etclabscore/core-geth/common" "github.com/etclabscore/core-geth/common"
"github.com/yuriy0803/etchash" "github.com/yuriy0803/etchash"
"github.com/yuriy0803/open-etc-pool-friends/util"
) )
var ( var (
@ -18,7 +19,7 @@ var (
hasher *etchash.Etchash = nil hasher *etchash.Etchash = nil
) )
func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, params []string) (bool, bool) { func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, params []string, stratum bool) (bool, bool) {
if hasher == nil { if hasher == nil {
if s.config.Network == "classic" { if s.config.Network == "classic" {
@ -42,27 +43,24 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param
nonce, _ := strconv.ParseUint(strings.Replace(nonceHex, "0x", "", -1), 16, 64) nonce, _ := strconv.ParseUint(strings.Replace(nonceHex, "0x", "", -1), 16, 64)
shareDiff := s.config.Proxy.Difficulty shareDiff := s.config.Proxy.Difficulty
h, ok := t.headers[hashNoNonce] var result common.Hash
if !ok { if stratum {
log.Printf("Stale share from %v@%v", login, ip) hashNoNonceTmp := common.HexToHash(params[2])
s.backend.WriteWorkerShareStatus(login, id, false, true, false)
return false, false
}
share := Block{ mixDigestTmp, hashTmp := hasher.Compute(t.Height, hashNoNonceTmp, nonce)
number: h.height, params[1] = hashNoNonceTmp.Hex()
hashNoNonce: common.HexToHash(hashNoNonce), params[2] = mixDigestTmp.Hex()
difficulty: big.NewInt(shareDiff), hashNoNonce = params[1]
nonce: nonce, result = hashTmp
mixDigest: common.HexToHash(mixDigest), } else {
} hashNoNonceTmp := common.HexToHash(hashNoNonce)
mixDigestTmp, hashTmp := hasher.Compute(t.Height, hashNoNonceTmp, nonce)
block := Block{ // check mixDigest
number: h.height, if mixDigestTmp.Hex() != mixDigest {
hashNoNonce: common.HexToHash(hashNoNonce), return false, false
difficulty: h.diff, }
nonce: nonce, result = hashTmp
mixDigest: common.HexToHash(mixDigest),
} }
//this is to stop people in wallet blacklist, from getting shares into the db. //this is to stop people in wallet blacklist, from getting shares into the db.
@ -74,20 +72,37 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param
//return codes need work here, a lot of it. //return codes need work here, a lot of it.
} }
isShare, actualDiff := hasher.Verify(share) // Block "difficulty" is BigInt
// NiceHash "difficulty" is float64 ...
// diffFloat => target; then: diffInt = 2^256 / target
shareDiffCalc := util.TargetHexToDiff(result.Hex()).Int64()
shareDiffFloat := util.DiffIntToFloat(shareDiffCalc)
if shareDiffFloat < 0.0001 {
log.Printf("share difficulty too low, %f < %d, from %v@%v", shareDiffFloat, t.Difficulty, login, ip)
s.backend.WriteWorkerShareStatus(login, id, false, true, false)
return false, false
}
if s.config.Proxy.Debug { if s.config.Proxy.Debug {
log.Printf("Difficulty pool Port/Shares found/Block difficulty = %d / %d / %d from %v@%v", shareDiff, actualDiff, t.Difficulty, login, ip) log.Printf("Difficulty pool/block/share = %d / %d / %d(%f) from %v@%v", shareDiff, t.Difficulty, shareDiffCalc, shareDiffFloat, login, ip)
} }
if !isShare { h, ok := t.headers[hashNoNonce]
s.backend.WriteWorkerShareStatus(login, id, false, false, true) if !ok {
log.Printf("Stale share from %v@%v", login, ip)
s.backend.WriteWorkerShareStatus(login, id, false, true, false)
return false, false return false, false
} }
isBlock, _ := hasher.Verify(block) // check share difficulty
shareTarget := new(big.Int).Div(maxUint256, big.NewInt(shareDiff))
if isBlock { if result.Big().Cmp(shareTarget) > 0 {
s.backend.WriteWorkerShareStatus(login, id, false, false, true)
return false, false
}
// check target difficulty
target := new(big.Int).Div(maxUint256, big.NewInt(h.diff.Int64()))
if result.Big().Cmp(target) <= 0 {
ok, err := s.rpc().SubmitBlock(params) ok, err := s.rpc().SubmitBlock(params)
if err != nil { if err != nil {
log.Printf("Block submission failure at height %v for %v: %v", h.height, t.Header, err) log.Printf("Block submission failure at height %v for %v: %v", h.height, t.Header, err)
@ -96,7 +111,7 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param
return false, false return false, false
} else { } else {
s.fetchBlockTemplate() s.fetchBlockTemplate()
exist, err := s.backend.WriteBlock(login, id, params, shareDiff, actualDiff, h.diff.Int64(), h.height, s.hashrateExpiration) exist, err := s.backend.WriteBlock(login, id, params, shareDiff, shareDiffCalc, h.diff.Int64(), h.height, s.hashrateExpiration)
if exist { if exist {
return true, false return true, false
} }
@ -108,7 +123,7 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param
log.Printf("Block found by miner %v@%v at height %d", login, ip, h.height) log.Printf("Block found by miner %v@%v at height %d", login, ip, h.height)
} }
} else { } else {
exist, err := s.backend.WriteShare(login, id, params, shareDiff, actualDiff, h.height, s.hashrateExpiration) exist, err := s.backend.WriteShare(login, id, params, shareDiff, shareDiffCalc, h.height, s.hashrateExpiration)
if exist { if exist {
return true, false return true, false
} }

12
proxy/proto.go

@ -8,6 +8,14 @@ type JSONRpcReq struct {
Params json.RawMessage `json:"params"` Params json.RawMessage `json:"params"`
} }
type JSONStratumReq struct {
Id interface{} `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
Height string `json:"height"`
Algo string `json:"algo"`
}
type StratumReq struct { type StratumReq struct {
JSONRpcReq JSONRpcReq
Worker string `json:"worker"` Worker string `json:"worker"`
@ -23,9 +31,9 @@ type JSONPushMessage struct {
type JSONRpcResp struct { type JSONRpcResp struct {
Id json.RawMessage `json:"id"` Id json.RawMessage `json:"id"`
Version string `json:"jsonrpc"` Version string `json:"jsonrpc,omitempty"`
Result interface{} `json:"result"` Result interface{} `json:"result"`
Error interface{} `json:"error,omitempty"` Error interface{} `json:"error"`
} }
type SubmitReply struct { type SubmitReply struct {

25
proxy/proxy.go

@ -35,6 +35,13 @@ type ProxyServer struct {
sessionsMu sync.RWMutex sessionsMu sync.RWMutex
sessions map[*Session]struct{} sessions map[*Session]struct{}
timeout time.Duration timeout time.Duration
// Extranonce
Extranonces map[string]bool
}
type staleJob struct {
SeedHash string
HeaderHash string
} }
type Session struct { type Session struct {
@ -46,7 +53,22 @@ type Session struct {
conn net.Conn conn net.Conn
login string login string
worker string worker string
lastErr error stratum int
subscriptionID string
JobDeatils jobDetails
Extranonce string
ExtranonceSub bool
JobDetails jobDetails
staleJobs map[string]staleJob
staleJobIDs []string
}
type jobDetails struct {
JobID string
SeedHash string
HeaderHash string
Height string
Epoch int64
} }
func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer { func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer {
@ -67,6 +89,7 @@ func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer {
if cfg.Proxy.Stratum.Enabled { if cfg.Proxy.Stratum.Enabled {
proxy.sessions = make(map[*Session]struct{}) proxy.sessions = make(map[*Session]struct{})
proxy.Extranonces = make(map[string]bool)
go proxy.ListenTCP() go proxy.ListenTCP()
} }

395
proxy/stratum.go

@ -7,7 +7,10 @@ import (
"errors" "errors"
"io" "io"
"log" "log"
"math/rand"
"net" "net"
"strconv"
"strings"
"time" "time"
"github.com/yuriy0803/open-etc-pool-friends/util" "github.com/yuriy0803/open-etc-pool-friends/util"
@ -17,12 +20,16 @@ const (
MaxReqSize = 1024 MaxReqSize = 1024
) )
const (
EthProxy int = iota
NiceHash
)
func (s *ProxyServer) ListenTCP() { func (s *ProxyServer) ListenTCP() {
s.timeout = util.MustParseDuration(s.config.Proxy.Stratum.Timeout) s.timeout = util.MustParseDuration(s.config.Proxy.Stratum.Timeout)
var err error var err error
var server net.Listener var server net.Listener
setKeepAlive := func(net.Conn) {}
if s.config.Proxy.Stratum.TLS { if s.config.Proxy.Stratum.TLS {
var cert tls.Certificate var cert tls.Certificate
cert, err = tls.LoadX509KeyPair(s.config.Proxy.Stratum.CertFile, s.config.Proxy.Stratum.KeyFile) cert, err = tls.LoadX509KeyPair(s.config.Proxy.Stratum.CertFile, s.config.Proxy.Stratum.KeyFile)
@ -30,14 +37,10 @@ func (s *ProxyServer) ListenTCP() {
log.Fatalln("Error loading certificate:", err) log.Fatalln("Error loading certificate:", err)
} }
tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}} tlsCfg := &tls.Config{Certificates: []tls.Certificate{cert}}
server, err = tls.Listen("tcp4", s.config.Proxy.Stratum.Listen, tlsCfg) server, err = tls.Listen("tcp", s.config.Proxy.Stratum.Listen, tlsCfg)
} else { } else {
server, err = net.Listen("tcp4", s.config.Proxy.Stratum.Listen) server, err = net.Listen("tcp", s.config.Proxy.Stratum.Listen)
setKeepAlive = func(conn net.Conn) {
conn.(*net.TCPConn).SetKeepAlive(true)
} }
}
if err != nil { if err != nil {
log.Fatalf("Error: %v", err) log.Fatalf("Error: %v", err)
} }
@ -52,8 +55,6 @@ func (s *ProxyServer) ListenTCP() {
if err != nil { if err != nil {
continue continue
} }
setKeepAlive(conn)
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
if s.policy.IsBanned(ip) || !s.policy.ApplyLimitPolicy(ip) { if s.policy.IsBanned(ip) || !s.policy.ApplyLimitPolicy(ip) {
@ -61,12 +62,16 @@ func (s *ProxyServer) ListenTCP() {
continue continue
} }
n += 1 n += 1
cs := &Session{conn: conn, ip: ip} // make unique extranonce
extranonce := s.uniqExtranonce()
cs := &Session{conn: conn, ip: ip, Extranonce: extranonce, ExtranonceSub: false, stratum: -1}
// allocate stales cache
cs.staleJobs = make(map[string]staleJob)
accept <- n accept <- n
go func(cs *Session) { go func(cs *Session) {
err = s.handleTCPClient(cs) err = s.handleTCPClient(cs)
if err != nil || cs.lastErr != nil { if err != nil {
s.removeSession(cs) s.removeSession(cs)
conn.Close() conn.Close()
} }
@ -79,6 +84,7 @@ func (s *ProxyServer) handleTCPClient(cs *Session) error {
cs.enc = json.NewEncoder(cs.conn) cs.enc = json.NewEncoder(cs.conn)
connbuff := bufio.NewReaderSize(cs.conn, MaxReqSize) connbuff := bufio.NewReaderSize(cs.conn, MaxReqSize)
s.setDeadline(cs.conn) s.setDeadline(cs.conn)
for { for {
data, isPrefix, err := connbuff.ReadLine() data, isPrefix, err := connbuff.ReadLine()
if isPrefix { if isPrefix {
@ -112,13 +118,29 @@ func (s *ProxyServer) handleTCPClient(cs *Session) error {
return nil return nil
} }
func (cs *Session) setStratumMode(str string) error {
switch str {
case "EthereumStratum/1.0.0":
cs.stratum = NiceHash
break
default:
cs.stratum = EthProxy
break
}
return nil
}
func (cs *Session) stratumMode() int {
return cs.stratum
}
func (cs *Session) handleTCPMessage(s *ProxyServer, req *StratumReq) error { func (cs *Session) handleTCPMessage(s *ProxyServer, req *StratumReq) error {
// Handle RPC methods // Handle RPC/Stratum methods
switch req.Method { switch req.Method {
case "eth_submitLogin": case "eth_submitLogin":
var params []string var params []string
err := json.Unmarshal(req.Params, &params) err := json.Unmarshal(req.Params, &params)
if err != nil { if err != nil || len(params) < 1 {
log.Println("Malformed stratum request params from", cs.ip) log.Println("Malformed stratum request params from", cs.ip)
return err return err
} }
@ -126,7 +148,178 @@ func (cs *Session) handleTCPMessage(s *ProxyServer, req *StratumReq) error {
if errReply != nil { if errReply != nil {
return cs.sendTCPError(req.Id, errReply) return cs.sendTCPError(req.Id, errReply)
} }
cs.setStratumMode("EthProxy")
log.Println("EthProxy login", cs.ip)
return cs.sendTCPResult(req.Id, reply) return cs.sendTCPResult(req.Id, reply)
case "mining.subscribe":
var params []string
err := json.Unmarshal(req.Params, &params)
if err != nil || len(params) < 2 {
log.Println("Malformed stratum request params from", cs.ip)
return err
}
if params[1] != "EthereumStratum/1.0.0" {
log.Println("Unsupported stratum version from ", cs.ip)
return cs.sendStratumError(req.Id, "unsupported stratum version")
}
cs.ExtranonceSub = true
cs.setStratumMode("EthereumStratum/1.0.0")
log.Println("Nicehash subscribe", cs.ip)
result := cs.getNotificationResponse(s)
return cs.sendStratumResult(req.Id, result)
default:
switch cs.stratumMode() {
case 0:
break
case 1:
break
default:
errReply := s.handleUnknownRPC(cs, req.Method)
return cs.sendTCPError(req.Id, errReply)
}
}
if cs.stratumMode() == NiceHash {
switch req.Method {
case "mining.authorize":
var params []string
err := json.Unmarshal(req.Params, &params)
if err != nil || len(params) < 1 {
return errors.New("invalid params")
}
splitData := strings.Split(params[0], ".")
params[0] = splitData[0]
reply, errReply := s.handleLoginRPC(cs, params, req.Worker)
if errReply != nil {
return cs.sendStratumError(req.Id, []string{
string(errReply.Code),
errReply.Message,
})
}
if err := cs.sendStratumResult(req.Id, reply); err != nil {
return err
}
paramsDiff := []float64{
util.DiffIntToFloat(s.config.Proxy.Difficulty),
}
respReq := JSONStratumReq{Method: "mining.set_difficulty", Params: paramsDiff}
if err := cs.sendTCPReq(respReq); err != nil {
return err
}
return cs.sendJob(s, req.Id, true)
case "mining.extranonce.subscribe":
var params []string
err := json.Unmarshal(req.Params, &params)
if err != nil {
return errors.New("invalid params")
}
if len(params) == 0 {
if err := cs.sendStratumResult(req.Id, true); err != nil {
return err
}
cs.ExtranonceSub = true
req := JSONStratumReq{
Id: nil,
Method: "mining.set_extranonce",
Params: []interface{}{
cs.Extranonce,
},
}
return cs.sendTCPReq(req)
}
return cs.sendStratumError(req.Id, []string{
"20",
"Not supported.",
})
case "mining.submit":
var params []string
err := json.Unmarshal(req.Params, &params)
if err != nil || len(params) < 3 {
log.Println("mining.submit: json.Unmarshal fail")
return err
}
// params[0] = Username
// params[1] = Job ID
// params[2] = Minernonce
// Reference:
// https://github.com/nicehash/nhethpool/blob/060817a9e646cd9f1092647b870ed625ee138ab4/nhethpool/EthereumInstance.cs#L369
// WORKER NAME MANDATORY 0x1234.WORKERNAME
splitData := strings.Split(params[0], ".")
id := "0"
if len(splitData) > 1 {
id = splitData[1]
}
cs.worker = id
// check Extranonce subscription.
extranonce := cs.Extranonce
if !cs.ExtranonceSub {
extranonce = ""
}
nonce := extranonce + params[2]
if cs.JobDetails.JobID != params[1] {
stale, ok := cs.staleJobs[params[1]]
if ok {
log.Printf("Cached stale JobID %s", params[1])
params = []string{
nonce,
stale.SeedHash,
stale.HeaderHash,
}
} else {
log.Printf("Stale share (mining.submit JobID received %s != current %s)", params[1], cs.JobDetails.JobID)
if err := cs.sendStratumError(req.Id, []string{"21", "Stale share."}); err != nil {
return err
}
return cs.sendJob(s, req.Id, false)
}
} else {
params = []string{
nonce,
cs.JobDetails.SeedHash,
cs.JobDetails.HeaderHash,
}
}
reply, errReply := s.handleTCPSubmitRPC(cs, id, params)
if errReply != nil {
log.Println("mining.submit: handleTCPSubmitRPC failed")
return cs.sendStratumError(req.Id, []string{
strconv.Itoa(errReply.Code),
errReply.Message,
})
}
// TEST, ein notify zu viel
//if err := cs.sendTCPResult(resp); err != nil {
// return err
//}
//return cs.sendJob(s, req.Id)
return cs.sendStratumResult(req.Id, reply)
default:
errReply := s.handleUnknownRPC(cs, req.Method)
return cs.sendStratumError(req.Id, []string{
strconv.Itoa(errReply.Code),
errReply.Message,
})
}
}
switch req.Method {
case "eth_getWork": case "eth_getWork":
reply, errReply := s.handleGetWorkRPC(cs) reply, errReply := s.handleGetWorkRPC(cs)
if errReply != nil { if errReply != nil {
@ -136,7 +329,7 @@ func (cs *Session) handleTCPMessage(s *ProxyServer, req *StratumReq) error {
case "eth_submitWork": case "eth_submitWork":
var params []string var params []string
err := json.Unmarshal(req.Params, &params) err := json.Unmarshal(req.Params, &params)
if err != nil { if err != nil || len(params) < 3 {
log.Println("Malformed stratum request params from", cs.ip) log.Println("Malformed stratum request params from", cs.ip)
return err return err
} }
@ -161,9 +354,71 @@ func (cs *Session) sendTCPResult(id json.RawMessage, result interface{}) error {
return cs.enc.Encode(&message) return cs.enc.Encode(&message)
} }
func (cs *Session) pushNewJob(result interface{}) error { // cache stale jobs
func (cs *Session) cacheStales(max, n int) {
l := len(cs.staleJobIDs)
// remove outdated stales except last n caches if l > max
if l > max {
save := cs.staleJobIDs[l-n : l]
del := cs.staleJobIDs[0 : l-n]
for _, v := range del {
delete(cs.staleJobs, v)
}
cs.staleJobIDs = save
}
// save stales cache
cs.staleJobs[cs.JobDetails.JobID] = staleJob{
cs.JobDetails.SeedHash,
cs.JobDetails.HeaderHash,
}
cs.staleJobIDs = append(cs.staleJobIDs, cs.JobDetails.JobID)
}
func (cs *Session) pushNewJob(s *ProxyServer, result interface{}) error {
cs.Lock() cs.Lock()
defer cs.Unlock() defer cs.Unlock()
if cs.stratumMode() == NiceHash {
cs.cacheStales(10, 3)
t := result.(*[]string)
cs.JobDetails = jobDetails{
JobID: randomHex(8),
SeedHash: (*t)[1],
HeaderHash: (*t)[0],
Height: (*t)[3],
}
// strip 0x prefix
if cs.JobDetails.SeedHash[0:2] == "0x" {
cs.JobDetails.SeedHash = cs.JobDetails.SeedHash[2:]
cs.JobDetails.HeaderHash = cs.JobDetails.HeaderHash[2:]
}
a := s.currentBlockTemplate()
resp := JSONStratumReq{
Method: "mining.notify",
Params: []interface{}{
cs.JobDetails.JobID,
cs.JobDetails.SeedHash,
cs.JobDetails.HeaderHash,
// If set to true, then miner needs to clear queue of jobs and immediatelly
// start working on new provided job, because all old jobs shares will
// result with stale share error.
//
// if true, NiceHash charges "Extra Rewards" for frequent job changes
// if false, the stale rate might be higher because miners take too long to switch jobs
//
// It's undetermined what's more cost-effective
false,
},
Height: util.ToHex1(int64(a.Height)),
Algo: "etchash",
}
return cs.enc.Encode(&resp)
}
// FIXME: Temporarily add ID for Claymore compliance // FIXME: Temporarily add ID for Claymore compliance
message := JSONPushMessage{Version: "2.0", Result: result, Id: 0} message := JSONPushMessage{Version: "2.0", Result: result, Id: 0}
return cs.enc.Encode(&message) return cs.enc.Encode(&message)
@ -181,6 +436,30 @@ func (cs *Session) sendTCPError(id json.RawMessage, reply *ErrorReply) error {
return errors.New(reply.Message) return errors.New(reply.Message)
} }
func (cs *Session) sendStratumResult(id json.RawMessage, result interface{}) error {
cs.Lock()
defer cs.Unlock()
resp := JSONRpcResp{Id: id, Error: nil, Result: result}
return cs.enc.Encode(&resp)
}
func (cs *Session) sendStratumError(id json.RawMessage, message interface{}) error {
cs.Lock()
defer cs.Unlock()
resp := JSONRpcResp{Id: id, Error: message}
return cs.enc.Encode(&resp)
}
func (cs *Session) sendTCPReq(resp JSONStratumReq) error {
cs.Lock()
defer cs.Unlock()
return cs.enc.Encode(&resp)
}
func (self *ProxyServer) setDeadline(conn net.Conn) { func (self *ProxyServer) setDeadline(conn net.Conn) {
conn.SetDeadline(time.Now().Add(self.timeout)) conn.SetDeadline(time.Now().Add(self.timeout))
} }
@ -194,15 +473,60 @@ func (s *ProxyServer) registerSession(cs *Session) {
func (s *ProxyServer) removeSession(cs *Session) { func (s *ProxyServer) removeSession(cs *Session) {
s.sessionsMu.Lock() s.sessionsMu.Lock()
defer s.sessionsMu.Unlock() defer s.sessionsMu.Unlock()
delete(s.Extranonces, cs.Extranonce)
delete(s.sessions, cs) delete(s.sessions, cs)
} }
// nicehash
func (cs *Session) sendJob(s *ProxyServer, id json.RawMessage, newjob bool) error {
if newjob {
reply, errReply := s.handleGetWorkRPC(cs)
if errReply != nil {
return cs.sendStratumError(id, []string{
string(errReply.Code),
errReply.Message,
})
}
cs.JobDetails = jobDetails{
JobID: randomHex(8),
SeedHash: reply[1],
HeaderHash: reply[0],
Height: reply[3],
}
// The NiceHash official .NET pool omits 0x...
// TO DO: clean up once everything works
if cs.JobDetails.SeedHash[0:2] == "0x" {
cs.JobDetails.SeedHash = cs.JobDetails.SeedHash[2:]
cs.JobDetails.HeaderHash = cs.JobDetails.HeaderHash[2:]
}
}
t := s.currentBlockTemplate()
resp := JSONStratumReq{
Method: "mining.notify",
Params: []interface{}{
cs.JobDetails.JobID,
cs.JobDetails.SeedHash,
cs.JobDetails.HeaderHash,
true,
},
Height: util.ToHex1(int64(t.Height)),
Algo: "etchash",
}
return cs.sendTCPReq(resp)
}
func (s *ProxyServer) broadcastNewJobs() { func (s *ProxyServer) broadcastNewJobs() {
t := s.currentBlockTemplate() t := s.currentBlockTemplate()
if t == nil || len(t.Header) == 0 || s.isSick() { if t == nil || len(t.Header) == 0 || s.isSick() {
return return
} }
reply := []string{t.Header, t.Seed, s.diff} reply := []string{t.Header, t.Seed, s.diff, util.ToHex(int64(t.Height))}
s.sessionsMu.RLock() s.sessionsMu.RLock()
defer s.sessionsMu.RUnlock() defer s.sessionsMu.RUnlock()
@ -210,7 +534,6 @@ func (s *ProxyServer) broadcastNewJobs() {
count := len(s.sessions) count := len(s.sessions)
log.Printf("Broadcasting new job to %v stratum miners", count) log.Printf("Broadcasting new job to %v stratum miners", count)
s.backend.NumberStratumWorker(count)
start := time.Now() start := time.Now()
bcast := make(chan int, 1024) bcast := make(chan int, 1024)
n := 0 n := 0
@ -220,9 +543,9 @@ func (s *ProxyServer) broadcastNewJobs() {
bcast <- n bcast <- n
go func(cs *Session) { go func(cs *Session) {
err := cs.pushNewJob(&reply) err := cs.pushNewJob(s, &reply)
<-bcast <-bcast
if err != nil || cs.lastErr != nil { if err != nil {
log.Printf("Job transmit error to %v@%v: %v", cs.login, cs.ip, err) log.Printf("Job transmit error to %v@%v: %v", cs.login, cs.ip, err)
s.removeSession(cs) s.removeSession(cs)
} else { } else {
@ -232,3 +555,37 @@ func (s *ProxyServer) broadcastNewJobs() {
} }
log.Printf("Jobs broadcast finished %s", time.Since(start)) log.Printf("Jobs broadcast finished %s", time.Since(start))
} }
func (s *ProxyServer) uniqExtranonce() string {
s.sessionsMu.RLock()
defer s.sessionsMu.RUnlock()
extranonce := randomHex(4)
for {
if _, ok := s.Extranonces[extranonce]; ok {
extranonce = randomHex(4)
} else {
break
}
}
s.Extranonces[extranonce] = true
return extranonce
}
func randomHex(strlen int) string {
rand.Seed(time.Now().UTC().UnixNano())
const chars = "0123456789abcdef"
result := make([]byte, strlen)
for i := 0; i < strlen; i++ {
result[i] = chars[rand.Intn(len(chars))]
}
return string(result)
}
func (cs *Session) getNotificationResponse(s *ProxyServer) interface{} {
result := make([]interface{}, 2)
result[0] = []string{"mining.notify", randomHex(16), "EthereumStratum/1.0.0"}
result[1] = cs.Extranonce
return result
}

22
storage/redis.go

@ -81,7 +81,7 @@ type RewardData struct {
type BlockData struct { type BlockData struct {
Login string `json:"login"` Login string `json:"login"`
Worker string `json:"worker"` Worker string `json:"worker"`
ActualDiff int64 `json:"shareDiff"` ShareDiffCalc int64 `json:"shareDiff"`
Height int64 `json:"height"` Height int64 `json:"height"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Difficulty int64 `json:"difficulty"` Difficulty int64 `json:"difficulty"`
@ -134,7 +134,7 @@ func (b *BlockData) RoundKey() string {
} }
func (b *BlockData) key() string { func (b *BlockData) key() string {
return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login, b.ActualDiff, b.Worker) return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login, b.ShareDiffCalc, b.Worker)
} }
type Miner struct { type Miner struct {
@ -517,7 +517,7 @@ func (r *RedisClient) checkPoWExist(height uint64, params []string) (bool, error
return val == 0, err return val == 0, err
} }
func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, actualDiff int64, height uint64, window time.Duration) (bool, error) { func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, shareDiffCalc int64, height uint64, window time.Duration) (bool, error) {
exist, err := r.checkPoWExist(height, params) exist, err := r.checkPoWExist(height, params)
if err != nil { if err != nil {
return false, err return false, err
@ -533,14 +533,14 @@ func (r *RedisClient) WriteShare(login, id string, params []string, diff int64,
ts := ms / 1000 ts := ms / 1000
_, err = tx.Exec(func() error { _, err = tx.Exec(func() error {
r.writeShare(tx, ms, ts, login, id, diff, actualDiff, window) r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window)
tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) tx.HIncrBy(r.formatKey("stats"), "roundShares", diff)
return nil return nil
}) })
return false, err return false, err
} }
func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actualDiff int64, roundDiff int64, height uint64, window time.Duration) (bool, error) { func (r *RedisClient) WriteBlock(login, id string, params []string, diff, shareDiffCalc int64, roundDiff int64, height uint64, window time.Duration) (bool, error) {
exist, err := r.checkPoWExist(height, params) exist, err := r.checkPoWExist(height, params)
if err != nil { if err != nil {
return false, err return false, err
@ -556,7 +556,7 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actual
ts := ms / 1000 ts := ms / 1000
cmds, err := tx.Exec(func() error { cmds, err := tx.Exec(func() error {
r.writeShare(tx, ms, ts, login, id, diff, actualDiff, window) r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window)
tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10))
tx.HDel(r.formatKey("stats"), "roundShares") tx.HDel(r.formatKey("stats"), "roundShares")
tx.HSet(r.formatKey("miners", login), "roundShares", strconv.FormatInt(0, 10)) tx.HSet(r.formatKey("miners", login), "roundShares", strconv.FormatInt(0, 10))
@ -599,13 +599,13 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actual
totalShares += n totalShares += n
} }
hashHex := strings.Join(params, ":") hashHex := strings.Join(params, ":")
s := join(hashHex, ts, roundDiff, totalShares, login, actualDiff, id) s := join(hashHex, ts, roundDiff, totalShares, login, shareDiffCalc, id)
cmd := r.client.ZAdd(r.formatKey("blocks", "candidates"), redis.Z{Score: float64(height), Member: s}) cmd := r.client.ZAdd(r.formatKey("blocks", "candidates"), redis.Z{Score: float64(height), Member: s})
return false, cmd.Err() return false, cmd.Err()
} }
} }
func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, actualDiff int64, expire time.Duration) { func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, shareDiffCalc int64, expire time.Duration) {
times := int(diff / 1000000000) times := int(diff / 1000000000)
for i := 0; i < times; i++ { for i := 0; i < times; i++ {
tx.LPush(r.formatKey("lastshares"), login) tx.LPush(r.formatKey("lastshares"), login)
@ -617,7 +617,7 @@ func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string
tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)})
tx.Expire(r.formatKey("hashrate", login), expire) // Will delete hashrates for miners that gone tx.Expire(r.formatKey("hashrate", login), expire) // Will delete hashrates for miners that gone
tx.HSet(r.formatKey("miners", login), "lastShare", strconv.FormatInt(ts, 10)) tx.HSet(r.formatKey("miners", login), "lastShare", strconv.FormatInt(ts, 10))
tx.HSet(r.formatKey("miners", login), "lastShareDiff", strconv.FormatInt(actualDiff, 10)) tx.HSet(r.formatKey("miners", login), "lastShareDiff", strconv.FormatInt(shareDiffCalc, 10))
} }
func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) { func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) {
@ -1448,7 +1448,7 @@ func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData {
block.Difficulty, _ = strconv.ParseInt(fields[4], 10, 64) block.Difficulty, _ = strconv.ParseInt(fields[4], 10, 64)
block.TotalShares, _ = strconv.ParseInt(fields[5], 10, 64) block.TotalShares, _ = strconv.ParseInt(fields[5], 10, 64)
block.Login = fields[6] block.Login = fields[6]
block.ActualDiff, _ = strconv.ParseInt(fields[7], 10, 64) block.ShareDiffCalc, _ = strconv.ParseInt(fields[7], 10, 64)
block.Worker = fields[8] block.Worker = fields[8]
block.candidateKey = v.Member.(string) block.candidateKey = v.Member.(string)
result = append(result, &block) result = append(result, &block)
@ -1496,7 +1496,7 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData {
block.RewardString = fields[7] block.RewardString = fields[7]
block.ImmatureReward = fields[7] block.ImmatureReward = fields[7]
block.Login = fields[8] block.Login = fields[8]
block.ActualDiff, _ = strconv.ParseInt(fields[9], 10, 64) block.ShareDiffCalc, _ = strconv.ParseInt(fields[9], 10, 64)
block.Worker = fields[10] block.Worker = fields[10]
block.immatureKey = v.Member.(string) block.immatureKey = v.Member.(string)
result = append(result, &block) result = append(result, &block)

14
util/util.go

@ -80,3 +80,17 @@ func String2Big(num string) *big.Int {
n.SetString(num, 0) n.SetString(num, 0)
return n return n
} }
func DiffFloatToInt(diffFloat float64) (diffInt int64) {
diffInt = int64(diffFloat * float64(1<<48) / float64(0xffff)) // 48 = 256 - 26*8
return
}
func DiffIntToFloat(diffInt int64) (diffFloat float64) {
diffFloat = float64(diffInt*0xffff) / float64(1<<48) // 48 = 256 - 26*8
return
}
func ToHex1(n int64) string {
return strconv.FormatInt(n, 10)
}

Loading…
Cancel
Save