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