Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

highchart + hashLimit + /api/settings + etc. #336

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4badf93
support hashLimit
hackmod Apr 1, 2018
ae0051b
support Ethersocial network
hackmod Apr 1, 2018
1d10621
redis: support unix domain socket
hackmod Apr 6, 2018
a683efd
support /api/settings
hackmod Apr 13, 2018
fce7676
support Callisto Network
hackmod Apr 17, 2018
bd505b0
i18nized
hackmod Apr 18, 2018
49e571b
support earnings per day
hackmod Apr 18, 2018
48d8b98
fixed select languages menu
hackmod Apr 19, 2018
8a53f11
fixup config.unit
hackmod Apr 18, 2018
e4c09af
fixup style
hackmod Mar 19, 2018
24e3c8c
app: change default font
hackmod Apr 18, 2018
a84c4e2
www/app: change icons
hackmod Apr 18, 2018
5895a27
www/app: fixed api URL
hackmod Apr 18, 2018
0abfd95
www/app: read the external pool settings from /api/settings
hackmod Apr 19, 2018
5ea9c4c
support Highcharts
rewrewby Jan 16, 2018
3330c57
highcharts: fixed jshint errors
hackmod Apr 20, 2018
9d28bbf
www/app: make highcharts configurable
hackmod Apr 20, 2018
3e54241
support payout charts
ComBba Apr 13, 2018
baf8f5b
fixed jshint errors
hackmod Apr 13, 2018
77e7f2d
highcharts: fixed chart updater
hackmod Apr 20, 2018
7ce6693
support charts separate api URLs
hackmod Apr 20, 2018
b973c07
www/app: support separate api urls
hackmod Apr 20, 2018
6c9f0a0
www/app: workaround strange highchart error
hackmod Apr 20, 2018
fbdc2ab
api: api URL /api/stats support "blockReward" info at the current blo…
hackmod Apr 20, 2018
4f991cf
reverse chart results to fixup highchart#15 error
hackmod Apr 28, 2018
1f0dd95
payout charts: append empty data at the end
hackmod Apr 28, 2018
e1734a8
highcharts: check timestamp and update chart again
hackmod Apr 28, 2018
f4a1063
remove duplicated refresh calls
hackmod Apr 29, 2018
d987afa
fixup language menu
hackmod Apr 29, 2018
f5f96e5
hide menu on mobile device
hackmod Apr 29, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 150 additions & 3 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,32 @@ package api

import (
"encoding/json"
"fmt"
"log"
"net/http"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/gorilla/mux"
"github.com/robfig/cron"

"github.com/sammy007/open-ethereum-pool/payouts"
"github.com/sammy007/open-ethereum-pool/rpc"
"github.com/sammy007/open-ethereum-pool/storage"
"github.com/sammy007/open-ethereum-pool/util"
)

type ApiConfig struct {
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
PoolCharts string `json:"poolCharts"`
PoolChartsNum int64 `json:"poolChartsNum"`
MinerChartsNum int64 `json:"minerChartsNum"`
MinerCharts string `json:"minerCharts"`
StatsCollectInterval string `json:"statsCollectInterval"`
HashrateWindow string `json:"hashrateWindow"`
HashrateLargeWindow string `json:"hashrateLargeWindow"`
Expand All @@ -30,6 +39,7 @@ type ApiConfig struct {
}

type ApiServer struct {
settings map[string]interface{}
config *ApiConfig
backend *storage.RedisClient
hashrateWindow time.Duration
Expand All @@ -38,22 +48,37 @@ type ApiServer struct {
miners map[string]*Entry
minersMu sync.RWMutex
statsIntv time.Duration
rpc *rpc.RPCClient
genesisHash string
}

type Entry struct {
stats map[string]interface{}
updatedAt int64
hasChart bool
}

func NewApiServer(cfg *ApiConfig, backend *storage.RedisClient) *ApiServer {
func NewApiServer(cfg *ApiConfig, settings map[string]interface{}, backend *storage.RedisClient) *ApiServer {
rpcDaemon := settings["BlockUnlocker"].(map[string]interface{})["Daemon"].(string)
rpcTimeout := settings["BlockUnlocker"].(map[string]interface{})["Timeout"].(string)
rpc := rpc.NewRPCClient("BlockUnlocker", rpcDaemon, rpcTimeout)

block, err := rpc.GetBlockByHeight(0)
if err != nil || block == nil {
log.Fatalf("Error while retrieving genesis block from node: %v", err)
}

hashrateWindow := util.MustParseDuration(cfg.HashrateWindow)
hashrateLargeWindow := util.MustParseDuration(cfg.HashrateLargeWindow)
return &ApiServer{
settings: settings,
config: cfg,
backend: backend,
hashrateWindow: hashrateWindow,
hashrateLargeWindow: hashrateLargeWindow,
miners: make(map[string]*Entry),
rpc: rpc,
genesisHash: block.Hash,
}
}

Expand Down Expand Up @@ -96,18 +121,77 @@ func (s *ApiServer) Start() {
}
}()

go func() {
c := cron.New()

poolCharts := s.config.PoolCharts
log.Printf("pool charts config is :%v", poolCharts)
c.AddFunc(poolCharts, func() {
s.collectPoolCharts()
})

minerCharts := s.config.MinerCharts
log.Printf("miner charts config is :%v", minerCharts)
c.AddFunc(minerCharts, func() {

miners, err := s.backend.GetAllMinerAccount()
if err != nil {
log.Println("Get all miners account error: ", err)
}
for _, login := range miners {
miner, _ := s.backend.CollectWorkersStats(s.hashrateWindow, s.hashrateLargeWindow, login)
s.collectMinerCharts(login, miner["currentHashrate"].(int64), miner["hashrate"].(int64), miner["workersOnline"].(int64))
}
})

c.Start()
}()

if !s.config.PurgeOnly {
s.listen()
}
}

func (s *ApiServer) collectPoolCharts() {
ts := util.MakeTimestamp() / 1000
now := time.Now()
year, month, day := now.Date()
hour, min, _ := now.Clock()
t2 := fmt.Sprintf("%d-%02d-%02d %02d_%02d", year, month, day, hour, min)
stats := s.getStats()
hash := fmt.Sprint(stats["hashrate"])
log.Println("Pool Hash is ", ts, t2, hash)
err := s.backend.WritePoolCharts(ts, t2, hash)
if err != nil {
log.Printf("Failed to fetch pool charts from backend: %v", err)
return
}
}

func (s *ApiServer) collectMinerCharts(login string, hash int64, largeHash int64, workerOnline int64) {
ts := util.MakeTimestamp() / 1000
now := time.Now()
year, month, day := now.Date()
hour, min, _ := now.Clock()
t2 := fmt.Sprintf("%d-%02d-%02d %02d_%02d", year, month, day, hour, min)

log.Println("Miner "+login+" Hash is", ts, t2, hash, largeHash)
err := s.backend.WriteMinerCharts(ts, t2, login, hash, largeHash, workerOnline)
if err != nil {
log.Printf("Failed to fetch miner %v charts from backend: %v", login, err)
}
}

func (s *ApiServer) listen() {
r := mux.NewRouter()
r.HandleFunc("/api/stats", s.StatsIndex)
r.HandleFunc("/api/stats/{chart:charts?}", s.StatsIndex)
r.HandleFunc("/api/miners", s.MinersIndex)
r.HandleFunc("/api/blocks", s.BlocksIndex)
r.HandleFunc("/api/payments", s.PaymentsIndex)
r.HandleFunc("/api/settings", s.Settings)
r.HandleFunc("/api/accounts/{login:0x[0-9a-fA-F]{40}}", s.AccountIndex)
r.HandleFunc("/api/accounts/{login:0x[0-9a-fA-F]{40}}/{chart:charts?}", s.AccountIndex)
r.NotFoundHandler = http.HandlerFunc(notFound)
err := http.ListenAndServe(s.config.Listen, r)
if err != nil {
Expand Down Expand Up @@ -146,6 +230,7 @@ func (s *ApiServer) collectStats() {
return
}
}
stats["poolCharts"], err = s.backend.GetPoolCharts(s.config.PoolChartsNum)
s.stats.Store(stats)
log.Printf("Stats collection finished %s", time.Since(start))
}
Expand All @@ -156,17 +241,27 @@ func (s *ApiServer) StatsIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)

chart := strings.ToLower(mux.Vars(r)["chart"])

reply := make(map[string]interface{})
nodes, err := s.backend.GetNodeStates()
if err != nil {
log.Printf("Failed to get nodes stats from backend: %v", err)
}
reply["nodes"] = nodes
if nodes[0] != nil {
height := nodes[0]["height"].(string)
number, _ := strconv.ParseInt(height, 10, 64)
reply["blockReward"] = payouts.GetConstReward(number).String()
}

stats := s.getStats()
if stats != nil {
reply["now"] = util.MakeTimestamp()
reply["stats"] = stats["stats"]
if chart != "" {
reply["poolCharts"] = stats["poolCharts"]
}
reply["hashrate"] = stats["hashrate"]
reply["minersTotal"] = stats["minersTotal"]
reply["maturedTotal"] = stats["maturedTotal"]
Expand Down Expand Up @@ -250,14 +345,16 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache")

login := strings.ToLower(mux.Vars(r)["login"])
chart := strings.ToLower(mux.Vars(r)["chart"])
useChart := chart != ""
s.minersMu.Lock()
defer s.minersMu.Unlock()

reply, ok := s.miners[login]
now := util.MakeTimestamp()
cacheIntv := int64(s.statsIntv / time.Millisecond)
// Refresh stats if stale
if !ok || reply.updatedAt < now-cacheIntv {
if !ok || reply.updatedAt < now-cacheIntv || (useChart && !reply.hasChart) {
exist, err := s.backend.IsMinerExists(login)
if !exist {
w.WriteHeader(http.StatusNotFound)
Expand Down Expand Up @@ -285,7 +382,23 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) {
stats[key] = value
}
stats["pageSize"] = s.config.Payments
reply = &Entry{stats: stats, updatedAt: now}
if useChart {
charts, err := s.backend.GetMinerCharts(s.config.MinerChartsNum, login)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Failed to fetch charts from backend: %v", err)
return
}
stats["minerCharts"] = charts
payments, err := s.backend.GetPaymentCharts(login)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Failed to fetch payments from backend: %v", err)
return
}
stats["paymentCharts"] = payments
}
reply = &Entry{stats: stats, updatedAt: now, hasChart: useChart}
s.miners[login] = reply
}

Expand All @@ -296,6 +409,40 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) {
}
}

func (s *ApiServer) Settings(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Cache-Control", "max-age=600")
w.WriteHeader(http.StatusOK)

reply := make(map[string]interface{})

reply["HashLimit"] = s.settings["Proxy"].(map[string]interface{})["HashLimit"]
reply["Difficulty"] = s.settings["Proxy"].(map[string]interface{})["Difficulty"]

reply["PoolFee"] = s.settings["BlockUnlocker"].(map[string]interface{})["PoolFee"]
reply["PoolFeeAddress"] = s.settings["BlockUnlocker"].(map[string]interface{})["PoolFeeAddress"]
reply["Donate"] = s.settings["BlockUnlocker"].(map[string]interface{})["Donate"]
reply["DonateFee"] = s.settings["BlockUnlocker"].(map[string]interface{})["DonateFee"]
reply["DonateAddress"] = s.settings["BlockUnlocker"].(map[string]interface{})["DonateAddress"]
reply["KeyTxFees"] = s.settings["BlockUnlocker"].(map[string]interface{})["KeepTxFees"]
reply["BlockUnlockDepth"] = s.settings["BlockUnlocker"].(map[string]interface{})["Depth"]

reply["EthProxy"] = s.settings["Proxy"].(map[string]interface{})["Enabled"]
reply["EthProxyPool"] = s.settings["Proxy"].(map[string]interface{})["Listen"]
reply["Stratum"] = s.settings["Proxy"].(map[string]interface{})["Stratum"].(map[string]interface{})["Enabled"]
reply["StratumPool"] = s.settings["Proxy"].(map[string]interface{})["Stratum"].(map[string]interface{})["Listen"]
reply["PayoutThreshold"] = s.settings["Payouts"].(map[string]interface{})["Threshold"]
reply["PayoutInterval"] = s.settings["Payouts"].(map[string]interface{})["Interval"]

reply["GenesisHash"] = s.genesisHash

err := json.NewEncoder(w).Encode(reply)
if err != nil {
log.Println("Error serializing API response: ", err)
}
}

func (s *ApiServer) getStats() map[string]interface{} {
stats := s.stats.Load()
if stats != nil {
Expand Down
7 changes: 6 additions & 1 deletion config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "main",

"proxy": {
"hashLimit" : 240000000,
"enabled": true,
"listen": "0.0.0.0:8888",
"limitHeadersSize": 1024,
Expand Down Expand Up @@ -56,7 +57,11 @@
"hashrateLargeWindow": "3h",
"luckWindow": [64, 128, 256],
"payments": 30,
"blocks": 50
"blocks": 50,
"poolCharts":"0 */20 * * * *",
"poolChartsNum":74,
"minerCharts":"0 */20 * * * *",
"minerChartsNum":74
},

"upstreamCheckInterval": "5s",
Expand Down
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"runtime"
"time"

"github.com/fatih/structs"
"github.com/yvasiyarov/gorelic"

"github.com/sammy007/open-ethereum-pool/api"
Expand All @@ -28,7 +29,8 @@ func startProxy() {
}

func startApi() {
s := api.NewApiServer(&cfg.Api, backend)
settings := structs.Map(&cfg)
s := api.NewApiServer(&cfg.Api, settings, backend)
s.Start()
}

Expand Down
37 changes: 32 additions & 5 deletions payouts/unlocker.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type UnlockerConfig struct {
}

const minDepth = 16
const byzantiumHardForkHeight = 4370000

var byzantiumHardForkHeight int64 = 4370000

var homesteadReward = math.MustParseBig256("5000000000000000000")
var byzantiumReward = math.MustParseBig256("3000000000000000000")
Expand Down Expand Up @@ -58,6 +59,32 @@ func NewBlockUnlocker(cfg *UnlockerConfig, backend *storage.RedisClient) *BlockU
}
u := &BlockUnlocker{config: cfg, backend: backend}
u.rpc = rpc.NewRPCClient("BlockUnlocker", cfg.Daemon, cfg.Timeout)

block, err := u.rpc.GetBlockByHeight(0)
if err != nil || block == nil {
log.Fatalf("Error while retrieving genesis block from node: %v", err)
}

// EtherSocial Network
if block.Hash == "0x310dd3c4ae84dd89f1b46cfdd5e26c8f904dfddddc73f323b468127272e20e9f" {
log.Printf("Found genesis.hash is %v", block.Hash)
byzantiumHardForkHeight = 600000
homesteadReward = math.MustParseBig256("9000000000000000000")
byzantiumReward = math.MustParseBig256("5000000000000000000")

log.Printf("Set byzantiumHardForkHeight to %v", byzantiumHardForkHeight)
log.Printf("Set homesteadReward to %v", homesteadReward)
log.Printf("Set byzantiumReward to %v", byzantiumReward)
} else if block.Hash == "0x82270b80fc90beb005505a9ef95039639968a0e81b2904ad30128c93d713d2c4" {
// CLO Network
log.Printf("Found genesis.hash is %v", block.Hash)
byzantiumHardForkHeight = 0
byzantiumReward = math.MustParseBig256("420000000000000000000")

log.Printf("Set byzantiumHardForkHeight(not used) to %v", byzantiumHardForkHeight)
log.Printf("Set homesteadReward(not used) to %v", homesteadReward)
log.Printf("Set byzantiumReward(CLO reward) to %v", byzantiumReward)
}
return u
}

Expand Down Expand Up @@ -209,7 +236,7 @@ func (u *BlockUnlocker) handleBlock(block *rpc.GetBlockReply, candidate *storage
return err
}
candidate.Height = correctHeight
reward := getConstReward(candidate.Height)
reward := GetConstReward(candidate.Height)

// Add TX fees
extraTxReward, err := u.getExtraRewardForTx(block)
Expand Down Expand Up @@ -501,20 +528,20 @@ func weiToShannonInt64(wei *big.Rat) int64 {
return value
}

func getConstReward(height int64) *big.Int {
func GetConstReward(height int64) *big.Int {
if height >= byzantiumHardForkHeight {
return new(big.Int).Set(byzantiumReward)
}
return new(big.Int).Set(homesteadReward)
}

func getRewardForUncle(height int64) *big.Int {
reward := getConstReward(height)
reward := GetConstReward(height)
return new(big.Int).Div(reward, new(big.Int).SetInt64(32))
}

func getUncleReward(uHeight, height int64) *big.Int {
reward := getConstReward(height)
reward := GetConstReward(height)
k := height - uHeight
reward.Mul(big.NewInt(8-k), reward)
reward.Div(reward, big.NewInt(8))
Expand Down
Loading