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
|
|||
|
}
|