package monero import ( "crypto/rand" "strings" //"database/sql" "encoding/hex" "encoding/json" //"log" //"math" "math/big" //"strings" "fmt" "pool/internal/msg" "pool/internal/server/coin" "pool/internal/server/dbif" "pool/internal/stratum" "pool/internal/utility" "time" _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" ) const SERVER_MONERO_VERSION string = "monero v0.1a" type ServerMoneroContext struct { ServerCtx *coin.ServerContext logg *zap.Logger Sha3xJob msg.Sha3xStratumJob RandomxVM *RandomXValidator } var logg *zap.Logger var ServerMoneroCtx ServerMoneroContext type MoneroNotify_params_msg struct { Id string `json:"id"` JobId string `json:"job_id"` SeedHash string `json:"seed_hash"` Blob string `json:"blob"` Height uint32 `json:"height"` Target string `json:"target"` NextSeedHash string `json:"next_seed_hash"` Algo string `json:"algo"` } type Monero_msg struct { Jsonrpc string `json:"jsonrpc"` Method string `json:"method"` Params MoneroNotify_params_msg `json:"params"` } // 辅助函数:反转每个字节的小端 hex func reverseHexBytes(s string) string { if len(s)%2 != 0 { s = "0" + s } res := "" for i := len(s); i > 0; i -= 2 { res += s[i-2 : i] } return res } func calc_target(diff uint64) string { difficulty := new(big.Int) difficulty.SetString(fmt.Sprintf("%d", diff), 10) // 2^256 - 1 max := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) // target = (2^256 - 1) / difficulty target := new(big.Int).Div(max, difficulty) // 转为32字节 hex(大端) targetHexBE := fmt.Sprintf("%064x", target) return targetHexBE } func calc_diff(hash string) uint64 { be := reverseHexBytes(hash) target_be := new(big.Int) target_be.SetString(be, 16) max := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) difficulty := new(big.Rat).SetFrac(max, target_be) // Convert *big.Rat to *big.Float, then to uint64 diffFloat := new(big.Float).SetRat(difficulty) result, _ := diffFloat.Uint64() return result } func handle_submit(miner *coin.MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool) { var submit_item coin.BlockMsg /*var user_blk_item coin.UserBlockMsg*/ var pool_blk_item coin.PoolBlkMsg var blk_detail_height int64 var blk_detail_hash string var blk_detail_success bool var blk_detail_miner_diff float64 var blk_detail_pool_diff float64 if miner.Authorized != true { miner.ErrOthers = miner.ErrOthers + 1 stratum.Handle_exception(miner, id, stratum.MINER_ERR_UNAUTH_WORKER) stratum.Send_reconnect_msg(miner) return false, false, false } var new_found bool = false var ack stratum.Submit_ack ack.ID = id ack.Result = true // fmt.Println("提交job_id:", job_id) var keys []string miner.Jobs.Range(func(k, v interface{}) bool { if key, ok := k.(string); ok { keys = append(keys, key) } return true // 继续遍历 }) // fmt.Println("目前任务所有key:", keys) v, ok := miner.Jobs.Load(job_id) if ok { job := v.(msg.MoneroStratumJob) if uint32(job.Height) < miner.CurHeight-1 { ack.Result = false stratum.Handle_exception(miner, id, stratum.MINER_ERR_STALED_JOB) miner.ErrStaleds = miner.ErrStaleds + 1 return false, false, false } if (miner.LastNonce != nonce) || (miner.MoneroJob.BlocktemplateBlob != job.BlocktemplateBlob) { miner.MoneroJob.BlocktemplateBlob = job.BlocktemplateBlob miner.LastNonce = nonce job.Nonce = nonce if miner.ZlogInit { miner.Zlog.Info().Msg("height " + string(job.Height) + " target " + job.Target + " " + miner.User + "." + miner.Miner) } var calc_hash []byte var completeHeader []byte nonceByte, err := hex.DecodeString(nonce) if err != nil { fmt.Println(err) return false, false, false } headerByte, err := hex.DecodeString(job.BlockhashingBlob) if err != nil { fmt.Println(err) return false, false, false } calc_hash, completeHeader, err = ServerMoneroCtx.RandomxVM.BuildPowHash(headerByte, nonceByte) if err != nil { fmt.Println("calc_hash error:", err) return false, false, false } job.CompleteHeader = completeHeader if miner.ZlogInit { miner.Zlog.Info().Msg("hash in " + submit_item.Header + " calc_hash " + hex.EncodeToString(calc_hash) + " " + miner.User + "." + miner.Miner) } submit_target := new(big.Int) submit_target.SetBytes(calc_hash) calc_diff := utility.MoneroTarget2Diff(calc_hash) if miner.ZlogInit { miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " target diff " + fmt.Sprintf("%f", (miner.Difficulty)) + " submit diff " + fmt.Sprintf("%f", (calc_diff))) } if calc_diff < float64(job.Difficulty) { ack.Result = false miner.ErrLowDiffs = miner.ErrLowDiffs + 1 stratum.Handle_exception(miner, id, stratum.MINER_ERR_LOW_DIF_SHARE) return false, false, false } stb, _ := hex.DecodeString(job.Target) server_diff := utility.MoneroTarget2Diff(utility.Reverse(stb)) network_target := new(big.Int) network_target.SetBytes(stb) if miner.ZlogInit { miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " calc_diff " + fmt.Sprintf("%f", (calc_diff)) + " miner.Difficulty " + fmt.Sprintf("%f", (miner.Difficulty)) + " server_diff " + fmt.Sprintf("%f", (server_diff))) miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " submit_target " + hex.EncodeToString(submit_target.Bytes()) + " network_target " + hex.EncodeToString(network_target.Bytes()) + " target " + hex.EncodeToString(miner.ServerTarget.Bytes()) + " cmp " + fmt.Sprintf("%d", (network_target.Cmp(submit_target)))) } submit_item.Hash = hex.EncodeToString(calc_hash) submit_item.Target = hex.EncodeToString(miner.Target.Bytes()) submit_item.Submit_target = hex.EncodeToString(calc_hash) submit_item.Height = int64(job.Height) submit_item.Pow = hex.EncodeToString(calc_hash) submit_item.Net_target = hex.EncodeToString(network_target.Bytes()) pool_blk_item.Height = int64(job.Height) pool_blk_item.Hash = hex.EncodeToString(calc_hash) pool_blk_item.Pow = hex.EncodeToString(calc_hash) pool_blk_item.Net_target = hex.EncodeToString(network_target.Bytes()) blk_detail_height = int64(job.Height) blk_detail_hash = hex.EncodeToString(calc_hash) blk_detail_success = false blk_detail_miner_diff = float64(job.Difficulty) blk_detail_pool_diff = miner.Server.RefDifficulty if ack.Result == true { if (calc_diff >= server_diff) || (network_target.Cmp(submit_target) >= 0) { miner.Server.SubIdx++ Produce_block_submit(miner /*header,*/, &job, submit_item.Hash, miner.Server.SubIdx) miner.SubmitIndex++ miner.Submits = miner.Submits + 1 new_found = true } if new_found && float64(miner.Server.MoneroJob.Difficulty) <= calc_diff { pool_blk_item.Submit = "y" pool_blk_item.Success = false pool_blk_item.Accepts = miner.Accepts pool_blk_item.Rejects = miner.Rejects pool_blk_item.Reward = 0 pool_blk_item.Fee = 0 pool_blk_item.Nonce = nonce pool_blk_item.SubIdx = miner.Server.SubIdx dbif.NotifyPoolBlkStatsDb2(miner.Server, &pool_blk_item) } } } else { miner.LastHeader = job.BlocktemplateBlob miner.LastNonce = nonce ack.Result = false stratum.Handle_exception(miner, id, stratum.MINER_ERR_DUP_SHARE) miner.ErrDuplicates = miner.ErrDuplicates + 1 return false, false, false } } else { ack.Result = false stratum.Handle_exception(miner, id, stratum.MINER_ERR_NOT_FOUND_JOB) miner.ErrStaleds = miner.ErrStaleds + 1 return false, false, false } miner.LastJobId = job_id ack.Error = nil body, err := json.Marshal(ack) if err != nil { if miner.ZlogInit { miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " handle_submit Marshal " + err.Error()) } miner.ErrOthers = miner.ErrOthers + 1 stratum.Handle_exception(miner, id, stratum.MINER_ERR_UNKNOWN) return false, false, false } var body_string = string(body) + "\n" err = stratum.Conn_tx(miner.Conn, []byte(body_string)) if err != nil { //miner.Server.Miners.Delete(miner.MinerId) } if miner.ZlogInit { miner.Zlog.Info().Msg(body_string) } miner.TxLock.Lock() miner.Status = coin.MINER_STATUS_RUNNING miner.TxLock.Unlock() if ack.Result { miner.Accepts += miner.Difficulty miner.M5Accepts += miner.Difficulty miner.VarDiffOpt.SubmitShares += miner.Difficulty } else { miner.Rejects += miner.Difficulty } now := time.Now() if miner.Server.Config.Diff.Filter == "kalman" { if ack.Result { share_interval := now.Sub(miner.LastSubmitime).Seconds() mhs := miner.Difficulty * share_interval diff_next, kalman_p := miner.DiffHandler.Handler(miner.Difficulty, share_interval) mhs_est := diff_next * miner.Server.Config.Diff.DiffAdjustInterval ratio := diff_next / miner.Difficulty if ratio > 0 { if now.Sub(miner.StartSubmitTime).Seconds() > 180 { if ratio >= 2 { miner.DifficultyNext = diff_next * 10000000 / 10000000 } else if ratio <= 0.5 { miner.DifficultyNext = diff_next * 10000000 / 10000000 } else { } } else { miner.DifficultyNext = diff_next * 10000000 / 10000000 } } if miner.DifficultyNext > 0.0 { if miner.DifficultyNext < miner.VarDiffOpt.MinDiff { miner.DifficultyNext = miner.VarDiffOpt.MinDiff } else if miner.DifficultyNext > miner.VarDiffOpt.MaxDiff { miner.DifficultyNext = miner.VarDiffOpt.MaxDiff } } if miner.Server.Config.Diff.Dbg { coin.New_diff_into_db(miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), miner.Difficulty, diff_next, kalman_p, share_interval, mhs, mhs_est) } } } else { if now.Sub(miner.LastSubmitime).Seconds() < miner.Server.Config.Diff.DiffAdjustInterval { if ack.Result { if miner.VarDiffOpt.Uptimes++; miner.VarDiffOpt.Uptimes >= coin.DIFFICULTY_WAIT_TIMES { coin.VarAdjustDifficulty(miner, coin.UP_DIFF) miner.VarDiffOpt.LastCalcTime = now } } } else { miner.VarDiffOpt.Uptimes = 0 } if now.Sub(miner.LastSubmitime).Seconds() > miner.Server.Config.Diff.DiffAdjustInterval*2 { if ack.Result { if miner.VarDiffOpt.Downtimes++; miner.VarDiffOpt.Downtimes >= coin.DIFFICULTY_WAIT_TIMES { coin.VarAdjustDifficulty(miner, coin.DOWN_DIFF) miner.VarDiffOpt.LastCalcTime = now } } } else { miner.VarDiffOpt.Downtimes = 0 } } if ack.Result { miner.LastSubmitime = now miner.VarDiffOpt.LastSubmitTime = now } var duration float64 = float64(now.Sub(miner.StartSubmitTime)) / 1000000000 if duration < 1 { duration = 1 } diffOneShareHashesAvg := uint64(0xFFFFFFFFFFFFFFFF) miner.AverageHashrate = miner.Accepts * float64(diffOneShareHashesAvg) / duration / 1000000 var m5_duration float64 = float64(now.Sub(miner.M5SubmitTime)) / 1000000000 if m5_duration >= float64(time.Minute*5)/1000000000 { miner.M5SubmitTime = now miner.M5Hashrate = miner.M5Accepts * float64(diffOneShareHashesAvg) / m5_duration / 1000000 miner.M5Accepts = 0 } if miner.ZlogInit { miner.Zlog.Info().Msg(miner.User + "." + miner.Miner + " handle_submit M5Accepts " + fmt.Sprintf("%f", (miner.M5Accepts)) + " M5Hashrate(MH/S) " + fmt.Sprintf("%f", (miner.M5Hashrate))) } if miner.Server.Config.Diff.Filter == "kalman" { } else { if float64(now.Sub(miner.VarDiffOpt.LastCalcTime))/1000000000 >= miner.VarDiffOpt.AdjustTime { coin.VarAdjustDifficulty(miner, coin.UPDATE_DIFF) miner.VarDiffOpt.LastCalcTime = now } } if ack.Result { submit_item.Success = false if new_found { submit_item.Submit = "y" submit_item.SubIdx = miner.Server.SubIdx } else { submit_item.Submit = "n" submit_item.SubIdx = -1 } submit_item.Accepts = miner.Accepts submit_item.Total_accepts = miner.Accepts submit_item.Rejects = miner.Rejects submit_item.Total_rejects = miner.Rejects submit_item.Reward = 0 submit_item.Fee = 0 submit_item.Nonce = nonce dbif.NotifyBlkDetailDb(miner, blk_detail_height, blk_detail_hash, blk_detail_success, blk_detail_miner_diff, blk_detail_pool_diff, nonce, submit_item.SubIdx) return true, new_found, true } return false, false, true } func contractBlockTemplateBlob(miner *coin.MinerObj, nonceHex string) (string, error) { blockTemplateStr := miner.MoneroJob.BlocktemplateBlob block, err := hex.DecodeString(blockTemplateStr) if err != nil { return "", err } if len(block) < 43 { return "", fmt.Errorf("blocktemplate blob too short: %d", len(block)) } // nonce 是 4 字节 hex,直接解码成 bytes nonceBytes, err := hex.DecodeString(nonceHex) if err != nil { return "", fmt.Errorf("invalid nonce hex: %v", err) } if len(nonceBytes) != 4 { return "", fmt.Errorf("nonce must be 4 bytes, got %d", len(nonceBytes)) } // 覆盖 nonce 区域 (39~42) copy(block[39:43], nonceBytes) return hex.EncodeToString(block), nil } func Produce_block_submit(miner *coin.MinerObj /*header Sha3xBlockHeader,*/, job *msg.MoneroStratumJob, PowHash string, SubIdx int64) { var nm msg.BlockMoneroMsg blockBlob, err := contractBlockTemplateBlob(miner, job.Nonce) if err != nil { fmt.Println(err) return } nm.Id = job.Id nm.Header = blockBlob nm.Nonce = job.Nonce nm.Pow = PowHash nm.SubIdx = SubIdx nm.User = miner.User nm.Miner = miner.Miner nm.Height = job.Height nm.Index = fmt.Sprint(miner.MinerIndex) body, err := json.Marshal(nm) if err != nil { logg.Error("[server]", zap.String("failed to Marshal job", err.Error())) return } // JSON主体 blk := string(body) // 高度(uint32 → 4字节 → hex编码8字符) heightHex := fmt.Sprintf("%08x", job.Height) // Index(uint32 → 4字节 → hex编码8字符) indexHex := fmt.Sprintf("%08x", miner.MinerIndex) // 拼接最终消息 msg := blk + heightHex + indexHex // fmt.Println(msg) logg.Info("[server]", zap.String("final_msg", msg)) if miner.Server.PubCh == nil { miner.Server.PubCh = utility.InitZmqPub(miner.Server.Config.Zmq.Pub) } if miner.Server.PubCh != nil { // fmt.Println(msg) err := miner.Server.PubCh.SendMessage([][]byte{[]byte("blkmonero"), []byte(msg)}) if err != nil { miner.Server.PubCh.Destroy() miner.Server.PubCh = nil logg.Info("[server]", zap.String("blk", err.Error())) } else { logg.Info("[server]", zap.String("blk", "sent")) } } } // var start_job_id uint64 = 0 // server-->miner func parse_miner_notify(miner *coin.MinerObj, msg msg.MoneroStratumJob) int { if miner.MoneroJob.Height != msg.Height { miner.Job.IsClean = true } miner.MoneroJob = msg miner.MoneroJob.JobId = msg.JobId return 1 } func Init(server *coin.ServerContext) { ServerMoneroCtx.ServerCtx = server ServerMoneroCtx.RandomxVM = &RandomXValidator{} logg = server.Logg logg.Info("[server]", zap.String("server_sha3x_version", SERVER_MONERO_VERSION)) coin.Init_diff_db() } func Start() { } func Stop() { coin.DiffStop() } func InitMiner(miner *coin.MinerObj) { miner.MoneroJob = miner.Server.MoneroJob // miner.MoneroJob.Extranonce1 = miner.Job.Extranonce1 server_target := new(big.Int) t_bytes, err := hex.DecodeString(miner.MoneroJob.Target) if err != nil { logg.Error("[server]", zap.String("DecodeString", err.Error())) return } //server_target.SetBytes(common.Reverse(t_bytes)) miner.MoneroJob = ServerMoneroCtx.ServerCtx.MoneroJob server_target.SetBytes(t_bytes) miner.ServerTarget = server_target miner.ServerTargetS = miner.Server.SJob.Target miner.CurHeight = uint32(miner.MoneroJob.Height) } func Handle_subscribe_sha3x(miner *coin.MinerObj, id float64, extranonce1 string) { } func HandleMinerSubscribe(miner *coin.MinerObj, id float64, extranonce1 string, msg string) { } func HandleMinerAuth(miner *coin.MinerObj) { } func HandleMinerSubmit(miner *coin.MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool) { //nonce_str, _ := stratum.ReverseHexStringByByte(nonce) accept_ok, submit_ok, handle_ok := handle_submit(miner, id, miner_user, job_id, nonce2, ntime, nonce) return accept_ok, submit_ok, handle_ok } func SetDifficulty(miner *coin.MinerObj) { stratum.Set_difficulty(miner) } func MoneroNotify(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 stratum.Set_difficulty(miner) //logg.Info("[gbt]", zap.Float64("update Diff", miner.Difficulty)) } else { miner.DifficultyNext = -1 } } miner.TxLock.Lock() //log.Println("[server]extra1, id", miner.Job.Extranonce1, miner.Job.Job_id, miner.MinerId) var msg Monero_msg msg.Params.Id = miner.Session msg.Params.SeedHash = miner.MoneroJob.SeedHash msg.Params.JobId = miner.MoneroJob.JobId msg.Params.Blob = miner.MoneroJob.BlockhashingBlob msg.Params.Height = uint32(miner.MoneroJob.Height) miner.MoneroJob.Difficulty = uint64(miner.Difficulty) msg.Params.NextSeedHash = "" msg.Params.Algo = "rx/0" //target_s, _ := stratum.ReverseHexStringByByte(miner.Sha3xJob.Target) //msg.Params.Target = target_s[48:] target_new, _ := utility.MoneroDiffToTarget(miner.Difficulty) target_str := fmt.Sprintf("%064x", target_new.Bytes()) target_strr, strerr := stratum.ReverseHexStringByByte(target_str) if strerr != nil { println("ReverseHexStringByByte", strerr.Error()) } //println("target=", target_str, "r=", target_strr) msg.Params.Target = target_strr[48:] miner.CurHeight = uint32(miner.MoneroJob.Height) // miner.MoneroJob.JobDifficulty = miner.Difficulty miner.Jobs.LoadOrStore(miner.MoneroJob.JobId, miner.MoneroJob) stratum.AddAndUpdateJob(miner) stratum.UpdateJobs(miner) miner.JobId++ var body []byte var err error msg.Jsonrpc = "2.0" msg.Method = "job" 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 = stratum.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 Notify(miner *coin.MinerObj) { MoneroNotify(miner) } func formatUint64ToHexWithPadding(val uint64) string { hexStr := fmt.Sprintf("%016x", val) if len(hexStr) < 64 { paddingLen := 64 - len(hexStr) hexStr += string(make([]byte, paddingLen)) for i := len(hexStr) - paddingLen; i < 64; i++ { hexStr = hexStr[:i] + "0" + hexStr[i+1:] } } return hexStr } func formatWideTargetTo32BytesTarget(wide_target string) string { if len(wide_target) > 64 { panic("任务中的wide_target错误:") } // 去掉前缀 0x 或 0X wide_target = strings.TrimPrefix(wide_target, "0x") wide_target = strings.TrimPrefix(wide_target, "0X") wide_target = fmt.Sprintf("%0*s%s", 64-len(wide_target), "", wide_target) return wide_target } var last_seed string = "" func randomxJobId() string { // 生成4个字节 bytes := make([]byte, 4) _, err := rand.Read(bytes) if err != nil { panic(err) } // 转成 hex 字符串 hexStr := hex.EncodeToString(bytes) return hexStr } func HandleJobMsg(server *coin.ServerContext, Msg []byte) { var result msg.MoneroStratumJob server.Logg.Warn("[server]", zap.String("receive", "job")) if err := json.Unmarshal(Msg, &result); err != nil { server.Logg.Error("[server]", zap.String("Unmarshal", err.Error())) return } result.Target = calc_target(result.Difficulty) // 上个模板 seed_hash 和本次 seed_hash 不一致时,重置 randomx 虚拟机 if result.SeedHash != last_seed { fmt.Println("开始创建新的 randomx vm, 本次 seed_hash:", result.SeedHash) seedBytes, err := hex.DecodeString(result.SeedHash) if err != nil { panic(err) } // 如果已有旧 VM,先释放 if ServerMoneroCtx.RandomxVM != nil { ServerMoneroCtx.RandomxVM.Destroy() } // 创建新 VM vm, err := NewRandomXValidator(seedBytes) if err != nil { panic(err) } ServerMoneroCtx.RandomxVM = vm last_seed = result.SeedHash } server.MoneroJob = msg.MoneroStratumJob(result) logg.Debug("[gbt]", zap.String("Target", server.MoneroJob.Target)) logg.Debug("[gbt]", zap.Uint64("Id", server.MoneroJob.Id), zap.Float64("network diff", float64(server.MoneroJob.Difficulty))) server.NetHight = uint64(server.MoneroJob.Height) // 当前server中的全网高度 server.NetTarget = result.Target // 当前server中的全网target server.Miners.Range(func(k, v interface{}) bool { if v != nil { m, ok := v.(*(coin.MinerObj)) if ok { if m != nil { server.Logg.Info("[server]", zap.String("lock", "start")) m.TxLock.Lock() status := m.Status cmd := parse_miner_notify(m, server.MoneroJob) m.TxLock.Unlock() server.Logg.Info("[server]", zap.String("lock", "end")) var need_notify bool = true if time.Now().Sub(m.ConnSetupTime) >= time.Duration(coin.CONN_EXPIRED_TIME)*time.Second { if (status != coin.MINER_STATUS_RUNNING) && (status != coin.MINER_STATUS_AUTHORIZED) { //m.Conn.Close() need_notify = false } } if need_notify { switch cmd { case 0: //extranonce 1 and extranonce2 size //TODO case 1: //notify MoneroNotify(m) } } } } } return true }) } func IsMhsLow(miner *coin.MinerObj) bool { if miner.Mhs5M < 1 { return true } return false } func GetBlockInterval() int { return 3600 }