m2pool_core/internal/server/coin/coin.go

788 lines
19 KiB
Go

// coin.go
package coin
import (
"bytes"
"container/list"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"database/sql"
"encoding/binary"
"encoding/hex"
"strconv"
"io"
"log"
"math"
"math/big"
"net"
"fmt"
"pool/internal/db"
"pool/internal/msg"
"pool/internal/server/diff"
"pool/internal/utility"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/rs/zerolog"
"github.com/redis/go-redis/v9"
"github.com/zeromq/goczmq"
"go.uber.org/zap"
"gopkg.in/natefinch/lumberjack.v2"
_ "github.com/mattn/go-sqlite3"
)
const JOB_EXPIRED_TIME uint32 = 60 //job expired time (second)
const LOCAL_JOBS_EXPIRED_TIME uint32 = 300 //300 //local jobs expired time (second)
const LOCAL_JOBS_TOTAL_SIZE uint32 = 300 //300 //total local jobs
const CONN_EXPIRED_TIME uint32 = 600 //connection expired time
// miner status
// const MINER_STATUS_OFFLINE string = "offline"
const MINER_STATUS_CONNECTED string = "connected"
const MINER_STATUS_SUBSCRIBED string = "subscribed"
const MINER_STATUS_AUTHORIZED string = "authorized"
const MINER_STATUS_RUNNING string = "online"
const MINER_STATUS_DISCONNECTED string = "offline"
const MINER_STATUS_DISABLED string = "disabled"
const MINER_DURATION_TIME time.Duration = 1
const MINER_DIFFICULTY_ADJUST_DURATION time.Duration = 5
// vardiff
const UP_DIFF int = 0
const DOWN_DIFF int = 1
const UPDATE_DIFF int = 2
const DIFFICULTY_WAIT_TIMES int = 6
const (
Low = 0
Mid = 1
Hign = 2
)
type BindConfig struct {
Listen string `json:"listen"`
Auth bool `json:"auth"`
}
type DiffConfig struct {
StartDifficulty float64 `json:"start_diff"`
DiffMin float64 `json:"diff_min"`
DiffMax float64 `json:"diff_max"`
DiffAdjustInterval float64 `json:"diff_adjust_interval"`
DiffAdjustPercentage float64 `json:"diff_adjust_percentage"`
DiffAdjustTime float64 `json:"diff_adjust_time"`
Filter string `json:"filter"`
Dbg bool `json:"dbg"`
}
type ServerConfig struct {
Coin string `json:"coin"`
Host BindConfig `json:"host"`
Diff DiffConfig `json:"diff"`
Zmq utility.ZmqConfig `json:"zmq"`
Redis utility.RedisConfig `json:"redis"`
Zaplog zap.Config `json:"zap"`
Logrotae utility.LogRotateConfig `json:"logrotate"`
}
type CoinObj struct {
Coin string
Init func(server *ServerContext)
Start func()
Stop func()
InitMiner func(miner *MinerObj)
HandleMinerSubscribe func(miner *MinerObj, id float64, extranonce1 string, msg string)
HandleMinerAuth func(miner *MinerObj)
HandleMinerSubmit func(miner *MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool)
SetDifficulty func(miner *MinerObj)
Notify func(miner *MinerObj)
HandleJobMsg func(server *ServerContext, Msg []byte)
IsMhsLow func(miner *MinerObj) bool
GetBlockInterval func() int
}
type PoolBlkMsg struct {
Height int64
Hash string
Pow string
Net_target string
Submit string
Success bool
Accepts float64
Rejects float64
Reward float64
Fee float64
Nonce string
SubIdx int64
}
type ServerContext struct {
CoinCtx CoinObj
DbCtx *db.DbContext
Config *ServerConfig
PubCh *goczmq.Sock
SubCh *goczmq.Sock
Listener net.Listener
MinerType string
Extranonce1 uint64
Difficulty float64
RefDifficulty float64
Accepts float64
Rejects float64
AverageHashrate float64
Miners sync.Map
MMhs sync.Map
Started bool
SLock sync.Mutex
ExitFlag bool
//AlivingChan chan bool
//LiveingExpired bool
FlagAliving int32
FlagAlivingExit int32
ExitPingChan chan bool
ExitJobChan chan bool
Logg *zap.Logger
LogR *lumberjack.Logger
SJob msg.StratumJob
//UpdateMap sync.Map
SyncJobChan chan bool
Synced bool
ExitDbMiners chan bool
ExitDbMinersStats chan bool
ExitDbUser chan bool
ExitDbUserStats chan bool
ExitDbPoolStats chan bool
NexaJob msg.NexaStratumJob
AlphJob msg.AlphStratumJob
ExitDiffVar chan bool
RedisClient *redis.Client
Accepts5M float64
Accepts15M float64
Accepts30M float64
Accepts1h float64
Accepts3h float64
Accepts6h float64
Accepts12h float64
Accepts24h float64
Accepts48h float64
Rejects5M float64
Rejects15M float64
Rejects30M float64
Rejects1h float64
Rejects3h float64
Rejects6h float64
Rejects12h float64
Rejects24h float64
Rejects48h float64
Mhs5M float64
Mhs15M float64
Mhs30M float64
Mhs1h float64
Mhs3h float64
Mhs6h float64
Mhs12h float64
Mhs24h float64
Mhs48h float64
RejectRatio5M float64
RejectRatio15M float64
RejectRatio30M float64
RejectRatio1h float64
RejectRatio3h float64
RejectRatio6h float64
RejectRatio12h float64
RejectRatio24h float64
RejectRatio48h float64
TotalMiners int64
Normal int64
Abnormal int64
Offline int64
MhsZero int64
MhsLow int64
HighRejects int64
Unstable int64
NetTarget string
NetHight uint64
Submits int64
Blocks int64
Orphans int64
Reward float64
Fee float64
/*Users sync.Map
UsersSLock sync.Mutex*/
/*PoolSLock sync.Mutex*/
SubIdx int64
MinerIndex int64
//NotifyBlkDetailIdx int32
}
type JobListEntry struct {
Job_id string
Ts time.Time
}
type MhsItem struct {
Tt time.Time
Diff float64
}
type MhsObj struct {
MinerId string
Name string
Accepts []MhsItem
Rejects []MhsItem
StartSubmitTime time.Time
StartDayTime time.Time
User string
Miner string
Index string
Status string
Algo int
}
type VarDiffOptions struct {
VariancePercent float64
AdjustTime float64
AdjustInterval float64
MinDiff float64
MaxDiff float64
MinShares float64
MaxShares float64
TargetShares float64
SubmitShares float64
SilenceCount float64
LastCalcTime time.Time
Uptimes int
Downtimes int
Level int
LastSubmitTime time.Time
}
type BlockMsg struct {
Target string
Submit_target string
Height int64
Success bool
Pow string
Net_target string
Submit string
Hash string
Header string
Accepts float64
Total_accepts float64
Rejects float64
Total_rejects float64
Reward float64
Fee float64
Nonce string
SubIdx int64
}
type MinerObj struct {
Server *ServerContext
Conn net.Conn
Authorized bool
MinerId string
JobId uint32
Name string
Jobs sync.Map
LockForJobs sync.Mutex
JobList *list.List
Target *big.Int
Difficulty float64
DifficultyNext float64
ServerDifficulty float64
ServerTarget *big.Int
ServerTargetS string
Accepts float64
Rejects float64
StartSubmitTime time.Time
LastSubmitime time.Time
SubmitIndex uint32
AverageHashrate float64
M5Accepts float64
M5Hashrate float64
M5SubmitTime time.Time
LastJobId string
LastNonce string
CurHeight uint32
CurHeight64 uint64
Reconnect bool
LastHeader string
VarDiffOpt VarDiffOptions
Version int32
Job msg.StratumJob
User string
PassWord string
Miner string
Duration float64
Status string
ConnSetupTime time.Time
TxLock sync.Mutex
KeepliveCnt float64
RecvedLiveAck bool
PongFailCnt int
PingCnt int
NexaJob msg.NexaStratumJob
AlphJob msg.AlphStratumJob
//EndCh chan bool
FromIP string
OnlineTime time.Time
OfflineTime time.Time
Retry int64
DurationTime float64
MinerIndex int64
Protocol string
ErrStaleds int64
ErrLowDiffs int64
ErrDuplicates int64
ErrFormats int64
ErrOthers int64
IsDisabled bool
Submits int64
Blocks int64
Orphans int64
Accepts5M float64
Accepts15M float64
Accepts30M float64
Accepts1h float64
Accepts3h float64
Accepts6h float64
Accepts12h float64
Accepts24h float64
Accepts48h float64
Rejects5M float64
Rejects15M float64
Rejects30M float64
Rejects1h float64
Rejects3h float64
Rejects6h float64
Rejects12h float64
Rejects24h float64
Rejects48h float64
Mhs5M float64
Mhs15M float64
Mhs30M float64
Mhs1h float64
Mhs3h float64
Mhs6h float64
Mhs12h float64
Mhs24h float64
Mhs48h float64
RejectRatio5M float64
RejectRatio15M float64
RejectRatio30M float64
RejectRatio1h float64
RejectRatio3h float64
RejectRatio6h float64
RejectRatio12h float64
RejectRatio24h float64
RejectRatio48h float64
Reward float64
Fee float64
Zlog zerolog.Logger
LogR *lumberjack.Logger
ZlogInit bool
//EndCh chan bool
DiffHandler diff.KalmanVarDiff
NeedExit int32
}
/*type UserBlockMsg struct {
User string
Miner string
Index string
Height int64
Hash string
Pow string
Net_target string
Submit string
Success bool
Accepts float64
Rejects float64
Reward float64
Fee float64
Nonce string
SubIdx int64
}*/
/*
type UserMinerContainer struct {
Data map[string]string
}*/
/*
type UserObj struct {
Server *ServerContext
User string
Name string
Normal int64
Abnormal int64
Offline int64
MhsZero int64
MhsLow int64
HighRejects int64
Unstable int64
Submits int64
Blocks int64
Orphans int64
Reward float64
Fee float64
Accepts5M float64
Accepts15M float64
Accepts30M float64
Accepts1h float64
Accepts3h float64
Accepts6h float64
Accepts12h float64
Accepts24h float64
Accepts48h float64
Rejects5M float64
Rejects15M float64
Rejects30M float64
Rejects1h float64
Rejects3h float64
Rejects6h float64
Rejects12h float64
Rejects24h float64
Rejects48h float64
Mhs5M float64
Mhs15M float64
Mhs30M float64
Mhs1h float64
Mhs3h float64
Mhs6h float64
Mhs12h float64
Mhs24h float64
Mhs48h float64
RejectRatio5M float64
RejectRatio15M float64
RejectRatio30M float64
RejectRatio1h float64
RejectRatio3h float64
RejectRatio6h float64
RejectRatio12h float64
RejectRatio24h float64
RejectRatio48h float64
}*/
func md5md5(v string) string {
md5Obj := md5.New()
md5Obj.Write([]byte(v))
char := md5Obj.Sum(nil)
return hex.EncodeToString(char)
}
func Guid() string {
c := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, c); err != nil {
return ""
}
return md5md5(base64.URLEncoding.EncodeToString(c))
}
func VarAdjustDifficulty(miner *MinerObj, adjust int) {
if adjust != UP_DIFF && adjust != DOWN_DIFF && adjust != UPDATE_DIFF {
miner.Server.Logg.Error("[server]", zap.Int("Not support adjust ", adjust))
return
}
if adjust == UP_DIFF {
miner.DifficultyNext = miner.Difficulty
if miner.VarDiffOpt.Level == Mid {
miner.DifficultyNext *= math.Pow(2, 1)
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
} else if miner.VarDiffOpt.Level == Hign {
miner.DifficultyNext *= math.Pow(2, 2)
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
}
} else if adjust == DOWN_DIFF {
miner.DifficultyNext = miner.Difficulty
if miner.VarDiffOpt.Level == Mid {
miner.DifficultyNext /= math.Pow(2, 1)
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
} else if miner.VarDiffOpt.Level == Hign {
miner.DifficultyNext /= math.Pow(2, 2)
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
}
} else if adjust == UPDATE_DIFF {
if miner.VarDiffOpt.SubmitShares > 0 {
// re-target if outside bounds
if miner.VarDiffOpt.SubmitShares < miner.VarDiffOpt.MinShares || miner.VarDiffOpt.SubmitShares > miner.VarDiffOpt.MaxShares {
var change float64 = miner.VarDiffOpt.SubmitShares / miner.VarDiffOpt.TargetShares
miner.DifficultyNext = miner.Difficulty
miner.DifficultyNext *= change
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
}
miner.VarDiffOpt.SilenceCount = 0
} else {
// radical measures if there were no shares submitted
miner.VarDiffOpt.SilenceCount++
miner.DifficultyNext = miner.Difficulty / math.Pow(2, miner.VarDiffOpt.SilenceCount)
miner.DifficultyNext = math.Round(miner.DifficultyNext*1000) / 1000
}
}
if miner.DifficultyNext < miner.VarDiffOpt.MinDiff {
miner.DifficultyNext = miner.VarDiffOpt.MinDiff
} else if miner.DifficultyNext > miner.VarDiffOpt.MaxDiff {
miner.DifficultyNext = miner.VarDiffOpt.MaxDiff
}
miner.VarDiffOpt.TargetShares = miner.VarDiffOpt.AdjustTime / miner.VarDiffOpt.AdjustInterval * miner.DifficultyNext
miner.VarDiffOpt.MinShares = miner.VarDiffOpt.AdjustTime / miner.VarDiffOpt.AdjustInterval * miner.DifficultyNext * (1 - miner.VarDiffOpt.VariancePercent)
miner.VarDiffOpt.MaxShares = miner.VarDiffOpt.AdjustTime / miner.VarDiffOpt.AdjustInterval * miner.DifficultyNext * (1 + miner.VarDiffOpt.VariancePercent)
miner.VarDiffOpt.SubmitShares = 0
miner.VarDiffOpt.Uptimes = 0
miner.VarDiffOpt.Downtimes = 0
miner.Server.Logg.Info("[server]", zap.Float64("DifficultyNext", miner.DifficultyNext))
miner.Server.Logg.Info("[server]", zap.Float64("TargetShares", miner.VarDiffOpt.TargetShares), zap.Float64("MinShares", miner.VarDiffOpt.MinShares), zap.Float64("MaxShares", miner.VarDiffOpt.MaxShares))
now := time.Now()
share_interval := now.Sub(miner.LastSubmitime).Seconds()
New_diff_into_db(miner.User, miner.Miner, fmt.Sprint(miner.MinerIndex), miner.Difficulty, miner.DifficultyNext, miner.VarDiffOpt.SubmitShares, share_interval, miner.VarDiffOpt.MinShares, miner.VarDiffOpt.MaxShares)
}
var gdiff_db *sql.DB
func Init_diff_db() {
db, err := sql.Open("sqlite3", "./diffs.db")
if err != nil {
log.Printf("Error opening database: %v", err)
return
}
//defer db.Close()
gdiff_db = db
createTableSQL := `
CREATE TABLE IF NOT EXISTS diffs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
user TEXT NOT NULL,
miner TEXT NOT NULL,
minerid TEXT NOT NULL,
diff REAL NOT NULL,
next REAL NOT NULL,
kp REAL NOT NULL,
interval REAL NOT NULL,
mhs REAL NOT NULL,
mhs_est REAL NOT NULL
);`
_, err = gdiff_db.Exec(createTableSQL)
if err != nil {
log.Printf("Error creating table: %v", err)
return
}
}
func New_diff_into_db(user string, miner string, minerid string, diff float64, diff_next float64, kp float64, interval float64, mhs float64, mhs_est float64) {
if gdiff_db != nil {
insertSQL := `INSERT INTO diffs (ts, user, miner, minerid, diff, next, kp, interval, mhs, mhs_est) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
_, err := gdiff_db.Exec(insertSQL, time.Now().Format("2006-01-02 15:04:05"), user, miner, minerid, diff, diff_next, kp, interval, mhs, mhs_est)
if err != nil {
log.Printf("Error inserting data from diffs %s: %v", user+"."+miner+"_"+minerid, err)
return
}
}
}
func DiffStop() {
if gdiff_db != nil {
defer gdiff_db.Close()
}
}
func Build_coinbase(coinbase1 string, extranonce1 string, extranonce2, coinbase2 string, doublehash bool) ([]byte, error) {
var coinbase_string string = coinbase1 + extranonce1 + extranonce2 + coinbase2
coinbase_bytes, err := hex.DecodeString(coinbase_string)
if err != nil {
//log.Println("[dash]failed to Build_coinbase", err)
return nil, err
}
//fmt.Println("coinbase", coinbase_string, hex.EncodeToString(coinbase_bytes))
var coinbase []byte
if doublehash {
coinbase = chainhash.DoubleHashB(coinbase_bytes)
} else {
coinbase = chainhash.HashB(coinbase_bytes)
}
//fmt.Println("coinbase_hash", hex.EncodeToString(coinbase))
//fmt.Println("coinbase_hash", coinbase)
return coinbase, nil
}
// build merkel root hash value
func Build_merkle_root(coinbase []byte, transaction_list *[]string) ([]byte, error) {
var merkle_root []byte
merkle_root = coinbase
count := len(*transaction_list)
var i int
for i = 0; i < count; i++ {
//fmt.Println("merkle_hash start", i, hex.EncodeToString(merkle_root))
t_bytes, err := hex.DecodeString((*transaction_list)[i])
if err != nil {
//log.Println("[dash]failed to Build_merkle_root", err)
return nil, err
}
for j := 0; j < len(t_bytes); j++ {
merkle_root = append(merkle_root, t_bytes[j])
}
//fmt.Println("merkle_hash append", i, hex.EncodeToString(merkle_root))
merkle := chainhash.DoubleHashB(merkle_root)
//fmt.Println("merkle_hash", i, hex.EncodeToString(merkle))
merkle_root = merkle
}
return merkle_root, nil
}
func BuildBlockHash(job *msg.StratumJob, doublehash bool, pow func(h wire.BlockHeader) chainhash.Hash) ([]byte, wire.BlockHeader) {
var bh wire.BlockHeader
//log.Printf("[dash]c1 %s,e1 %s,e2 %s,c2 %s\n", job.Coinbase1, job.Extranonce1, job.Extranonce2, job.Coinbase2)
coinbase, err := Build_coinbase(job.Coinbase1, job.Extranonce1, job.Extranonce2, job.Coinbase2, doublehash)
if err != nil {
return nil, bh
}
//log.Println("[dash]trans", job.Transactions)
merkle_root, err := Build_merkle_root(coinbase, job.Transactions)
if err != nil {
return nil, bh
}
vb := make([]byte, 4)
binary.LittleEndian.PutUint32(vb, uint32(job.Version))
tb := make([]byte, 4)
binary.LittleEndian.PutUint32(tb, uint32(job.Timestamp.Unix()))
bh, err = Build_block_hash(hex.EncodeToString(vb), job.PrevblockBig, merkle_root, hex.EncodeToString(tb), job.BitsS, job.Nonce)
if err != nil {
return nil, bh
}
powhash := pow(bh)
//log.Printf("POW hash:%s", powhash.String())
return powhash.CloneBytes(), bh
}
func Build_block_hash(block_version string, prev_hash string, merkle_root []byte, ntime string, nbits string, nonce string) (wire.BlockHeader, error) {
var header wire.BlockHeader
b, err := strconv.ParseUint(nbits, 16, 32)
if err != nil {
//log.Println("[dash]failed to Build_block_hash", err)
return header, err
}
header.Bits = uint32(b)
//log.Printf("Bits:%x", header.Bits)
p_bytes, err := hex.DecodeString(prev_hash)
if err != nil {
log.Println("[dash]failed to Build_block_hash", err)
return header, err
}
p, err := chainhash.NewHash(utility.Convert_big_endian(p_bytes))
header.PrevBlock = *p
//log.Printf("Prev Block:%s", hex.EncodeToString(header.PrevBlock.CloneBytes()))
m, err := chainhash.NewHash(merkle_root)
header.MerkleRoot = *m
//log.Printf("MerkleRoot:%s", hex.EncodeToString(header.MerkleRoot.CloneBytes()))
n, err := strconv.ParseUint(nonce, 16, 32)
if err != nil {
//log.Println("[dash]failed to Build_block_hash", err)
return header, err
}
header.Nonce = uint32(n)
//log.Printf("Nonce:%x", header.Nonce)
v, err := hex.DecodeString(block_version)
if err != nil {
//log.Println("[dash]failed to Build_block_hash", err)
return header, err
}
vBuffer := bytes.NewBuffer(v)
binary.Read(vBuffer, binary.BigEndian, &(header.Version))
//log.Printf("Version:%x", header.Version)
t, err := strconv.ParseInt(ntime, 16, 64)
if err != nil {
//log.Println("[dash]failed to Build_block_hash", err)
return header, err
}
header.Timestamp = time.Unix(t, 0)
//log.Printf("nTime %v", header.Timestamp)
return header, nil
}