386 lines
9.9 KiB
Go
386 lines
9.9 KiB
Go
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
|
||
}
|