// nexa.go package nexa import ( "encoding/binary" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "log" "sync/atomic" //"pool/internal/cache" "pool/internal/db" "pool/internal/gbt/coin" "pool/internal/gbt/dbif" "pool/internal/msg" "pool/internal/utility" "time" "database/sql" //"github.com/btcsuite/btcd/rpcclient" _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" ) const GBT_NEXA_VERSION string = "nexa v2.0m" type NexaAddrConfig struct { Addr string `json:"addr"` } type NexaConfig struct { Nexa NexaAddrConfig `json:"nexa"` } type GbtNexaContext struct { Config NexaConfig GbtCtx *coin.GbtContext last_time time.Time last_gbt GbtNexaMsg last_blockhash string last_height uint32 Submits float64 addressIndex int Target []byte Header []byte last_body string new_block_chan chan int new_block_index int } var logg *zap.Logger var GbtNexaCtx GbtNexaContext type GbtNexaMsg struct { Id uint64 `json:"id"` HeaderCommitment string `json:"headerCommitment"` NBits string `json:"nBits"` } type GetBlockHeaderMsg struct { Height int `json:"height"` Nonce string `json:"nonce"` Confirmations int `json:"confirmations"` } type GetBlockStatsMsg struct { Height int `json:"height"` Subsidy float64 `json:"subsidy"` Totalfee float64 `json:"totalfee"` } type BlockCheckData struct { Height int Nonce string User string Miner string MinerId string Hash string SubIdx int } /*type NewBlockMsg struct { Height int `json:"height"` Nonce int `json:"nonce"` }*/ type PushBlkNewMsg struct { Coin string `json:"coin"` Height int `json:"height"` Nonce string `json:"nonce"` } func update_block_confirm(gbt *GbtNexaContext) { db, err := sql.Open("sqlite3", "./blocks.db") if err != nil { //log.Printf("Error opening database: %v", err) logg.Error("[gbt]", zap.String("Error opening database", err.Error())) return } defer db.Close() query := "SELECT user,miner,minerid,height,nonce,hash,subidx FROM blocks WHERE checked=0 AND created_at >= datetime('now', '-30 minutes') order by id desc limit 2" rows, err := db.Query(query) if err != nil { //log.Printf("Error executing query from blocks: %v", err) logg.Error("[gbt]", zap.String("Error executing query from blocks:", err.Error())) return } defer rows.Close() var blocks []BlockCheckData for rows.Next() { var height int var nonce string var user string var miner string var minerid string var hash string var subidx int if err := rows.Scan(&user, &miner, &minerid, &height, &nonce, &hash, &subidx); err != nil { //log.Printf("Error scanning row in blocks: %v", err) logg.Error("[gbt]", zap.String("Error scanning row in blocks:", err.Error())) return } var blockdata BlockCheckData blockdata.Height = height blockdata.Nonce = nonce blockdata.User = user blockdata.Miner = miner blockdata.MinerId = minerid blockdata.Hash = hash blockdata.SubIdx = subidx blocks = append(blocks, blockdata) //fmt.Printf("blocks - Height: %d, Nonce: %d\n", height, nonce) //log.Printf("update block height %d nonce %s, subidx %d, user %s", height, nonce, subidx, user+"."+miner+"_"+minerid) } for _, block := range blocks { block_hash, err := gbt.GbtCtx.Client.GetBlockHash(int64(block.Height)) if err != nil { logg.Info("[gbt]", zap.String("GetBlockHash ", err.Error())) continue } rawmsgs := make([]json.RawMessage, 1) param_str := `"` + block_hash.String() + `"` rawmsgs[0] = json.RawMessage(param_str) result, err := gbt.GbtCtx.Client.RawRequest("getblockheader", rawmsgs) if err != nil { //log.Printf("getblockheader %s", err.Error()) logg.Error("[gbt]", zap.String("getblockheader", err.Error())) continue } //log.Printf("getblockheader %d %s:%v", block.Height, block_hash, result) var blockHeader msg.GetBlockHeaderMsg err = json.Unmarshal(result, &blockHeader) if err != nil { //log.Printf("getblockheader Unmarshal %s", err.Error()) logg.Error("[gbt]", zap.String("getblockheader Unmarshal ", fmt.Sprint(block.Height)+" "+err.Error())) continue } rawmsgs_stats := make([]json.RawMessage, 1) rawmsgs_stats[0] = json.RawMessage(param_str) result_stats, err := gbt.GbtCtx.Client.RawRequest("getblockstats", rawmsgs_stats) if err != nil { //log.Printf("getblockstats %s", err.Error()) logg.Error("[gbt]", zap.String("getblockstats", err.Error())) continue } //log.Printf("getblockheader %d %s:%v", block.Height, block_hash, result) var blockStats msg.GetBlockStatsMsg err = json.Unmarshal(result_stats, &blockStats) if err != nil { //log.Printf("getblockstats Unmarshal %s", err.Error()) logg.Error("[gbt]", zap.String("getblockstats Unmarshal ", fmt.Sprint(block.Height)+" "+err.Error())) continue } if blockHeader.Confirmations > 3 { //log.Printf("cmp block: %d %s vs %s %s", block.Height, block.Nonce, string(result), blockHeader.Nonce) //log.Printf("cmp block: %d %s %s_%s_%s vs %s\n", block.Height, block.Nonce, block.User, block.Miner, block.MinerId, blockHeader.Nonce) if blockHeader.Nonce == block.Nonce { block_height := int64(block.Height) /*dbif.NotifyMinerSuccess(gbt.GbtCtx, block.User, block.Miner, block.MinerId, block_height, "", block.Nonce, int64(block.SubIdx), blockStats.Subsidy, blockStats.Totalfee) dbif.NotifyUsersBlkStatsSuccess(gbt.GbtCtx, block.User, block_height, "", block.Nonce, int64(block.SubIdx), blockStats.Subsidy, blockStats.Totalfee)*/ dbif.NotifyPoolBlkStatsSuccess(gbt.GbtCtx, block_height, "", block.Nonce, int64(block.SubIdx), blockStats.Subsidy, blockStats.Totalfee) dbif.NotifyBlkDetailSuccess(gbt.GbtCtx, block_height, "", block.Nonce, int64(block.SubIdx)) /*dbif.NotifyBlockStat(gbt.GbtCtx, block.User, block.Miner, block.MinerId, blockStats.Subsidy, blockStats.Totalfee)*/ //dbif.NotifyBlkNewSuccess(gbt.GbtCtx, block_height, "", block.Nonce, -1) dbif.NotifyBlkNewDb(gbt.GbtCtx, block_height, block.Hash, true, block.Nonce, int64(block.SubIdx)) //gbt.GbtCtx.Blocks += 1 //cache.StorePoolCache(gbt.GbtCtx.RedisClient, gbt.GbtCtx.Coin, "blocks", gbt.GbtCtx.Blocks) //gbt.GbtCtx.Reward += blockStats.Subsidy //cache.StorePoolCache(gbt.GbtCtx.RedisClient, gbt.GbtCtx.Coin, "reward", gbt.GbtCtx.Reward) //gbt.GbtCtx.Fee += blockStats.Totalfee //cache.StorePoolCache(gbt.GbtCtx.RedisClient, gbt.GbtCtx.Coin, "fee", gbt.GbtCtx.Fee) /*var pushmsg PushBlkNewMsg pushmsg.Coin = gbt.GbtCtx.Coin pushmsg.Height = block.Height pushmsg.Nonce = block.Nonce jsonData, err := json.Marshal(pushmsg) if err != nil { //log.Printf("Failed to marshal JSON: %v", err) logg.Error("[gbt]", zap.String("Failed to marshal JSON:", err.Error())) continue } if gbt.GbtCtx.PushCh == nil { gbt.GbtCtx.PushCh = utility.InitZmqPush(gbt.GbtCtx.Config.Profit.Push) } if gbt.GbtCtx.PushCh != nil { err = gbt.GbtCtx.PushCh.SendMessage([][]byte{[]byte("blk_new"), jsonData}) if err != nil { //log.Printf("Failed to send data: %v", err) logg.Error("[gbt]", zap.String("Failed to SendMessage:", err.Error())) continue } }*/ updateSQL := `UPDATE blocks SET checked = 1 WHERE height = ? AND nonce = ? AND checked = 0` _, err = db.Exec(updateSQL, block.Height, block.Nonce) if err != nil { //log.Printf("Error updating blk_new: %v", err) logg.Error("[gbt]", zap.String("Error updating blk_new:", err.Error())) continue } //log.Printf("update block success: %d %s", block.Height, block.Nonce) logg.Warn("[gbt]", zap.String("update block success:", fmt.Sprint(block.Height)+" "+block.Nonce)) } } } } func get_gbt_msg(gbt *GbtNexaContext) []byte { height, err := gbt.GbtCtx.Client.GetBlockCount() if err != nil { logg.Info("[gbt]", zap.String("GetBlockCount ", err.Error())) return nil } height = height + 1 rawmsgs := make([]json.RawMessage, 3) param_str1 := `"` + "1000" + `"` param_str2 := `"` + gbt.Config.Nexa.Addr + `"` if len(gbt.GbtCtx.MinerAddrs) > 0 { param_str2 = `"` + gbt.GbtCtx.MinerAddrs[gbt.GbtCtx.MinerAddrIndex] + `"` gbt.GbtCtx.MinerAddrIndex = gbt.GbtCtx.MinerAddrIndex + 1 if gbt.GbtCtx.MinerAddrIndex >= len(gbt.GbtCtx.MinerAddrs) { gbt.GbtCtx.MinerAddrIndex = 0 } } param_str3 := `"` + " / m2pool.com / " + `"` //logg.Debug("[gbt]", zap.String("option", param_str)) rawmsgs[0] = json.RawMessage(param_str1) rawmsgs[1] = json.RawMessage(param_str2) rawmsgs[2] = json.RawMessage(param_str3) result, err := gbt.GbtCtx.Client.RawRequest("getminingcandidate", rawmsgs) // 获得挖矿数据 //log.Printf("[gbt] getminingcandidate %v %s\n", err, result) if err != nil { logg.Error("[gbt]", zap.String("getminingcandidate", err.Error())) return nil } var rxmsg GbtNexaMsg // 获取任务的结构 err = json.Unmarshal(result, &rxmsg) if err != nil { logg.Error("[gbt]", zap.String("getminingcandidate", err.Error())) return nil } gbtstr := fmt.Sprintf("[gbt] height %d, id %d, header %s, nBits %s", height, rxmsg.Id, rxmsg.HeaderCommitment, rxmsg.NBits) //logg.Debug("[gbt]", zap.String(" ", gbtstr)) if rxmsg.Id == gbt.last_gbt.Id { return nil } gbt.last_gbt = rxmsg gbt.last_time = time.Now() nbits_b, _ := hex.DecodeString(rxmsg.NBits) var nbits_i uint32 = binary.BigEndian.Uint32(nbits_b) bigdiff := utility.CompactToBig(nbits_i) targetdiff := fmt.Sprintf("%064x", bigdiff.Bytes()) //logg.Debug("[gbt]", zap.String("target", targetdiff)) logg.Debug("[gbt]", zap.String(" ", gbtstr), zap.String("target", targetdiff)) var job msg.NexaStratumJob job.Id = rxmsg.Id job.Header = utility.Reverse_string(rxmsg.HeaderCommitment) job.NBits = rxmsg.NBits job.CurTime = uint64(time.Now().Unix()) job.Height = uint32(height) job.Nonce = "" job.Target = targetdiff job.Extranonce1 = "" job.Extranonce2_size = 8 job.Extranonce2 = "" body, err := json.Marshal(job) if err != nil { logg.Error("[gbt]", zap.String("failed to Marshal jobmsg", err.Error())) return nil } return body } func gbt_notify_running(gbt *GbtNexaContext) { for { if !gbt.GbtCtx.Started { break } if gbt.GbtCtx.NodeSubCh == nil { gbt.GbtCtx.NodeSubCh = utility.InitZmqSub(gbt.GbtCtx.Config.Rpc.ZmqSub, utility.BITCOIND_ZMQ_HASHBLOCK) } if gbt.GbtCtx.NodeSubCh != nil { cmsg_sub, err := gbt.GbtCtx.NodeSubCh.RecvMessage() if err != nil { if !gbt.GbtCtx.Started { break } gbt.GbtCtx.NodeSubCh.SetSubscribe(utility.BITCOIND_ZMQ_HASHBLOCK) gbt.GbtCtx.NodeSubCh.Connect(gbt.GbtCtx.Config.Rpc.ZmqSub) continue } if len(cmsg_sub) >= 2 { if string(cmsg_sub[0]) == "hashblock" { GbtNexaCtx.new_block_index = GbtNexaCtx.new_block_index + 1 //log.Println("gbt_notify_running", hex.EncodeToString(cmsg_sub[1]), GbtNexaCtx.new_block_index) gbt.new_block_chan <- GbtNexaCtx.new_block_index } } } else { logg.Error("[gbt]", zap.String("notify", "NodeSubCh fail!")) time.Sleep(time.Duration(1) * time.Second) } } } func gbt_running(gbt *GbtNexaContext) { gbtmsg := get_gbt_msg(gbt) if gbtmsg != nil { if gbt.GbtCtx.PubCh == nil { gbt.GbtCtx.PubCh = utility.InitZmqPub(gbt.GbtCtx.Config.Zmq.Pub) } if gbt.GbtCtx.PubCh != nil { for trycnt := 0; trycnt < 3; trycnt++ { err := gbt.GbtCtx.PubCh.SendMessage([][]byte{[]byte("jobnexa"), gbtmsg}) if err != nil { if !gbt.GbtCtx.Started { return } //gbt.GbtCtx.PubCh.Bind(gbt.GbtCtx.Config.Zmq.Pub) logg.Warn("[gbt]", zap.String("job ", err.Error())) } else { //gbt.GbtCtx.PubCh.SendChan <- [][]byte{[]byte("jobnexa"), gbtmsg} logg.Warn("[gbt]", zap.String("job ", "sent")) break } } //gbt.GbtCtx.AlivingChan <- true atomic.StoreInt32(&(gbt.GbtCtx.FlagAliving), 1) } else { logg.Warn("[gbt]", zap.String("job ", "sent failed! PubCh nil")) } } else { atomic.StoreInt32(&(gbt.GbtCtx.FlagAliving), 1) } timer := time.NewTimer(time.Duration(gbt.GbtCtx.Config.Rpc.Timeout) * time.Millisecond) defer timer.Stop() for { if !gbt.GbtCtx.Started { break } new_block_notify := false select { case blk_idx := <-gbt.new_block_chan: log.Println("new block chan", blk_idx) new_block_notify = true if !timer.Stop() { <-timer.C } timer.Reset(time.Duration(gbt.GbtCtx.Config.Rpc.Timeout) * time.Millisecond) case <-gbt.GbtCtx.ExitGbtChan: logg.Error("[gbt]", zap.String("gbt", "exit")) return case <-time.After(time.Duration(gbt.GbtCtx.Config.Rpc.Timeout) * time.Millisecond): log.Println("poll gbt timeout") timer.Reset(time.Duration(gbt.GbtCtx.Config.Rpc.Timeout) * time.Millisecond) } /*if check_bestblockhash(gbt) {*/ gbtmsg := get_gbt_msg(gbt) if gbtmsg != nil { //check_preblock(gbt, DbCtx) if gbt.GbtCtx.PubCh == nil { gbt.GbtCtx.PubCh = utility.InitZmqPub(gbt.GbtCtx.Config.Zmq.Pub) } if gbt.GbtCtx.PubCh != nil { for trycnt := 0; trycnt < 3; trycnt++ { err := gbt.GbtCtx.PubCh.SendMessage([][]byte{[]byte("jobnexa"), gbtmsg}) if err != nil { logg.Warn("[gbt]", zap.String("job ", err.Error())) continue } else { //gbt.GbtCtx.PubCh.SendChan <- [][]byte{[]byte("jobnexa"), gbtmsg} logg.Warn("[gbt]", zap.String("job ", "sent")) break } } //gbt.GbtCtx.AlivingChan <- true atomic.StoreInt32(&(gbt.GbtCtx.FlagAliving), 1) } else { logg.Warn("[gbt]", zap.String("job ", "sent failed! PubCh nil")) } } else { atomic.StoreInt32(&(gbt.GbtCtx.FlagAliving), 1) } if new_block_notify { update_block_confirm(gbt) } } } func nexaInit(config *NexaConfig) { data, err := ioutil.ReadFile("gbt.conf") if err != nil { panic(err.Error()) } if err = json.Unmarshal(data, &config); err != nil { panic(err.Error()) } } func Init(GbtCtx *coin.GbtContext, DbCtx *db.DbContext) { GbtNexaCtx.GbtCtx = GbtCtx GbtNexaCtx.last_height = 0 nexaInit(&GbtNexaCtx.Config) GbtNexaCtx.Target = make([]byte, 32) GbtNexaCtx.Header = make([]byte, 49) GbtNexaCtx.last_time = time.Now() logg = GbtCtx.Log GbtNexaCtx.new_block_chan = make(chan int, 256) GbtNexaCtx.new_block_index = 0 logg.Info("[gbt]", zap.String("gbt_nexa_version", GBT_NEXA_VERSION)) rawmsgs := make([]json.RawMessage, 1) pool_tag := " / m2pool.com / " param_str := `"` + pool_tag + `"` rawmsgs[0] = json.RawMessage(param_str) _, err := GbtNexaCtx.GbtCtx.Client.RawRequest("setminercomment", rawmsgs) if err != nil { //log.Printf("setminercomment %s", err.Error()) logg.Error("[gbt]", zap.String("setminercomment", err.Error())) } } func Start() { go gbt_running(&GbtNexaCtx) go gbt_notify_running(&GbtNexaCtx) go submit_block_running(&GbtNexaCtx) } func Stop() { defer close(GbtNexaCtx.new_block_chan) } func new_block_into_db(block *GbtNexaContext, user string, miner string, minerid string, height int64, nonce string, hash string, subidx int64) bool { db, err := sql.Open("sqlite3", "./blocks.db") if err != nil { log.Printf("Error opening database: %v", err) return false } defer db.Close() createTableSQL := ` CREATE TABLE IF NOT EXISTS blocks ( id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT NOT NULL, miner TEXT NOT NULL, minerid TEXT NOT NULL, height INTEGER, nonce TEXT NOT NULL, hash TEXT NOT NULL, subidx INTEGER, checked INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );` _, err = db.Exec(createTableSQL) if err != nil { log.Printf("Error creating table: %v", err) return false } insertSQL := `INSERT INTO blocks (user, miner, minerid, height, nonce, checked, hash, subidx) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` _, err = db.Exec(insertSQL, user, miner, minerid, height, nonce, 0, hash, subidx) if err != nil { log.Printf("Error inserting data from blocks %s: %v", fmt.Sprint(height), err) return false } return true } func submit_block_running(block *GbtNexaContext) { logg.Info("[block]", zap.String("submit_block_running", "Start.")) for { if !block.GbtCtx.Started { break } if block.GbtCtx.SubCh == nil { block.GbtCtx.SubCh = utility.InitZmqSub(block.GbtCtx.Config.Zmq.Sub, "blk"+block.GbtCtx.Coin) } if block.GbtCtx.SubCh != nil { cmsg_sub, err := block.GbtCtx.SubCh.RecvMessage() if err != nil { if !block.GbtCtx.Started { break } time.Sleep(time.Duration(1) * time.Second) /*block.GbtCtx.SubCh.Destroy() block.GbtCtx.SubCh = nil*/ block.GbtCtx.SubCh.SetSubscribe("blk" + block.GbtCtx.Coin) block.GbtCtx.SubCh.Connect(block.GbtCtx.Config.Zmq.Sub) continue } //log.Println(cmsg_sub, len(cmsg_sub), block.GbtCtx.SubCh) if len(cmsg_sub) >= 2 { if string(cmsg_sub[0]) == "blknexa" { cmsg := cmsg_sub[1] //block data msgb := make([]byte, len(cmsg)-16) copy(msgb, cmsg) //height //heightb := make([]byte, 4) heightb, err := hex.DecodeString(string(cmsg[len(msgb) : len(msgb)+8])) if err != nil { //block.Consumer.MarkOffset(cmsg, "") logg.Error("[block]", zap.String("failed to decode height", err.Error())) continue } var height uint32 = utility.ByteToUint32(heightb) logg.Warn("[block]", zap.Uint32("height", height)) if height <= block.last_height { continue } block.last_height = height //index //indexb := make([]byte, 4) indexb, err1 := hex.DecodeString(string(cmsg[len(msgb)+8:])) if err1 != nil { //block.Consumer.MarkOffset(cmsg, "") logg.Error("[block]", zap.String("failed to decode index", err1.Error())) continue } //copy(indexb, cmsg.Value[len(msgb)+4:]) var index uint32 = utility.ByteToUint32(indexb) logg.Warn("[block]", zap.Uint32("index", index)) logg.Debug("[block]", zap.String("msg", string(cmsg)), zap.String("blk", string(msgb))) var nexablock msg.BlockNexaMsg if err := json.Unmarshal(msgb, &nexablock); err != nil { //block.Consumer.MarkOffset(cmsg, "") logg.Error("[block]", zap.String("failed to Unmarshal job", err.Error())) continue } blk := fmt.Sprintf(`{"id":%d,"nonce":"%s"}`, nexablock.Id, nexablock.Nonce) rawmsgs := make([]json.RawMessage, 1) logg.Info("[block]", zap.String("blk", blk)) rawmsgs[0] = json.RawMessage(blk) //var last_result json.RawMessage //for i := 0; i < len(block.Client); i++ { result, err := block.GbtCtx.Client.RawRequest("submitminingsolution", rawmsgs) if err != nil { logg.Error("[block]", zap.String("submitminingsolution", err.Error())) } else { //last_result = result } logg.Info("[block]", zap.String("result", string(result))) //} /*dbif.NotifyMinerSubmitResult(block.GbtCtx, nexablock.User, nexablock.Miner, nexablock.Index, int64(height), nexablock.Pow, string(result), nexablock.Nonce, nexablock.SubIdx) dbif.NotifyUsersBlkStatsSubmitResult(block.GbtCtx, nexablock.User, int64(height), nexablock.Pow, string(result), nexablock.Nonce, nexablock.SubIdx)*/ dbif.NotifyPoolBlkStatsSubmitResult(block.GbtCtx, int64(height), nexablock.Pow, string(result), nexablock.Nonce, nexablock.SubIdx) block.Submits += 1 //log.Printf("[block] height %d subidx %d nonce %s\n", height, nexablock.SubIdx, nexablock.Nonce) logg.Warn("[block]", zap.Float64("total submits", block.Submits), zap.Int64("SubIdx", nexablock.SubIdx)) new_block_into_db(block, nexablock.User, nexablock.Miner, nexablock.Index, int64(height), nexablock.Nonce, nexablock.Pow, nexablock.SubIdx) } } } else { logg.Error("[block]", zap.String("block", "SubCh failed! retry")) time.Sleep(time.Duration(1) * time.Second) } } }