m2pool-core/internal/gbt/tari/sha3x/sha3x.go

386 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sha3x
import (
"context"
"crypto/rand"
"database/sql"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"pool/internal/db"
"pool/internal/gbt/coin"
"pool/internal/gbt/dbif"
"pool/internal/gbt/tari"
"pool/internal/msg"
"pool/internal/utility"
"sync"
"sync/atomic"
"time"
"go.uber.org/zap"
)
const GBT_SHA3X_VERSION string = "sha3x v1.0"
type Sha3xAddrConfig struct {
Addr string `json:"addr"`
}
type Sha3xConfig struct {
Monero Sha3xAddrConfig `json:"sha3x"`
}
type GbtSha3xContext struct {
Config Sha3xConfig
GbtCtx *coin.GbtContext
Ctx context.Context
last_time time.Time
last_gbt tari.TariBlockTemplate
last_blockhash string
last_height uint64
Submits float64
addressIndex int
Target []byte
Header []byte
last_body string
Jobs sync.Map
JobIds []string
JobGenCount int
new_block_chan chan int
new_block_index int
}
var logg *zap.Logger
var GbtSha3xCtx GbtSha3xContext
func configInit(config *Sha3xConfig) {
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) {
GbtSha3xCtx.GbtCtx = GbtCtx
GbtSha3xCtx.last_height = 0
configInit(&GbtSha3xCtx.Config)
GbtSha3xCtx.JobGenCount = 0
GbtSha3xCtx.Target = make([]byte, 32)
GbtSha3xCtx.Header = make([]byte, 49)
GbtSha3xCtx.last_time = time.Now()
GbtSha3xCtx.Ctx = context.Background()
logg = GbtCtx.Log
GbtSha3xCtx.new_block_chan = make(chan int, 256)
GbtSha3xCtx.new_block_index = 0
logg.Info("[gbt]", zap.String("gbt_sha3x_version", GBT_SHA3X_VERSION))
}
func Start() {
go gbt_running(&GbtSha3xCtx)
go gbt_notify_running(&GbtSha3xCtx)
go submit_block_running(&GbtSha3xCtx)
}
func Stop() {
defer close(GbtSha3xCtx.new_block_chan)
}
func update_block_confirm(gbt *GbtSha3xContext) {
}
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 removeJobs(gbt *GbtSha3xContext, clean bool) {
if !clean {
if len(gbt.JobIds) > 10 {
end := len(gbt.JobIds) - 10
for i := 0; i < end; i++ {
gbt.Jobs.Delete(gbt.JobIds[i])
}
gbt.JobIds = gbt.JobIds[end:]
}
} else {
gbt.Jobs.Range(func(key, value interface{}) bool {
gbt.Jobs.Delete(key)
return true
})
gbt.JobIds = nil
gbt.JobGenCount = 0
}
}
func gbt_running(gbt *GbtSha3xContext) {
ticker := time.NewTicker(1000 * time.Millisecond)
defer ticker.Stop()
for gbt.GbtCtx.Started {
select {
case <-ticker.C:
// 获取区块模板
blockTemplate, err := gbt.GbtCtx.TariClient.GetBlockTemplate(gbt.Ctx, 1)
if err != nil || len(blockTemplate.Header) != 638 {
fmt.Println("任务模板中的区块头长度:", len(blockTemplate.Header))
return
}
// 初始化 ZMQ 发布通道
if gbt.GbtCtx.PubCh == nil {
gbt.GbtCtx.PubCh = utility.InitZmqPub(gbt.GbtCtx.Config.Zmq.Pub)
continue
}
height := blockTemplate.Height
if height == gbt.last_height {
removeJobs(gbt, false)
if gbt.JobGenCount >= 10 {
generateJob(gbt, &blockTemplate)
gbt.JobGenCount = 0 // 成功生成 Job 后重置计数器
}
} else {
removeJobs(gbt, true)
generateJob(gbt, &blockTemplate)
gbt.last_height = height
gbt.JobGenCount = 0 // 高度变化也重置计数器
}
// 标记存活
atomic.StoreInt32(&(gbt.GbtCtx.FlagAliving), 1)
// 无论是否需要发布新任务,计数均+1保持至少10秒更新一次任务
gbt.JobGenCount += 1
case blkIdx := <-gbt.new_block_chan:
log.Println("new block chan", blkIdx)
update_block_confirm(gbt)
case <-gbt.GbtCtx.ExitGbtChan:
logg.Error("[gbt]", zap.String("gbt", "exit"))
return
}
}
}
func generateJob(gbt *GbtSha3xContext, blockTemplate *tari.TariBlockTemplate) {
for trycnt := 0; trycnt < 3; trycnt++ {
job := msg.Sha3xJob{
JobId: randomxJobId(),
MiningHash: blockTemplate.MiningHash,
Header: blockTemplate.Header,
Body: blockTemplate.Body,
TargetDifficulty: blockTemplate.TargetDifficulty,
Height: blockTemplate.Height,
}
sendMsg := msg.Sha3xStratumJob{
JobId: job.JobId,
Header: job.MiningHash,
U64target: job.TargetDifficulty,
Height: uint32(job.Height),
}
bt, err := json.Marshal(sendMsg)
if err != nil {
fmt.Println(err)
continue
}
// 保存 Job
gbt.Jobs.LoadOrStore(job.JobId, job)
gbt.JobIds = append(gbt.JobIds, job.JobId)
// 发布 Job
if err := gbt.GbtCtx.PubCh.SendMessage([][]byte{[]byte("jobsha3x"), bt}); err != nil {
logg.Warn("[gbt]", zap.String("job", err.Error()))
continue
}
logg.Warn("[gbt]", zap.String("job", "sent"))
gbt.JobGenCount++
break
}
}
func gbt_notify_running(gbt *GbtSha3xContext) {
for {
if !gbt.GbtCtx.Started {
break
}
if gbt.GbtCtx.MoneroNewBlockSubCh == nil {
gbt.GbtCtx.MoneroNewBlockSubCh = utility.InitZmqSub(gbt.GbtCtx.Config.Rpc.ZmqSub, utility.BITCOIND_ZMQ_HASHBLOCK)
}
if gbt.GbtCtx.MoneroNewBlockSubCh != nil {
cmsg_sub, err := gbt.GbtCtx.MoneroNewBlockSubCh.RecvMessage()
if err != nil {
if !gbt.GbtCtx.Started {
break
}
gbt.GbtCtx.MoneroNewBlockSubCh.SetSubscribe(utility.BITCOIND_ZMQ_HASHBLOCK)
gbt.GbtCtx.MoneroNewBlockSubCh.Connect(gbt.GbtCtx.Config.Rpc.ZmqSub)
continue
}
if len(cmsg_sub) >= 2 {
if string(cmsg_sub[0]) == "hashblock" {
GbtSha3xCtx.new_block_index = GbtSha3xCtx.new_block_index + 1
//log.Println("gbt_notify_running", hex.EncodeToString(cmsg_sub[1]), GbtNexaCtx.new_block_index)
gbt.new_block_chan <- GbtSha3xCtx.new_block_index
}
}
} else {
logg.Error("[gbt]", zap.String("notify", "NodeSubCh fail!"))
time.Sleep(time.Duration(1) * time.Second)
}
}
}
type ServerSubmitBlock struct {
JobId string
Header []byte
Body []byte
Nonce string
}
func submit_block_running(block *GbtSha3xContext) {
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.SetSubscribe("blk" + block.GbtCtx.Coin)
block.GbtCtx.SubCh.Connect(block.GbtCtx.Config.Zmq.Sub)
continue
}
if len(cmsg_sub) >= 2 {
if string(cmsg_sub[0]) == "blksha3x" {
cmsg := cmsg_sub[1]
//block data
msgb := make([]byte, len(cmsg)-16)
copy(msgb, cmsg)
var submitMsg msg.BlockSha3xMsg
if err := json.Unmarshal(msgb, &submitMsg); err != nil {
//block.Consumer.MarkOffset(cmsg, "")
logg.Error("[block]", zap.String("failed to Unmarshal job", err.Error()))
continue
}
var height = submitMsg.Height
logg.Warn("[block]", zap.Uint64("height", height))
if height <= block.last_height {
continue
}
block.last_height = height
indexb, err1 := hex.DecodeString(string(cmsg[len(msgb)+8:]))
if err1 != nil {
logg.Error("[block]", zap.String("failed to decode index", err1.Error()))
continue
}
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)))
if v, ok := block.Jobs.Load(submitMsg.Id); ok {
job := v.(*msg.Sha3xJob) // 类型断言
headerStr, bodyStr := job.Header, job.Body
header, err := hex.DecodeString(headerStr)
if err != nil {
fmt.Println(err)
return
}
body, err := hex.DecodeString(bodyStr)
if err != nil {
fmt.Println(err)
return
}
nonceByte := make([]byte, 8)
binary.LittleEndian.PutUint64(nonceByte, submitMsg.Nonce)
copy(header[311:319], nonceByte)
blockHash_byte, _ := block.GbtCtx.TariClient.SubmitBlock(block.Ctx, header, body)
nonceStr := fmt.Sprintf("%x", submitMsg.Nonce)
blockHash := hex.EncodeToString(blockHash_byte)
dbif.NotifyPoolBlkStatsSubmitResult(block.GbtCtx, int64(height), blockHash, "OK", nonceStr, submitMsg.SubIdx)
block.Submits += 1
logg.Warn("[block]", zap.Float64("total submits", block.Submits), zap.Int64("SubIdx", submitMsg.SubIdx))
new_block_into_db(block, submitMsg.User, submitMsg.Miner, submitMsg.Index, int64(height), nonceStr, blockHash, submitMsg.SubIdx)
}
}
}
} else {
logg.Error("[block]", zap.String("block", "SubCh failed! retry"))
time.Sleep(time.Duration(1) * time.Second)
}
}
}
func new_block_into_db(block *GbtSha3xContext, 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
}