2025-09-03 08:00:42 +00:00
// 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" `
}
2025-09-09 06:54:04 +00:00
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" `
}
2025-09-03 08:00:42 +00:00
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 ( ) ) )
}
2025-09-09 06:54:04 +00:00
} else if miner . Name == "sha3x" || miner . Name == "monero" || miner . Name == "randomxt" {
2025-09-03 08:00:42 +00:00
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 ) )
2025-09-09 06:54:04 +00:00
} 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)
}
2025-09-03 08:00:42 +00:00
} 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
}