From e1d566559781f5a3dcde41b61a26ac7ab75f70e4 Mon Sep 17 00:00:00 2001 From: swordlet Date: Tue, 14 May 2024 22:07:23 +0800 Subject: [PATCH] add new RPC APIs --- config.example.json | 6 +- kvstore/query.go | 17 ++-- main.go | 3 + stratum/api.go | 207 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 11 deletions(-) diff --git a/config.example.json b/config.example.json index 48bac3a..fe82303 100644 --- a/config.example.json +++ b/config.example.json @@ -46,7 +46,7 @@ "listen": "0.0.0.0:8082", "login": "admin", "password": "", - "hideIP": false + "hideIP": true }, "kvrocks": { "endpoint": "127.0.0.1:6379", @@ -56,8 +56,8 @@ }, "payout": { "poolRation": 5.0, - "rewardRation": 5.0, - "directRation": 5.0, + "rewardRation": 0, + "directRation": 0, "threshold": 3, "paymentInterval": "10m", "mode": "equal", diff --git a/kvstore/query.go b/kvstore/query.go index 16c7c44..8797262 100644 --- a/kvstore/query.go +++ b/kvstore/query.go @@ -91,19 +91,20 @@ func (r *KvClient) GetPoolRewardsList(start, end int64) ([]PoolRewardsData, erro func (r *KvClient) GetMinerAccount(address string) (float64, float64, float64, error) { reward, err := r.client.HGet(ctx, r.formatKey("account", address), "reward").Int64() if err != nil { - util.Error.Println("get miner total donate error", err, address) - return 0, 0, 0, err + util.Error.Println("get miner total reward error", err, address) + return 0, 0, 0, nil } - payment, err := r.client.HGet(ctx, r.formatKey("account", address), "payment").Int64() - if err != nil { - util.Error.Println("get miner total payment error", err, address) - return 0, 0, 0, err - } unpaid, err := r.client.HGet(ctx, r.formatKey("account", address), "unpaid").Int64() if err != nil { util.Error.Println("get miner total unpaid error", err, address) - return 0, 0, 0, err + return float64(reward) / 1e9, 0, 0, nil + } + + payment, err := r.client.HGet(ctx, r.formatKey("account", address), "payment").Int64() + if err != nil { + util.Error.Println("get miner total payment error", err, address) + return float64(reward) / 1e9, 0, float64(unpaid) / 1e9, nil } return float64(reward) / 1e9, float64(payment) / 1e9, float64(unpaid) / 1e9, nil } diff --git a/main.go b/main.go index 5a543bc..5f3f668 100644 --- a/main.go +++ b/main.go @@ -123,6 +123,9 @@ func startFrontend(cfg *pool.Config, s *stratum.StratumServer) { apiServer.Add("xdag_getPoolWorkers", s.XdagGetPoolWorkers) apiServer.Add("xdag_poolConfig", s.XdagPoolConfig) apiServer.Add("xdag_updatePoolConfig", s.XdagUpdatePoolConfig) + apiServer.Add("xdag_minerAccount", s.XdagMinerAccount) + apiServer.Add("xdag_minerHashrate", s.XdagMinerHashrate) + apiServer.Add("xdag_poolHashrate", s.XdagPoolHashrate) err := apiServer.Run(cfg.Frontend.Listen) if err != nil { diff --git a/stratum/api.go b/stratum/api.go index 7e74cc6..2c37f66 100644 --- a/stratum/api.go +++ b/stratum/api.go @@ -824,3 +824,210 @@ func (s *StratumServer) XdagGetPoolWorkers(id uint64, params json.RawMessage) jr return jrpc.EncodeResponse(id, rec, nil) } + +type MinerAccount struct { + Address string `json:"address"` + Timestamp int64 `json:"timestamp"` + TotalReward float64 `json:"total_reward"` + TotalPayment float64 `json:"total_payment"` + TotalUnpaid float64 `json:"total_unpaid"` +} + +func (s *StratumServer) XdagMinerAccount(id uint64, params json.RawMessage) jrpc.Response { + now := util.MakeTimestamp() + + var args []json.RawMessage + if err := json.Unmarshal(params, &args); err != nil { + return jrpc.EncodeResponse(id, struct{}{}, err) + } + if len(args) != 1 { + return jrpc.EncodeResponse(id, struct{}{}, errors.New("params length error")) + } + + var address string + err := json.Unmarshal(args[0], &address) + if err != nil { + return jrpc.EncodeResponse(id, struct{}{}, err) + } + + if address == "" || !util.ValidateAddress(address) { + return jrpc.EncodeResponse(id, struct{}{}, errors.New("addres is empty or invalid")) + } + reward, payment, unpaid, err := s.backend.GetMinerAccount(address) + if err != nil { + return jrpc.EncodeResponse(id, struct{}{}, err) + } + + data := MinerAccount{ + Address: address, + Timestamp: now, + TotalReward: reward, + TotalPayment: payment, + TotalUnpaid: unpaid, + } + return jrpc.EncodeResponse(id, data, nil) +} + +type MinerHashrate struct { + Address string `json:"address"` + Timestamp int64 `json:"timestamp"` + TotalHashrate float64 `json:"total_hashrate"` + TotalHashrate24h float64 `json:"total_hashrate24h"` + TotalOnline int `json:"total_online"` + Hashrate []WorkerHashrate `json:"hashrate"` +} + +type WorkerHashrate struct { + Name string `json:"name"` + Hashrate float64 `json:"hashrate"` + Hashrate24h float64 `json:"hashrate24h"` + LastBeat int64 `json:"lastBeat"` + ValidShares int64 `json:"validShares"` + StaleShares int64 `json:"staleShares"` + InvalidShares int64 `json:"invalidShares"` + Accepts int64 `json:"accepts"` + Rejects int64 `json:"rejects"` + Ip string `json:"ip"` + Warning bool `json:"warning"` + Timeout bool `json:"timeout"` +} + +func (s *StratumServer) XdagMinerHashrate(id uint64, params json.RawMessage) jrpc.Response { + now := util.MakeTimestamp() + + var args []json.RawMessage + if err := json.Unmarshal(params, &args); err != nil { + return jrpc.EncodeResponse(id, struct{}{}, err) + } + if len(args) != 1 { + return jrpc.EncodeResponse(id, struct{}{}, errors.New("params length error")) + } + + var address string + err := json.Unmarshal(args[0], &address) + if err != nil { + return jrpc.EncodeResponse(id, struct{}{}, err) + } + + if address == "" || !util.ValidateAddress(address) { + return jrpc.EncodeResponse(id, struct{}{}, errors.New("addres is empty or invalid")) + } + workers := s.workers.GetWorkers(address) + if len(workers) == 0 { + return jrpc.EncodeResponse(id, struct{}{}, errors.New("no workers")) + } + + var result []WorkerHashrate + window24h := 24 * time.Hour + + minerHashrate := MinerHashrate{ + Address: address, + Timestamp: now, + } + + dataChan := make(chan WorkerHashrate, len(workers)) + + for _, w := range workers { + go func(adress, w string) { + m, ok := s.miners.Get(address + "." + w) + if !ok { + dataChan <- WorkerHashrate{} + return + } + + lastBeat := m.getLastBeat() + hashrate := m.hashrate(s.estimationWindow) + hashrate24h := m.hashrate(window24h) + stats := WorkerHashrate{ + Name: w, + Hashrate: hashrate, + Hashrate24h: hashrate24h, + LastBeat: lastBeat, + ValidShares: atomic.LoadInt64(&m.validShares), + StaleShares: atomic.LoadInt64(&m.staleShares), + InvalidShares: atomic.LoadInt64(&m.invalidShares), + Accepts: atomic.LoadInt64(&m.accepts), + Rejects: atomic.LoadInt64(&m.rejects), + } + if !s.config.Frontend.HideIP { + stats.Ip = m.ip + } else { + stats.Ip = "-" + + } + + if now-lastBeat > (int64(s.timeout/2) / 1000000) { + stats.Warning = true + } + if now-lastBeat > (int64(s.timeout) / 1000000) { + stats.Timeout = true + } + dataChan <- stats + }(address, w) + } + + i := 0 + var hashrate float64 + var hashrate24h float64 + totalOnline := 0 + + for { + select { + case <-time.After(10 * time.Second): + return jrpc.EncodeResponse(id, struct{}{}, errors.New("timeout")) + case v := <-dataChan: + if v.Name != "" { + result = append(result, v) + } + i++ + hashrate += v.Hashrate + hashrate24h += hashrate24h + if !v.Timeout { + totalOnline++ + } + + if i == len(workers) { + minerHashrate.TotalHashrate = hashrate + minerHashrate.TotalHashrate24h = hashrate24h + minerHashrate.TotalOnline = totalOnline + minerHashrate.Hashrate = result + return jrpc.EncodeResponse(id, minerHashrate, nil) + } + } + } + +} + +type PoolHashrate struct { + Hashrate float64 `json:"hashrate"` + Hashrate24h float64 `json:"hashrate24h"` + Total int `json:"total"` + TotalOnline int `json:"total_online"` +} + +func (s *StratumServer) XdagPoolHashrate(id uint64, params json.RawMessage) jrpc.Response { + now := util.MakeTimestamp() + totalhashrate := float64(0) + totalhashrate24h := float64(0) + total := 0 + totalOnline := 0 + window24h := 24 * time.Hour + + for m := range s.miners.Iter() { + lastBeat := m.Val.getLastBeat() + hashrate := m.Val.hashrate(s.estimationWindow) + hashrate24h := m.Val.hashrate(window24h) + totalhashrate += hashrate + totalhashrate24h += hashrate24h + total++ + if now-lastBeat <= (int64(s.timeout) / 1000000) { + totalOnline++ + } + } + return jrpc.EncodeResponse(id, PoolHashrate{ + Hashrate: totalhashrate, + Hashrate24h: totalhashrate24h, + Total: total, + TotalOnline: totalOnline, + }, nil) +}