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