m2pool-core/internal/stratum/stratum.go

1353 lines
36 KiB
Go

// stratum.go
package stratum
import (
"bufio"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"bytes"
"net"
"pool/internal/cache"
"pool/internal/db"
"pool/internal/msg"
"pool/internal/server/coin"
"pool/internal/utility"
"regexp"
"strings"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog"
"gopkg.in/natefinch/lumberjack.v2"
"sync/atomic"
//"container/list"
"sync"
"go.uber.org/zap"
"log"
)
const STRATUM_PING_INTERVAL_CNT int = 3
// const STRATUM_PING_FAILED_MAX_CNT int = STRATUM_PING_INTERVAL_CNT * 4
const STRATUM_PING_FAILED_MAX_CNT int = 70
// Exception Macro
const MINER_ERR_UNKNOWN int = 20
const MINER_ERR_NOT_FOUND_JOB int = 21
const MINER_ERR_DUP_SHARE int = 22
const MINER_ERR_LOW_DIF_SHARE int = 23
const MINER_ERR_UNAUTH_WORKER int = 24
const MINER_ERR_NOT_SUBSCRIBED int = 25
const MINER_ERR_ILLEGAL_METHOD int = 26
const MINER_ERR_ILLEGAL_PARARMS int = 27
const MINER_ERR_IP_BANNED int = 28
const MINER_ERR_INVALID_USERNAME int = 29
const MINER_ERR_INTERNAL_ERROR int = 30
const MINER_ERR_TIME_TOO_OLD int = 31
const MINER_ERR_TIME_TOO_NEW int = 32
const MINER_ERR_ILLEGAL_VERMASK int = 33
const MINER_ERR_STALED_JOB int = 34
type Exception_reply struct {
ID float64 `json:"id"`
Result interface{} `json:"result"`
Error [3]interface{} `json:"error"`
}
type Exception_reply_str struct {
ID string `json:"id"`
Result interface{} `json:"result"`
Error [3]interface{} `json:"error"`
}
type Subscribe_reply struct {
Result [3]interface{} `json:"result"`
ID float64 `json:"id"`
Error interface{} `json:"error"`
}
type SubscribeGpu_reply struct {
Jsonrpc string `json:"jsonrpc"`
Result [3]interface{} `json:"result"`
ID float64 `json:"id"`
Error interface{} `json:"error"`
}
type Subscribe_reply_str struct {
Result [3]interface{} `json:"result"`
ID string `json:"id"`
Error interface{} `json:"error"`
}
type Notify_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params [9]interface{} `json:"params"`
}
type Difficulty_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params [1]float64 `json:"params"`
}
type DifficultyNexa_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params [1]string `json:"params"`
}
type DifficultyNexaGpu_msg struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params [1]float64 `json:"params"`
ID interface{} `json:"id"`
}
type ExtranonceSubscribeGpu_reply struct {
ID interface{} `json:"id"`
Jsonrpc string `json:"jsonrpc"`
Result bool `json:"result"`
}
type Authorize_reply struct {
Result bool `json:"result"`
ID float64 `json:"id"`
Error interface{} `json:"error"`
}
type Sha3xAuthorize_reply struct {
Jsonrpc string `json:"jsonrpc"`
Result Sha3xAuthorize_result_msg `json:"result"`
ID float64 `json:"id"`
}
type Authorize_reply_str struct {
Result bool `json:"result"`
ID string `json:"id"`
Error interface{} `json:"error"`
}
type Submit_nonce struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params []string `json:"params"`
}
type Sha3xSubmit_params struct {
Id string `json:"id"`
Job_id string `json:"job_id"`
Nonce string `json:"nonce"`
Result string `json:"result"`
}
type Sha3xSubmit_nonce struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params Sha3xSubmit_params `json:"params"`
}
type Reconnect_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params []string `json:"params"`
}
type Ping_msg struct {
ID float64 `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
type Authorize_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params []string `json:"params"`
}
type Sha3xAuthorize_params_msg struct {
Login string `json:"login"`
Pass string `json:"pass"`
Agent string `json:"agent"`
}
type Sha3xAuthorize_msg struct {
ID interface{} `json:"id"`
Method string `json:"method"`
Params Sha3xAuthorize_params_msg `json:"params"`
}
type Sha3xAuthorize_job_msg struct {
Algo string `json:"algo"`
Blob string `json:"blob"`
Height uint32 `json:"height"`
Job_id string `json:"job_id"`
Target string `json:"target"`
Xn string `json:"xn"`
}
type Sha3xAuthorize_result_msg struct {
Id string `json:"id"`
Job Sha3xAuthorize_job_msg `json:"job"`
Status string `json:"status"`
}
type MoneroAuthorize_reply struct {
ID float64 `json:"id"`
Jsonrpc string `json:"jsonrpc"`
Error interface{} `json:"error"`
Result MoneroJob `json:"result"`
}
type MoneroJob struct {
Id string `json:"id"`
Job struct {
Id string `json:"id"`
JobId string `json:"job_id"`
Blob string `json:"blob"`
Target string `json:"target"`
SeedHash string `json:"seed_hash"`
NextSeedHash string `json:"next_seed_hash"`
Algo string `json:"algo"`
Height uint64 `json:"height"`
} `json:"job"`
Status string `json:"status"`
}
type RandomxTAuthorize_reply struct {
ID float64 `json:"id"`
Jsonrpc string `json:"jsonrpc"`
Result RandomxTAuthorize_job_msg `json:"job"`
Error interface{} `json:"error"`
}
type RandomxTAuthorize_job_msg struct {
ID string `json:"id"`
Job struct {
Algo string `json:"algo"` // "rx/0"
JobId string `json:"job_id"`
Blob string `json:"blob"`
SeedHash string `json:"seed_hash"`
Target string `json:"target"`
Height uint32 `json:"height"`
Variant string `json:"variant"` // "rx/0"
} `json:"job"`
Status string `json:"status"`
}
type KeepAlived_resp struct {
ID int `json:"id"`
Jsonrpc string `json:"jsonrpc"`
Result struct {
Status string `json:"status"`
} `json:"result"`
}
func Conn_tx(conn net.Conn, body []byte) error {
_, err := conn.Write(body)
if err != nil {
conn.Close()
}
return err
}
func Conn_rx(reader *bufio.Reader) (line string) {
line, err := reader.ReadString('\n')
if err != nil {
//if err != io.EOF {
//logg.Error("[server]", zap.String("ReadString", err.Error()))
return ""
//}
}
strings.TrimSpace(line)
return line
}
// miner-->server
func Handle_subscribe(miner *coin.MinerObj, id float64, extranonce1 string) {
miner.TxLock.Lock()
var results [1][2]string
results[0][0] = "mining.notify"
results[0][1] = miner.MinerId
var result [3]interface{}
result[0] = results
//result[1] = miner.Job.Extranonce1
/*be1 := make([]byte, 4)
binary.LittleEndian.PutUint32(be1, (miner.Server.Extranonce1 + 0x81000000))
result[1] = "0000000000000000" + hex.EncodeToString(be1)
miner.Server.Extranonce1++*/
result[1] = extranonce1
//miner.Server.Logg.Debug("[server]", zap.Uint64("extra2", miner.Job.Extranonce2_size))
if miner.Job.Extranonce2_size == 0 {
result[2] = 4
} else {
result[2] = miner.Job.Extranonce2_size
}
var ack Subscribe_reply
ack.ID = id
ack.Result = result
ack.Error = nil
if extranonce1 == "" {
miner.TxLock.Unlock()
Handle_exception(miner, id, MINER_ERR_NOT_SUBSCRIBED)
return
}
body, err := json.Marshal(ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
miner.Status = coin.MINER_STATUS_SUBSCRIBED
miner.TxLock.Unlock()
}
// server-->miner
func Handle_exception(miner *coin.MinerObj, Id float64, errId int) {
miner.TxLock.Lock()
var errors [3]interface{}
errors[0] = errId
switch errId {
case MINER_ERR_UNKNOWN:
errors[1] = "Other/Unknown"
break
case MINER_ERR_NOT_FOUND_JOB:
errors[1] = "Job not found"
break
case MINER_ERR_STALED_JOB:
errors[1] = "Job staled"
break
case MINER_ERR_DUP_SHARE:
errors[1] = "Duplicate share"
break
case MINER_ERR_LOW_DIF_SHARE:
errors[1] = "Low difficulty share"
break
case MINER_ERR_UNAUTH_WORKER:
errors[1] = "Unauthorized worker"
break
case MINER_ERR_NOT_SUBSCRIBED:
errors[1] = "Not subscribed"
break
case MINER_ERR_ILLEGAL_METHOD:
errors[1] = "Illegal method"
break
case MINER_ERR_ILLEGAL_PARARMS:
errors[1] = "Illegal params"
break
case MINER_ERR_IP_BANNED:
errors[1] = "Ip banned"
break
case MINER_ERR_INVALID_USERNAME:
errors[1] = "Invalid username"
break
case MINER_ERR_INTERNAL_ERROR:
errors[1] = "Internal error"
break
case MINER_ERR_TIME_TOO_OLD:
errors[1] = "Time too old"
break
case MINER_ERR_TIME_TOO_NEW:
errors[1] = "Time too new"
break
case MINER_ERR_ILLEGAL_VERMASK:
errors[1] = "Invalid version mask"
break
}
errors[2] = nil
var ack Exception_reply
ack.ID = Id
ack.Result = nil
ack.Error = errors
body, err := json.Marshal(ack)
if err != nil {
miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
//miner.Server.Miners.Delete(miner.MinerId)
miner.TxLock.Unlock()
return
}
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
}
func Handle_exception_str(miner *coin.MinerObj, Id string, errId int) {
miner.TxLock.Lock()
var errors [3]interface{}
errors[0] = errId
switch errId {
case MINER_ERR_UNKNOWN:
errors[1] = "Other/Unknown"
break
case MINER_ERR_NOT_FOUND_JOB:
errors[1] = "Job not found"
break
case MINER_ERR_STALED_JOB:
errors[1] = "Job staled"
break
case MINER_ERR_DUP_SHARE:
errors[1] = "Duplicate share"
break
case MINER_ERR_LOW_DIF_SHARE:
errors[1] = "Low difficulty share"
break
case MINER_ERR_UNAUTH_WORKER:
errors[1] = "Unauthorized worker"
break
case MINER_ERR_NOT_SUBSCRIBED:
errors[1] = "Not subscribed"
break
case MINER_ERR_ILLEGAL_METHOD:
errors[1] = "Illegal method"
break
case MINER_ERR_ILLEGAL_PARARMS:
errors[1] = "Illegal params"
break
case MINER_ERR_IP_BANNED:
errors[1] = "Ip banned"
break
case MINER_ERR_INVALID_USERNAME:
errors[1] = "Invalid username"
break
case MINER_ERR_INTERNAL_ERROR:
errors[1] = "Internal error"
break
case MINER_ERR_TIME_TOO_OLD:
errors[1] = "Time too old"
break
case MINER_ERR_TIME_TOO_NEW:
errors[1] = "Time too new"
break
case MINER_ERR_ILLEGAL_VERMASK:
errors[1] = "Invalid version mask"
break
}
errors[2] = nil
var ack Exception_reply_str
ack.ID = Id
ack.Result = nil
ack.Error = errors
body, err := json.Marshal(ack)
if err != nil {
miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
miner.Server.Logg.Debug("[server]", zap.String("fail to handle_exception", err.Error()))
//miner.Server.Miners.Delete(miner.MinerId)
miner.TxLock.Unlock()
return
}
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
}
func InitMinerMhs(miner *coin.MinerObj, user string, minername string, minerindex string, miner_id string, status string, DbCtx *db.DbContext) {
var k string
k = user + "." + minername + "_" + minerindex
m, ok := miner.Server.MMhs.Load(k)
if ok {
var mhs *coin.MhsObj = m.(*coin.MhsObj)
mhs.StartSubmitTime = time.Now()
mhs.Status = status
mhs.MinerId = miner_id
//mhs.LockForMhs.Lock()
//mhs.Accepts = nil
//mhs.Rejects = nil
//mhs.LockForMhs.Unlock()
miner.Server.MMhs.Store(k, mhs)
//miner.Server.Logg.Info("[server]", zap.String("exist mhs", k))
} else {
var mhs coin.MhsObj
mhs.MinerId = miner_id
mhs.StartSubmitTime = time.Now()
mhs.Status = status
mhs.Name = miner.Name
mhs.User = user
mhs.Miner = minername
mhs.Index = minerindex
mhs.StartDayTime = time.Now()
mhs.Algo = -1
mhs.Release = false
var mhs_lock sync.Mutex
mhs.LockForMhs = mhs_lock
miner.Server.MMhs.Store(k, &mhs)
//miner.Server.Logg.Info("[server]", zap.String("new mhs", k))
}
}
func validateUsername(username string, min int, max int) bool {
//
if len(username) < min || len(username) > max {
return false
}
//
/*allowedChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
for _, char := range username {
if !strings.ContainsRune(allowedChars, char) {
return false
}
}*/
//
//pattern := `^[a-zA-Z0-9_]{4,15}$`
pattern := fmt.Sprintf("^[a-zA-Z0-9_]{%d,%d}$", min, max)
match, err := regexp.MatchString(pattern, username)
if err != nil {
//fmt.Println("Error matching pattern:", err)
return false
}
return match
}
func Handle_extranonce(miner *coin.MinerObj, id float64) {
miner.TxLock.Lock()
var ack ExtranonceSubscribeGpu_reply
ack.ID = id
ack.Result = true
ack.Jsonrpc = "2.0"
body, err := json.Marshal(ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
}
func extractAndConvertDiff(password string) (float64, bool) {
index := strings.Index(password, "d=")
if index == -1 {
return 0, false
}
valueStr := password[index+2:]
value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return 0, false
}
return value, true
}
// miner-->server
func Handle_authorize(miner *coin.MinerObj, id float64, auth_msg string, DbCtx *db.DbContext) bool {
miner.TxLock.Lock()
var s Authorize_msg
var e error
var s_sha3x Sha3xAuthorize_msg
if miner.Name == "nexa" {
if e = json.Unmarshal([]byte(auth_msg), &s); e != nil {
miner.Server.Logg.Error("[server]", zap.String("Unmarshal", e.Error()))
}
} else if miner.Name == "sha3x" || miner.Name == "monero" || miner.Name == "randomxt" {
if e = json.Unmarshal([]byte(auth_msg), &s_sha3x); e != nil {
miner.Server.Logg.Error("[server]", zap.String("Unmarshal", e.Error()))
}
s.Params = append(s.Params, s_sha3x.Params.Login)
s.Params = append(s.Params, s_sha3x.Params.Pass)
} else {
}
if len(s.Params) < 2 {
miner.Server.Logg.Error("[server]", zap.String("Handle_authorize err", s.Params[0]))
miner.TxLock.Unlock()
return false
}
if s.Params[0] == "" {
miner.Server.Logg.Error("[server]", zap.String("Handle_authorize err", s.Params[0]))
miner.TxLock.Unlock()
return false
}
var strArr []string
if strings.Index(s.Params[0], ".") == -1 {
miner.Server.Logg.Error("[server]", zap.String("user format err", s.Params[0]))
miner.TxLock.Unlock()
return false
}
strArr = strings.Split(s.Params[0], ".") //jjyykk.4x251.dash(jjyykk:user 4x251:miner)
if strArr[0] == "" || strArr[1] == "" {
miner.Server.Logg.Error("[server]", zap.String("user", strArr[0]), zap.String("miner", strArr[1]))
miner.TxLock.Unlock()
return false
}
if (!validateUsername(strArr[0], 3, 15)) || (!validateUsername(strArr[1], 1, 15)) {
miner.TxLock.Unlock()
miner.Server.Logg.Error("[server]", zap.String("invalid user", strArr[0]))
Handle_exception(miner, id, MINER_ERR_INVALID_USERNAME)
return false
}
miner.Server.Logg.Warn("[server]", zap.String("user", strArr[0]), zap.String("miner", strArr[1]))
if miner.Server.Config.Host.Auth {
if !db.CheckUserIsPermitted(strArr[0], miner.Server.MinerType) {
_, ok := miner.Server.CacheUsers.Load(strArr[0])
if ok {
} else {
miner.TxLock.Unlock()
miner.Server.Logg.Error("[server]", zap.String("not found user", strArr[0]))
Handle_exception(miner, id, MINER_ERR_INVALID_USERNAME)
return false
}
} else {
_, ok := miner.Server.CacheUsers.Load(strArr[0])
if ok {
} else {
if atomic.LoadInt32(&(miner.Server.CacheUsersCnt)) < 1000 {
miner.Server.CacheUsers.Store(strArr[0], strArr[0])
atomic.AddInt32(&(miner.Server.CacheUsersCnt), 1)
}
}
}
}
miner.Session = uuid.New().String()
prediff, ok := extractAndConvertDiff(s.Params[1])
if ok {
if (prediff >= miner.Server.Config.Diff.DiffMin) && (prediff <= miner.Server.Config.Diff.DiffMax) {
miner.Difficulty = prediff
}
}
miner.User = strArr[0]
miner.Miner = strArr[1]
miner.Authorized = true
var body_string = ""
if miner.Name == "nexa" {
var ack Authorize_reply
ack.ID = id
ack.Result = true
ack.Error = nil
body, err := json.Marshal(ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return false
}
body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
} else if miner.Name == "sha3x" {
var sha3x_ack Sha3xAuthorize_reply
sha3x_ack.Jsonrpc = "2.0"
idb := make([]byte, 4)
binary.BigEndian.PutUint32(idb, miner.JobId)
miner.Job.Job_id = hex.EncodeToString(idb)
sha3x_ack.ID = id
sha3x_ack.Result.Status = "OK"
sha3x_ack.Result.Id = miner.Job.Job_id
sha3x_ack.Result.Job.Algo = "sha3x"
sha3x_ack.Result.Job.Blob = miner.Sha3xJob.Header
sha3x_ack.Result.Job.Height = miner.Sha3xJob.Height
sha3x_ack.Result.Job.Job_id = miner.Job.Job_id
//target_str, _ := ReverseHexStringByByte(miner.Sha3xJob.Target)
//sha3x_ack.Result.Job.Target = target_str[48:]
target_new, _ := utility.DiffToTarget(miner.Difficulty)
target_str := fmt.Sprintf("%064x", target_new.Bytes())
target_strr, strerr := ReverseHexStringByByte(target_str)
if strerr != nil {
println("ReverseHexStringByByte", strerr.Error())
}
//println("target=", target_str, "r=", target_strr)
sha3x_ack.Result.Job.Target = target_strr[48:]
sha3x_ack.Result.Job.Xn = miner.Sha3xJob.Extranonce1[:4]
body, err := json.Marshal(sha3x_ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return false
}
body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
} else if miner.Name == "monero" {
var monero_ack MoneroAuthorize_reply
monero_ack.Jsonrpc = "2.0"
monero_ack.ID = id
monero_ack.Result.Status = "OK"
monero_ack.Result.Id = miner.Session
monero_ack.Result.Job.Id = miner.Session
monero_ack.Result.Job.JobId = miner.MoneroJob.JobId
monero_ack.Result.Job.Algo = "rx/0"
target_new, _ := utility.MoneroDiffToTarget(miner.Difficulty)
target_str := fmt.Sprintf("%064x", target_new.Bytes())
target_strr, strerr := ReverseHexStringByByte(target_str)
if strerr != nil {
println("ReverseHexStringByByte", strerr.Error())
}
monero_ack.Result.Job.Target = target_strr[48:]
monero_ack.Result.Job.Blob = miner.MoneroJob.BlockhashingBlob
monero_ack.Result.Job.Height = miner.MoneroJob.Height
monero_ack.Result.Job.SeedHash = miner.MoneroJob.SeedHash
monero_ack.Result.Job.NextSeedHash = ""
body, err := json.Marshal(monero_ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return false
}
miner.Jobs.LoadOrStore(miner.MoneroJob.JobId, miner.MoneroJob)
body_string = string(body) + "\n"
// fmt.Println(body_string)
err = Conn_tx(miner.Conn, []byte(body_string))
} else if miner.Name == "randomxt" {
var randomxt_ack RandomxTAuthorize_reply
randomxt_ack.Result.Status = "OK"
idb := make([]byte, 4)
binary.BigEndian.PutUint32(idb, miner.JobId)
miner.Job.Job_id = hex.EncodeToString(idb)
randomxt_ack.ID = id
randomxt_ack.Result.Status = "OK"
randomxt_ack.Result.ID = "1"
randomxt_ack.Result.Job.JobId = miner.Job.Job_id
randomxt_ack.Result.Job.Algo = "rx/0"
randomxt_ack.Result.Job.Blob = miner.RandomxTJob.Header
randomxt_ack.Result.Job.Height = miner.RandomxTJob.Height
randomxt_ack.Result.Job.Variant = "rx/0"
randomxt_ack.Result.Job.SeedHash = miner.RandomxTJob.SeedHash
target_new, _ := utility.MoneroDiffToTarget(miner.Difficulty)
target_str := fmt.Sprintf("%064x", target_new.Bytes())
target_strr, strerr := ReverseHexStringByByte(target_str)
if strerr != nil {
println("ReverseHexStringByByte", strerr.Error())
}
randomxt_ack.Result.Job.Target = target_strr[48:]
body, err := json.Marshal(randomxt_ack)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return false
}
body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
} else {
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
miner.Status = coin.MINER_STATUS_AUTHORIZED
miner.TxLock.Unlock()
mlogfile := "./logs/" + miner.Name + "/" + miner.User + "_" + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex) + ".log"
logFile := &lumberjack.Logger{
Filename: mlogfile,
MaxSize: 1,
MaxBackups: 3,
MaxAge: 31,
Compress: true,
}
miner.LogR = logFile
zerolog.TimeFieldFormat = time.RFC3339
miner.Zlog = zerolog.New(logFile).With().Timestamp().Logger()
miner.ZlogInit = true
miner.Zlog.Info().Msg(auth_msg)
miner.Zlog.Info().Msg(body_string)
return true
}
// server-->miner
func Set_difficulty(miner *coin.MinerObj) {
miner.TxLock.Lock()
var msg Difficulty_msg
msg.ID = nil
msg.Method = "mining.set_difficulty"
msg.Params[0] = miner.Difficulty
body, err := json.Marshal(msg)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
}
/*func Set_difficulty_nexa(miner *coin.MinerObj) {
target_new, err_to := utility.DiffToTarget(miner.Difficulty)
if err_to != nil {
miner.Server.Logg.Error("[server]", zap.String("DiffToTarget", err_to.Error()))
return
}
miner.Target = target_new
miner.TxLock.Lock()
var msg DifficultyNexa_msg
msg.ID = nil
msg.Method = "mining.set_target"
target := fmt.Sprintf("%064x\n", miner.Target.Bytes())
msg.Params[0] = target
body, err := json.Marshal(msg)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
}*/
func Set_difficulty_nexa(miner *coin.MinerObj) {
miner.TxLock.Lock()
var msg DifficultyNexaGpu_msg
msg.ID = nil
msg.Method = "mining.set_difficulty"
msg.Params[0] = miner.Difficulty
msg.Jsonrpc = "2.0"
body, err := json.Marshal(msg)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
}
func removeExpiredJobs(miner *coin.MinerObj, checkExpiration bool) {
miner.LockForJobs.Lock()
defer miner.LockForJobs.Unlock()
if checkExpiration {
var removes []string
for element := miner.JobList.Front(); element != nil; {
entry, isValidEntry := element.Value.(coin.JobListEntry)
next := element.Next()
if isValidEntry {
//if checkExpiration {
if time.Since(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
removes = append(removes, entry.Job_id)
miner.JobList.Remove(element)
element.Value = nil
}
//} else {
//removes = append(removes, entry.Job_id)
//miner.JobList.Remove(element)
//}
}
element = next
}
for _, jobID := range removes {
//miner.Jobs.Store(jobID, nil)
miner.Jobs.Delete(jobID)
}
} else {
//miner.JobList.Init()
for elem := miner.JobList.Front(); elem != nil; {
next := elem.Next()
miner.JobList.Remove(elem)
elem.Value = nil
elem = next
}
//miner.Jobs = sync.Map{}
var keysJobs []interface{}
miner.Jobs.Range(func(k, v interface{}) bool {
keysJobs = append(keysJobs, k)
return true
})
for _, key := range keysJobs {
//miner.Jobs.Store(key, nil)
miner.Jobs.Delete(key)
}
}
var total_jobs int32 = 0
miner.Jobs.Range(func(k, v interface{}) bool {
total_jobs += 1
return true
})
total_entries := 0
for e := miner.JobList.Front(); e != nil; e = e.Next() {
total_entries++
}
// log.Println("jobs: ", miner.User, miner.Miner, total_jobs, total_entries)
}
func UpdateJobs(miner *coin.MinerObj) {
/*var removes []string
miner.LockForJobs.Lock()
defer miner.LockForJobs.Unlock()
//for e := miner.JobList.Front(); e != nil; e = e.Next() {
for e := miner.JobList.Front(); e != nil; {
entry, ok := e.Value.(coin.JobListEntry)
if ok {
//if time.Now().Sub(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
if time.Since(entry.Ts) >= time.Duration(coin.LOCAL_JOBS_EXPIRED_TIME)*time.Second {
removes = append(removes, entry.Job_id)
next := e.Next()
miner.JobList.Remove(e)
e = next
continue
}
}
e = e.Next()
}
//miner.LockForJobs.Unlock()
for i := range removes {
miner.Jobs.Delete(removes[i])
}*/
removeExpiredJobs(miner, true)
}
func StaleAllJobs(miner *coin.MinerObj) {
/*var removes []string
miner.LockForJobs.Lock()
defer miner.LockForJobs.Unlock()
//for e := miner.JobList.Front(); e != nil; e = e.Next() {
for e := miner.JobList.Front(); e != nil; {
entry, ok := e.Value.(coin.JobListEntry)
if ok {
removes = append(removes, entry.Job_id)
next := e.Next()
miner.JobList.Remove(e)
e = next
continue
}
e = e.Next()
}
//miner.LockForJobs.Unlock()
for i := range removes {
miner.Jobs.Delete(removes[i])
}*/
removeExpiredJobs(miner, false)
}
func AddAndUpdateJob(miner *coin.MinerObj) {
miner.LockForJobs.Lock()
defer miner.LockForJobs.Unlock()
currentLen := miner.JobList.Len()
maxSize := int(coin.LOCAL_JOBS_TOTAL_SIZE)
needRemove := currentLen + 1 - maxSize
for cnt := 0; cnt < needRemove; cnt++ {
//if miner.JobList.Len()+1 > int(coin.LOCAL_JOBS_TOTAL_SIZE) {
if e := miner.JobList.Back(); e != nil {
if oldestEntry, ok := e.Value.(coin.JobListEntry); ok {
miner.JobList.Remove(e)
e.Value = nil
//miner.Jobs.Store(oldestEntry.Job_id, nil)
miner.Jobs.Delete(oldestEntry.Job_id)
} else {
miner.JobList.Remove(e)
e.Value = nil
}
}
//}
}
entry := coin.JobListEntry{
Job_id: miner.Job.Job_id,
Ts: time.Now(),
}
miner.JobList.PushFront(entry)
}
func Notify(miner *coin.MinerObj) {
miner.TxLock.Lock()
if !((miner.Status == coin.MINER_STATUS_AUTHORIZED) || (miner.Status == coin.MINER_STATUS_RUNNING)) {
miner.TxLock.Unlock()
return
}
miner.TxLock.Unlock()
if miner.DifficultyNext > -1 {
ratio := miner.DifficultyNext / miner.Difficulty
if ratio > 1.1 || ratio < 0.9 {
miner.Difficulty = miner.DifficultyNext
miner.DifficultyNext = -1
//Set_difficulty(miner)
miner.Server.CoinCtx.SetDifficulty(miner)
} else {
miner.DifficultyNext = -1
}
}
miner.TxLock.Lock()
//log.Println("[server]extra1, id", miner.Job.Extranonce1, miner.Job.Job_id, miner.MinerId)
var params [9]interface{}
var tlist []string = make([]string, 0)
idb := make([]byte, 4)
binary.BigEndian.PutUint32(idb, miner.JobId)
miner.Job.Job_id = hex.EncodeToString(idb)
params[0] = miner.Job.Job_id
if len(miner.Job.PrevblockS) > 0 {
params[1] = miner.Job.PrevblockBig
} else {
p_big := utility.Convert_big_endian(miner.Job.Prevblock.CloneBytes())
params[1] = hex.EncodeToString(p_big)
}
params[2] = miner.Job.Coinbase1
params[3] = miner.Job.Coinbase2
params[4] = tlist
miner.CurHeight = miner.Job.Height
if miner.Job.Transactions != nil {
if len(*miner.Job.Transactions) > 0 {
params[4] = miner.Job.Transactions
/*miner.Server.Logg.Error("[notify]", zap.String("coinbase1", miner.Job.Coinbase1), zap.String("coinbase2", miner.Job.Coinbase2), zap.Uint32("height", miner.Job.Height))
for i := 0; i < len(*miner.Job.Transactions); i++ {
miner.Server.Logg.Error("[notify]", zap.String("trans", (*miner.Job.Transactions)[i]))
}*/
}
}
vb := make([]byte, 4)
binary.LittleEndian.PutUint32(vb, uint32(miner.Job.Version))
params[5] = hex.EncodeToString(vb)
bb := make([]byte, 4)
binary.LittleEndian.PutUint32(bb, miner.Job.Bits)
params[6] = hex.EncodeToString(bb)
t := miner.Job.Timestamp.Unix()
if t > int64(^uint32(0)) {
tb := make([]byte, 8)
binary.LittleEndian.PutUint64(tb, uint64(t))
params[7] = hex.EncodeToString(tb)
} else {
tb := make([]byte, 4)
binary.LittleEndian.PutUint32(tb, uint32(t))
params[7] = hex.EncodeToString(tb)
}
if miner.Reconnect {
params[8] = true
miner.Reconnect = false
} else {
params[8] = miner.Job.IsClean
}
miner.Job.JobDifficulty = miner.Difficulty
//miner.Jobs[miner.Job.Job_id] = miner.Job
miner.Jobs.LoadOrStore(miner.Job.Job_id, miner.Job)
/*var entry coin.JobListEntry
entry.Job_id = miner.Job.Job_id
entry.Ts = time.Now()
miner.LockForJobs.Lock()
miner.JobList.PushFront(entry)
var removes string = ""
if miner.JobList.Len() > int(coin.LOCAL_JOBS_TOTAL_SIZE) {
e := miner.JobList.Back()
entry := e.Value.(coin.JobListEntry)
removes = entry.Job_id
miner.JobList.Remove(e)
}
miner.LockForJobs.Unlock()
if len(removes) > 0 {
miner.Jobs.Delete(removes)
}*/
AddAndUpdateJob(miner)
UpdateJobs(miner)
//miner.LastJobId = miner.Job.Job_id
miner.JobId++
var msg Notify_msg
msg.ID = nil
msg.Method = "mining.notify"
msg.Params = params
body, err := json.Marshal(msg)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
miner.TxLock.Unlock()
return
}
var body_string = string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
//delete(miner.Server.Miners, miner.MinerId)
//miner.Server.Miners.Delete(miner.MinerId)
}
//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
miner.TxLock.Unlock()
if miner.ZlogInit {
miner.Zlog.Info().Msg(body_string)
}
}
type Submit_ack struct {
Result bool `json:"result"`
ID float64 `json:"id"`
Error interface{} `json:"error"`
}
type Submit_ack_str struct {
Result bool `json:"result"`
ID string `json:"id"`
Error interface{} `json:"error"`
}
// server-->miner
func parse_miner_notify(miner *coin.MinerObj, msg msg.StratumJob) int {
miner.Job.Version = msg.Version
miner.Job.Prevblock = msg.Prevblock
miner.Job.Coinbase1 = msg.Coinbase1
miner.Job.Coinbase2 = msg.Coinbase2
miner.Job.Bits = msg.Bits
miner.Job.Timestamp = msg.Timestamp
miner.Job.Target = msg.Target
miner.Job.PrevblockS = msg.PrevblockS
miner.Job.PrevblockBig = msg.PrevblockBig
miner.Job.Transactions = msg.Transactions
miner.Job.BitsS = msg.BitsS
miner.Job.Height = msg.Height
miner.Job.Extranonce2_size = msg.Extranonce2_size
miner.Job.TransData = msg.TransData
miner.Job.Payloadstart = msg.Payloadstart
miner.Job.Segwit = msg.Segwit
miner.Job.IsClean = msg.IsClean
miner.Job.Mintime = msg.Mintime
miner.ServerTargetS = msg.Target
vb := make([]byte, 4)
binary.LittleEndian.PutUint32(vb, uint32(msg.Version))
vBuffer := bytes.NewBuffer(vb)
binary.Read(vBuffer, binary.BigEndian, &(miner.Version))
//log.Printf("version %04x, %04x", miner.Version, msg.Version)
//miner.Server.Logg.Info("[server]", zap.Int32("miner.Version", miner.Version), zap.Int32("msg.Version", msg.Version))
return 1
}
func Send_reconnect_msg(miner *coin.MinerObj) bool {
var msg Reconnect_msg
msg.ID = nil
msg.Method = "client.reconnect"
msg.Params = nil
body, err := json.Marshal(msg)
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("failed to Send_reconnect_msg", err.Error()), zap.String("user", miner.User), zap.String("miner", miner.Miner))
return false
}
body_string := string(body) + "\n"
err = Conn_tx(miner.Conn, []byte(body_string))
if err != nil {
miner.Server.Logg.Error("[server]", zap.String("failed to Send_reconnect_msg", err.Error()), zap.String("user", miner.User), zap.String("miner", miner.Miner))
return false
}
return true
}
func UpdateMhs(miner *coin.MinerObj, accept bool, diff float64, algo int, DbCtx *db.DbContext) {
var k string
k = miner.User + "." + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex)
v, ok := miner.Server.MMhs.Load(k)
if ok {
if v != nil {
var m *coin.MhsObj = v.(*coin.MhsObj)
if m != nil {
var item coin.MhsItem
item.Tt = time.Now()
item.Diff = diff
m.LockForMhs.Lock()
if accept {
m.Accepts = append(m.Accepts, item)
} else {
m.Rejects = append(m.Rejects, item)
}
m.LockForMhs.Unlock()
m.Status = miner.Status
m.MinerId = miner.MinerId
if m.Algo < 0 {
m.Algo = algo
}
log.Println("miner:", k, len(m.Accepts), len(m.Rejects))
miner.Server.MMhs.Store(k, m)
var mhsItem cache.CacheMhsItem
mhsItem.Tt = item.Tt.Format(time.RFC3339)
mhsItem.Diff = item.Diff
if accept {
//cache.StoreMhsCache(miner.Server.RedisClient, miner.Server.MinerType, miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), "accepts", mhsItem)
} else {
//cache.StoreMhsCache(miner.Server.RedisClient, miner.Server.MinerType, miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), "rejects", mhsItem)
}
//miner.Server.Logg.Info("[mhs]", zap.String("UpdateMhs", k), zap.Int("accepts", len(m.Accepts)), zap.Int("rejects", len(m.Rejects)), zap.Int("algo", m.Algo))
}
}
}
}
func UpdateMhsStatus(miner *coin.MinerObj, DbCtx *db.DbContext) {
var k string
k = miner.User + "." + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex)
v, ok := miner.Server.MMhs.Load(k)
if ok {
if v != nil {
var m *coin.MhsObj = v.(*coin.MhsObj)
if m != nil {
m.Status = miner.Status
m.MinerId = miner.MinerId
miner.Server.MMhs.Store(k, m)
//miner.Server.Logg.Info("[mhs]", zap.String("UpdateMhsStatus", k), zap.String("update status", m.Status))
}
}
}
}
func SetMhsRelease(miner *coin.MinerObj) {
var k string
k = miner.User + "." + miner.Miner + "_" + fmt.Sprint(miner.MinerIndex)
v, ok := miner.Server.MMhs.Load(k)
if ok {
if v != nil {
var m *coin.MhsObj = v.(*coin.MhsObj)
if m != nil {
//if m.Status == coin.MINER_STATUS_DISCONNECTED {
//m.Release = true
m.LockForMhs.Lock()
m.Accepts = nil
m.Rejects = nil
m.LockForMhs.Unlock()
miner.Server.MMhs.Store(k, m)
//}
}
}
}
//miner.Server.MMhs.Store(k, nil)
miner.Server.MMhs.Delete(k)
}
func ReverseHexStringByByte(hexStr string) (string, error) {
bytes, err := hex.DecodeString(hexStr)
if err != nil {
return "", err
}
for i, j := 0, len(bytes)-1; i < j; i, j = i+1, j-1 {
bytes[i], bytes[j] = bytes[j], bytes[i]
}
return hex.EncodeToString(bytes), nil
}