diff --git a/api/server.go b/api/server.go index 8d01bcc3..12c08db6 100644 --- a/api/server.go +++ b/api/server.go @@ -4,18 +4,20 @@ import ( "encoding/json" "fmt" "log" - "net" "net/http" "sort" "strconv" + "strings" "sync" "sync/atomic" "time" + "math/big" + "github.com/gorilla/mux" - "github.com/robfig/cron" + "github.com/robfig/cron" "github.com/yuriy0803/open-etc-pool-friends/storage" "github.com/yuriy0803/open-etc-pool-friends/util" ) @@ -23,14 +25,6 @@ import ( type ApiConfig struct { Enabled bool `json:"enabled"` Listen string `json:"listen"` - PoolCharts string `json:"poolCharts"` - PoolChartsNum int64 `json:"poolChartsNum"` - NetCharts string `json:"netCharts"` - NetChartsNum int64 `json:"netChartsNum"` - MinerChartsNum int64 `json:"minerChartsNum"` - MinerCharts string `json:"minerCharts"` - ShareCharts string `json:"shareCharts"` - ShareChartsNum int64 `json:"shareChartsNum"` StatsCollectInterval string `json:"statsCollectInterval"` HashrateWindow string `json:"hashrateWindow"` HashrateLargeWindow string `json:"hashrateLargeWindow"` @@ -39,6 +33,14 @@ type ApiConfig struct { Blocks int64 `json:"blocks"` PurgeOnly bool `json:"purgeOnly"` PurgeInterval string `json:"purgeInterval"` + PoolCharts string `json:"poolCharts"` + PoolChartsNum int64 `json:"poolChartsNum"` + MinerChartsNum int64 `json:"minerChartsNum"` + NetCharts string `json:"netCharts"` + NetChartsNum int64 `json:"netChartsNum"` + MinerCharts string `json:"minerCharts"` + ShareCharts string `json:"shareCharts"` + ShareChartsNum int64 `json:"shareChartsNum"` } type ApiServer struct { @@ -57,6 +59,8 @@ type Entry struct { updatedAt int64 } +const diff = 4000000000 + func NewApiServer(cfg *ApiConfig, backend *storage.RedisClient) *ApiServer { hashrateWindow := util.MustParseDuration(cfg.HashrateWindow) hashrateLargeWindow := util.MustParseDuration(cfg.HashrateLargeWindow) @@ -110,7 +114,6 @@ 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() { @@ -126,14 +129,16 @@ func (s *ApiServer) Start() { 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)) + } }) @@ -176,120 +181,155 @@ func (s *ApiServer) Start() { } } -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) +func (s *ApiServer) listen() { + r := mux.NewRouter() + r.HandleFunc("/api/stats", s.StatsIndex) + r.HandleFunc("/api/finders", s.FindersIndex) + r.HandleFunc("/api/miners", s.MinersIndex) + r.HandleFunc("/api/blocks", s.BlocksIndex) + r.HandleFunc("/api/payments", s.PaymentsIndex) + r.HandleFunc("/api/accounts/{login:0x[0-9a-fA-F]{40}}", s.AccountIndex) + r.HandleFunc("/api/settings", s.SubscribeHandler).Methods("POST") + r.HandleFunc("/api/mining", s.MiningHandler).Methods("POST") + r.NotFoundHandler = http.HandlerFunc(notFound) + err := http.ListenAndServe(s.config.Listen, r) + if err != nil { + log.Fatalf("Failed to start API: %v", err) + } +} + +func (s *ApiServer) FindersIndex(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", "no-cache") + w.WriteHeader(http.StatusOK) + + reply := make(map[string]interface{}) stats := s.getStats() - hash := fmt.Sprint(stats["hashrate"]) - log.Println("Pool Hash is ", ts, t2, hash) - err := s.backend.WritePoolCharts(ts, t2, hash) + if stats != nil { + reply["now"] = util.MakeTimestamp() + reply["finders"] = stats["finders"] + } + + err := json.NewEncoder(w).Encode(reply) if err != nil { - log.Printf("Failed to fetch pool charts from backend: %v", err) - return + log.Println("Error serializing API response: ", err) } } -func (s *ApiServer) collectnetCharts() { +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() - //diff := fmt.Sprint(stats["difficulty"]) - nodes, erro := s.backend.GetNodeStates() - if erro != nil { - log.Printf("Failed to fetch Diff charts from backend: %v", erro) - return - } - diff := fmt.Sprint(nodes[0]["difficulty"]) - log.Println("Difficulty Hash is ", ts, t2, diff) - err := s.backend.WriteDiffCharts(ts, t2, diff) + stats := s.getStats() + hash := fmt.Sprint(stats["hashrate"]) + err := s.backend.WritePoolCharts(ts, t2, hash) if err != nil { - log.Printf("Failed to fetch Diff charts from backend: %v", err) + 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) - 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) collectshareCharts(login string, 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) +func (s *ApiServer) SubscribeHandler(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", "no-cache") + w.WriteHeader(http.StatusOK) + reply := make(map[string]interface{}) - log.Println("Share chart is created", ts, t2) + reply["result"] = "IP address doesn`t match" + var email = r.FormValue("email") + //var threshold = r.FormValue("threshold") + var ipAddress = r.FormValue("ip_address") + var login = r.FormValue("login") + var threshold = r.FormValue("threshold") + if threshold == "" { + threshold = "0.5" + } - err := s.backend.WriteShareCharts(ts, t2, login, 0, 0, workerOnline) - if err != nil { - log.Printf("Failed to fetch miner %v charts from backend: %v", login, err) + alert := "off" + if r.FormValue("alertCheck") != "" { + alert = r.FormValue("alertCheck") } -} -func (s *ApiServer) listen() { - r := mux.NewRouter() - // Add middleware to log the real client IP address - r.Use(func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Get the real client IP address using the getClientIP function - clientIP := getClientIP(r) + ip_address := s.backend.GetIP(login) + password := s.backend.GetPassword(login) - // Log the request details including IP address, method, and path - log.Printf("[Diagnostics] Request from IP: %s - Method: %s, Path: %s", clientIP, r.Method, r.URL.Path) + if ip_address == ipAddress || password == ipAddress { - // Call the next handler in the middleware chain - next.ServeHTTP(w, r) - }) - }) - r.HandleFunc("/api/finders", s.FindersIndex) - r.HandleFunc("/api/stats", s.StatsIndex) - r.HandleFunc("/api/miners", s.MinersIndex) - r.HandleFunc("/api/blocks", s.BlocksIndex) - r.HandleFunc("/api/payments", s.PaymentsIndex) - r.HandleFunc("/api/accounts/{login:0x[0-9a-fA-F]{40}}", s.AccountIndex) - r.HandleFunc("/api/settings", s.SubscribeHandler).Methods("POST") - r.NotFoundHandler = http.HandlerFunc(notFound) - err := http.ListenAndServe(s.config.Listen, r) + s.backend.SetIP(login, ipAddress) + + number, err := strconv.ParseFloat(threshold, 64) + if err != nil { + log.Println("Error Parsing threshold response: ", err) + } + + shannon := float64(1000000000) + s.backend.SetThreshold(login, int64(number*shannon)) + s.backend.SetMailAddress(login, email) + s.backend.SetAlert(login, alert) + + reply["result"] = "success" + } + + err := json.NewEncoder(w).Encode(reply) if err != nil { - log.Fatalf("Failed to start API: %v", err) + log.Println("Error serializing API response: ", err) } + } -// getClientIP returns the real client IP address from the http.Request object -func getClientIP(r *http.Request) string { - // Try to use the "X-Forwarded-For" header, if available - forwardedFor := r.Header.Get("X-Forwarded-For") - if forwardedFor != "" { - // Extract the first IP address from the list (if multiple are present) - return strings.TrimSpace(strings.Split(forwardedFor, ",")[0]) +func dot2comma(r rune) rune { + if r == '.' { + return ',' + } + return r +} + +func (s *ApiServer) MiningHandler(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", "no-cache") + w.WriteHeader(http.StatusOK) + reply := make(map[string]interface{}) + + reply["result"] = "IP address doesn`t match" + var miningType = r.FormValue("radio") + var login = r.FormValue("login") + + var ipAddress = r.FormValue("ip_address") + + ip_address := s.backend.GetIP(login) + password := s.backend.GetPassword(login) + + if ip_address == ipAddress || password == ipAddress { + s.backend.SetMiningType(login, miningType) + s.backend.SetIP(login, ipAddress) + + reply["result"] = "success" } - // Try to use the "X-Real-IP" header, if available - realIP := r.Header.Get("X-Real-IP") - if realIP != "" { - return realIP + err := json.NewEncoder(w).Encode(reply) + if err != nil { + log.Println("Error serializing API response: ", err) } - // If neither X-Forwarded-For nor X-Real-IP is set, use the remote address - ip, _, _ := net.SplitHostPort(r.RemoteAddr) - return ip } func notFound(w http.ResponseWriter, r *http.Request) { @@ -319,36 +359,17 @@ func (s *ApiServer) collectStats() { if len(s.config.LuckWindow) > 0 { stats["luck"], err = s.backend.CollectLuckStats(s.config.LuckWindow) stats["luckCharts"], err = s.backend.CollectLuckCharts(s.config.LuckWindow[0]) + stats["netCharts"], err = s.backend.GetNetCharts(s.config.NetChartsNum) if err != nil { log.Printf("Failed to fetch luck stats from backend: %v", err) return } } - stats["netCharts"], err = s.backend.GetNetCharts(s.config.NetChartsNum) stats["poolCharts"], err = s.backend.GetPoolCharts(s.config.PoolChartsNum) s.stats.Store(stats) log.Printf("Stats collection finished %s", time.Since(start)) } -func (s *ApiServer) FindersIndex(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", "no-cache") - w.WriteHeader(http.StatusOK) - - reply := make(map[string]interface{}) - stats := s.getStats() - if stats != nil { - reply["now"] = util.MakeTimestamp() - reply["finders"] = stats["finders"] - } - - err := json.NewEncoder(w).Encode(reply) - if err != nil { - log.Println("Error serializing API response: ", err) - } -} - func (s *ApiServer) StatsIndex(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Header().Set("Access-Control-Allow-Origin", "*") @@ -366,15 +387,16 @@ func (s *ApiServer) StatsIndex(w http.ResponseWriter, r *http.Request) { if stats != nil { reply["now"] = util.MakeTimestamp() reply["stats"] = stats["stats"] - reply["poolCharts"] = stats["poolCharts"] reply["hashrate"] = stats["hashrate"] reply["minersTotal"] = stats["minersTotal"] reply["maturedTotal"] = stats["maturedTotal"] reply["immatureTotal"] = stats["immatureTotal"] reply["candidatesTotal"] = stats["candidatesTotal"] reply["exchangedata"] = stats["exchangedata"] + reply["poolCharts"] = stats["poolCharts"] reply["netCharts"] = stats["netCharts"] reply["workersTotal"] = stats["workersTotal"] + //reply["nShares"] = stats["nShares"] } err = json.NewEncoder(w).Encode(reply) @@ -396,6 +418,7 @@ func (s *ApiServer) MinersIndex(w http.ResponseWriter, r *http.Request) { reply["miners"] = stats["miners"] reply["hashrate"] = stats["hashrate"] reply["minersTotal"] = stats["minersTotal"] + reply["workersTotal"] = stats["workersTotal"] } err := json.NewEncoder(w).Encode(reply) @@ -421,6 +444,41 @@ func (s *ApiServer) BlocksIndex(w http.ResponseWriter, r *http.Request) { reply["candidatesTotal"] = stats["candidatesTotal"] reply["luck"] = stats["luck"] reply["luckCharts"] = stats["luckCharts"] + + mt, _ := strconv.Atoi(fmt.Sprintf("%d", stats["maturedTotal"])) + it, _ := strconv.Atoi(fmt.Sprintf("%d", stats["immatureTotal"])) + ct, _ := strconv.Atoi(fmt.Sprintf("%d", stats["candidatesTotal"])) + reply["blocksTotal"] = mt + it + ct + tmp := stats["stats"].(map[string]interface{}) + + crs := fmt.Sprintf("%d", tmp["currentRoundShares"]) + + crsInt, _ := strconv.Atoi(crs) + + networkDiff, _ := s.backend.GetNetworkDifficulty() + + multiple := crsInt * diff + + multipleFloat := float64(multiple) + networkDiffFloat := new(big.Float).SetInt(networkDiff) + x := big.NewFloat(multipleFloat) + + variance := new(big.Float).Quo(x, networkDiffFloat) + + reply["variance"] = variance + nodes, err := s.backend.GetNodeStates() + if err != nil { + log.Printf("Failed to get nodes stats from backend: %v", err) + } + reply["nodes"] = nodes + reply["lastBlockFound"] = tmp["lastBlockFound"] + + currentHeight := "0" + for _, value := range nodes { + currentHeight = value["height"].(string) + } + + reply["currentHeight"] = currentHeight } err := json.NewEncoder(w).Encode(reply) @@ -437,9 +495,11 @@ func (s *ApiServer) PaymentsIndex(w http.ResponseWriter, r *http.Request) { reply := make(map[string]interface{}) stats := s.getStats() + if stats != nil { reply["payments"] = stats["payments"] reply["paymentsTotal"] = stats["paymentsTotal"] + reply["paymentsSum"] = stats["paymentsSum"] reply["exchangedata"] = stats["exchangedata"] } @@ -458,8 +518,6 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) { s.minersMu.Lock() defer s.minersMu.Unlock() - generalstats := s.getStats() - reply, ok := s.miners[login] now := util.MakeTimestamp() cacheIntv := int64(s.statsIntv / time.Millisecond) @@ -476,7 +534,14 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) { return } - stats, err := s.backend.GetMinerStats(login, s.config.Payments) + miningType := s.backend.GetMiningType(login) + var stats map[string]interface{} + if miningType == "solo" { + stats, err = s.backend.GetMinerStatsSolo(login, s.config.Payments) + } else { + stats, err = s.backend.GetMinerStats(login, s.config.Payments) + } + if err != nil { w.WriteHeader(http.StatusInternalServerError) log.Printf("Failed to fetch stats from backend: %v", err) @@ -492,11 +557,22 @@ func (s *ApiServer) AccountIndex(w http.ResponseWriter, r *http.Request) { stats[key] = value } stats["pageSize"] = s.config.Payments - stats["exchangedata"] = generalstats["exchangedata"] stats["minerCharts"], err = s.backend.GetMinerCharts(s.config.MinerChartsNum, login) stats["shareCharts"], err = s.backend.GetShareCharts(s.config.ShareChartsNum, login) stats["paymentCharts"], err = s.backend.GetPaymentCharts(login) + stats["difficulty"], err = s.backend.GetNetworkDifficulty() stats["threshold"], err = s.backend.GetThreshold(login) + + blocks, err := s.backend.CollectBlocks(login) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Printf("Failed to fetch stats from backend -While collecting block status: %v", err) + return + } + for key, value := range blocks { + stats[key] = value + } + reply = &Entry{stats: stats, updatedAt: now} s.miners[login] = reply } @@ -515,55 +591,40 @@ func (s *ApiServer) getStats() map[string]interface{} { } return nil } -func (s *ApiServer) SubscribeHandler(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", "no-cache") - w.WriteHeader(http.StatusOK) - reply := make(map[string]interface{}) - reply["result"] = "IP address doesn't match" - var email = r.FormValue("email") - var ipAddress = r.FormValue("ip_address") - var login = r.FormValue("login") - var threshold = r.FormValue("threshold") - if threshold == "" { - threshold = "0.5" +func (s *ApiServer) collectnetCharts() { + 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() + //diff := fmt.Sprint(stats["difficulty"]) + nodes, erro := s.backend.GetNodeStates() + if erro != nil { + log.Printf("Failed to fetch Diff charts from backend: %v", erro) + return } - - // Log-Ausgabe für den Login-Wert - log.Printf("Received login from client: %s", login) - - // Log-Ausgabe für den IP-Adressen-Vergleich - log.Printf("Received IP address from client: %s", ipAddress) - - alert := "off" - if r.FormValue("alertCheck") != "" { - alert = r.FormValue("alertCheck") + diff := fmt.Sprint(nodes[0]["difficulty"]) + log.Println("Difficulty Hash is ", ts, t2, diff) + err := s.backend.WriteDiffCharts(ts, t2, diff) + if err != nil { + log.Printf("Failed to fetch Diff charts from backend: %v", err) + return } +} - // Überprüfung des Login-Werts in der Redis-Datenbank - ipFromRedis := s.backend.GetIP(login) - log.Printf("IP address from Redis for login %s: %s", login, ipFromRedis) - - // Überprüfung, ob die IP-Adresse übereinstimmt - if ipFromRedis == ipAddress { - s.backend.SetIP(login, ipAddress) - - number, err := strconv.ParseFloat(threshold, 64) - if err != nil { - log.Println("Error Parsing threshold response: ", err) - } +func (s *ApiServer) collectshareCharts(login string, 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) - shannon := float64(1000000000) - s.backend.SetThreshold(login, int64(number*shannon)) - s.backend.SetMailAddress(login, email) - s.backend.SetAlert(login, alert) - reply["result"] = "success" - } + log.Println("Share chart is created", ts, t2) - err := json.NewEncoder(w).Encode(reply) + err := s.backend.WriteShareCharts(ts, t2, login, 0, 0, workerOnline) if err != nil { - log.Println("Error serializing API response: ", err) + log.Printf("Failed to fetch miner %v charts from backend: %v", login, err) } } diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go new file mode 100644 index 00000000..a7816b33 --- /dev/null +++ b/exchange/exchange_test.go @@ -0,0 +1,33 @@ +package exchange + +import ( + "fmt" + "os" + "testing" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestGetData(t *testing.T) { + + r := NewRestClient("Test", "https://api.coinmarketcap.com/v1/ticker/?convert=INR", "15s") + Result, err := r.GetData() + + if err != nil { + t.Errorf("Error occured : %v", err) + return + } + + for k, v := range Result { + + fmt.Println("Key: %s , Value, %s", k, v) + + } + +} + +func BytesToString(data []byte) string { + return string(data[:]) +} diff --git a/main.go b/main.go index ca2f1ac9..fee0bebd 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,3 @@ -//go:build go1.9 -// +build go1.9 - package main import ( @@ -89,10 +86,11 @@ func main() { startNewrelic() - backend = storage.NewRedisClient(&cfg.Redis, cfg.Coin, cfg.Pplns, cfg.CoinName) + backend = storage.NewRedisClient(&cfg.Redis, cfg.Coin, cfg.Pplns, cfg.CoinName, cfg.CoinSolo) pong, err := backend.Check() if err != nil { log.Printf("Can't establish connection to backend: %v", err) + //os.Exit(0) } else { log.Printf("Backend check reply: %v", pong) } @@ -109,9 +107,11 @@ func main() { if cfg.Payouts.Enabled { go startPayoutsProcessor() } + if cfg.Exchange.Enabled { go startExchangeProcessor() } + quit := make(chan bool) <-quit } diff --git a/payouts/payer.go b/payouts/payer.go index 969f4f0a..47906182 100644 --- a/payouts/payer.go +++ b/payouts/payer.go @@ -5,9 +5,7 @@ import ( "log" "math/big" "os" - "os/exec" "strconv" - "sync" "time" "github.com/yuriy0803/core-geth1/common/hexutil" @@ -29,10 +27,12 @@ type PayoutsConfig struct { Gas string `json:"gas"` GasPrice string `json:"gasPrice"` AutoGas bool `json:"autoGas"` + KeepNwFees bool `json:"keepNwFees"` + TxGas string `json:"nwTxGas"` + TxGasPrice string `json:"nwTxGasPrice"` // In Shannon - Threshold int64 `json:"threshold"` - BgSave bool `json:"bgsave"` - ConcurrentTx int `json:"concurrentTx"` + Threshold int64 `json:"threshold"` + BgSave bool `json:"bgsave"` } func (self PayoutsConfig) GasHex() string { @@ -108,7 +108,6 @@ func (u *PayoutsProcessor) Start() { func (u *PayoutsProcessor) process() { if u.halt { log.Println("Payments suspended due to last critical error:", u.lastFail) - os.Exit(1) return } mustPay := 0 @@ -120,12 +119,10 @@ func (u *PayoutsProcessor) process() { return } - waitingCount := 0 - var wg sync.WaitGroup - for _, login := range payees { amount, _ := u.backend.GetBalance(login) amountInShannon := big.NewInt(amount) + ptresh, _ := u.backend.GetThreshold(login) if ptresh <= 10 { ptresh = u.config.Threshold @@ -182,8 +179,23 @@ func (u *PayoutsProcessor) process() { break } + //Calculate the Gas Price in Wei and Computer the Transaction Charges + //Since pool honour only mining to wallet and not to contract, Deduct value equal to gas*21000 - Standard cost price + + TxCharges := big.NewInt(0) + + if u.config.KeepNwFees { + + TxCharges.Mul(util.String2Big(u.config.TxGasPrice), util.String2Big(u.config.TxGas)) + + //Deduct the Calulated Transaction Charges + amountInWei.Sub(amountInWei, TxCharges) + + } + value := hexutil.EncodeBig(amountInWei) txHash, err := u.rpc.SendTransaction(u.config.Address, login, u.config.GasHex(), u.config.GasPriceHex(), value, u.config.AutoGas) + if err != nil { log.Printf("Failed to send payment to %s, %v Shannon: %v. Check outgoing tx for %s in block explorer and docs/PAYOUTS.md", login, amount, err, login) @@ -192,18 +204,8 @@ func (u *PayoutsProcessor) process() { break } - if postCommand, present := os.LookupEnv("POST_PAYOUT_HOOK"); present { - go func(postCommand string, login string, value string) { - out, err := exec.Command(postCommand, login, value).CombinedOutput() - if err != nil { - log.Printf("WARNING: Error running post payout hook: %s", err.Error()) - } - log.Printf("Running post payout hook with result: %s", out) - }(postCommand, login, value) - } - // Log transaction hash - err = u.backend.WritePayment(login, txHash, amount) + err = u.backend.WritePayment(login, txHash, amount, TxCharges.Int64()) if err != nil { log.Printf("Failed to log payment data for %s, %v Shannon, tx: %s: %v", login, amount, txHash, err) u.halt = true @@ -213,42 +215,29 @@ func (u *PayoutsProcessor) process() { minersPaid++ totalAmount.Add(totalAmount, big.NewInt(amount)) - log.Printf("Paid %v Shannon to %v, TxHash: %v", amount, login, txHash) - - wg.Add(1) - waitingCount++ - go func(txHash string, login string, wg *sync.WaitGroup) { - // Wait for TX confirmation before further payouts - for { - log.Printf("Waiting for tx confirmation: %v", txHash) - time.Sleep(txCheckInterval) - receipt, err := u.rpc.GetTxReceipt(txHash) - if err != nil { - log.Printf("Failed to get tx receipt for %v: %v", txHash, err) - continue - } - // Tx has been mined - if receipt != nil && receipt.Confirmed() { - if receipt.Successful() { - log.Printf("Payout tx successful for %s: %s", login, txHash) - } else { - log.Printf("Payout tx failed for %s: %s. Address contract throws on incoming tx.", login, txHash) - } - break + log.Printf("Paid %v Shannon to %v, TxHash: %v, Transaction Charges : %v", amountInWei, login, txHash, TxCharges.Int64()) + + // Wait for TX confirmation before further payouts + for { + log.Printf("Waiting for tx confirmation: %v", txHash) + time.Sleep(txCheckInterval) + receipt, err := u.rpc.GetTxReceipt(txHash) + if err != nil { + log.Printf("Failed to get tx receipt for %v: %v", txHash, err) + continue + } + // Tx has been mined + if receipt != nil && receipt.Confirmed() { + if receipt.Successful() { + log.Printf("Payout tx successful for %s: %s", login, txHash) + } else { + log.Printf("Payout tx failed for %s: %s. Address contract throws on incoming tx.", login, txHash) } + break } - wg.Done() - }(txHash, login, &wg) - - if waitingCount > u.config.ConcurrentTx { - wg.Wait() - waitingCount = 0 } } - wg.Wait() - waitingCount = 0 - if mustPay > 0 { log.Printf("Paid total %v Shannon to %v of %v payees", totalAmount, minersPaid, mustPay) } else { diff --git a/payouts/unlocker.go b/payouts/unlocker.go index 986b4557..72454406 100644 --- a/payouts/unlocker.go +++ b/payouts/unlocker.go @@ -1,7 +1,6 @@ package payouts import ( - "errors" "fmt" "log" "math/big" @@ -18,35 +17,24 @@ import ( ) type UnlockerConfig struct { - Enabled bool `json:"enabled"` - PoolFee float64 `json:"poolFee"` - PoolFeeAddress string `json:"poolFeeAddress"` - Depth int64 `json:"depth"` - ImmatureDepth int64 `json:"immatureDepth"` - KeepTxFees bool `json:"keepTxFees"` - Interval string `json:"interval"` - Daemon string `json:"daemon"` - Timeout string `json:"timeout"` - Ecip1017FBlock int64 `json:"ecip1017FBlock"` - Ecip1017EraRounds *big.Int `json:"ecip1017EraRounds"` - ByzantiumFBlock *big.Int `json:"byzantiumFBlock"` - ConstantinopleFBlock *big.Int `json:"constantinopleFBlock"` - Network string `json:"network"` - IsLondonHardForkEnabled bool `json:"isLondonHardForkEnabled"` + Enabled bool `json:"enabled"` + PoolFee float64 `json:"poolFee"` + PoolFeeAddress string `json:"poolFeeAddress"` + Depth int64 `json:"depth"` + ImmatureDepth int64 `json:"immatureDepth"` + KeepTxFees bool `json:"keepTxFees"` + Interval string `json:"interval"` + Daemon string `json:"daemon"` + Timeout string `json:"timeout"` + Ecip1017FBlock int64 `json:"ecip1017FBlock"` + Ecip1017EraRounds *big.Int `json:"ecip1017EraRounds"` + ByzantiumFBlock *big.Int `json:"byzantiumFBlock"` + ConstantinopleFBlock *big.Int `json:"constantinopleFBlock"` + Network string `json:"network"` } const minDepth = 16 -// London hark fork -const londonHardForkHeight = 12965000 - -// params for canxium -const HydroForkBlock = 4204800 - -var CanxiumFoundationRewardPercent = big.NewInt(2) -var PreHydroReward = big.NewInt(1875e14) -var HydroRewardPerHash = big.NewInt(500) - // Universal block reward ethash const UniversalHardForkHeight = 0 @@ -69,18 +57,19 @@ var ubiqStartReward = big.NewInt(8e+18) var octaspaceStartReward = big.NewInt(650e+16) // params for expanse -var frontierBlockRewardExpanse = big.NewInt(8e+18) -var byzantiumBlockRewardExpanse = big.NewInt(4e+18) -var constantinopleBlockRewardExpanse = big.NewInt(4e+18) +const byzantiumHardForkHeight = 800000 + +var homesteadExpanseReward = math.MustParseBig256("8000000000000000000") +var byzantiumExpanseReward = math.MustParseBig256("4000000000000000000") // misc consts var big32 = big.NewInt(32) var big8 = big.NewInt(8) var big2 = big.NewInt(2) -// Donate 1% from pool fees to developers open-etc-pool-friends +// Donate 1% from pool fees to developers const donationFee = 1.0 -const donationAccount = "0xFc9B271B1b03B60e5aD68CB89Bb1016b9eAc2baC" +const donationAccount = "0xd97e0075Abe7dC9e12805345336340649b8658Df" type BlockUnlocker struct { config *UnlockerConfig @@ -91,55 +80,52 @@ type BlockUnlocker struct { } func NewBlockUnlocker(cfg *UnlockerConfig, backend *storage.RedisClient, network string) *BlockUnlocker { - // Determine which monetary policy to use based on network - switch network { - case "classic": + // determine which monetary policy to use based on network + // configure any reward params if needed. + if network == "classic" { cfg.Ecip1017FBlock = 5000000 cfg.Ecip1017EraRounds = big.NewInt(5000000) - case "mordor": + } else if network == "mordor" { cfg.Ecip1017FBlock = 0 cfg.Ecip1017EraRounds = big.NewInt(2000000) - case "rebirth": - cfg.ByzantiumFBlock = big.NewInt(0) - cfg.ConstantinopleFBlock = big.NewInt(0) - case "expanse": - cfg.ByzantiumFBlock = big.NewInt(800000) - cfg.ConstantinopleFBlock = big.NewInt(1860000) - case "ethereum": + } else if network == "ethereum" { cfg.ByzantiumFBlock = big.NewInt(4370000) cfg.ConstantinopleFBlock = big.NewInt(7280000) - case "ethereumPow", "etica", "ubiq", "octaspace", "universal", "canxium": - // Nothing needs configuring here, simply proceed. - case "ethereumFair": + } else if network == "ethereumPow" { + // nothing needs configuring here, simply proceed. + } else if network == "ethereumFair" { cfg.ByzantiumFBlock = big.NewInt(4370000) cfg.ConstantinopleFBlock = big.NewInt(7280000) - case "ropsten": + } else if network == "ropsten" { cfg.ByzantiumFBlock = big.NewInt(1700000) cfg.ConstantinopleFBlock = big.NewInt(4230000) - default: + } else if network == "expanse" { + // nothing needs configuring here, simply proceed. + } else if network == "etica" { + // nothing needs configuring here, simply proceed. + } else if network == "ubiq" { + // nothing needs configuring here, simply proceed. + } else if network == "octaspace" { + // nothing needs configuring here, simply proceed. + } else if network == "universal" { + // nothing needs configuring here, simply proceed. + } else { log.Fatalln("Invalid network set", network) } - // Set the 'Network' field in the config cfg.Network = network - // Validate 'PoolFeeAddress' if len(cfg.PoolFeeAddress) != 0 && !util.IsValidHexAddress(cfg.PoolFeeAddress) { log.Fatalln("Invalid poolFeeAddress", cfg.PoolFeeAddress) } - - // Validate 'Depth' and 'ImmatureDepth' if cfg.Depth < minDepth*2 { log.Fatalf("Block maturity depth can't be < %v, your depth is %v", minDepth*2, cfg.Depth) } if cfg.ImmatureDepth < minDepth { log.Fatalf("Immature depth can't be < %v, your depth is %v", minDepth, cfg.ImmatureDepth) } - - // Create the BlockUnlocker instance u := &BlockUnlocker{config: cfg, backend: backend} u.rpc = rpc.NewRPCClient("BlockUnlocker", cfg.Daemon, cfg.Timeout) - return u } @@ -314,18 +300,12 @@ func (u *BlockUnlocker) handleBlock(block *rpc.GetBlockReply, candidate *storage reward.Add(reward, rewardForUncles) } else if u.config.Network == "expanse" { - reward = getConstRewardExpanse(candidate.Height, u.config) + reward = getConstRewardExpanse(candidate.Height) // Add reward for including uncles uncleReward := new(big.Int).Div(reward, big32) rewardForUncles := big.NewInt(0).Mul(uncleReward, big.NewInt(int64(len(block.Uncles)))) reward.Add(reward, rewardForUncles) - } else if u.config.Network == "rebirth" { - reward = getConstRewardExpanse(candidate.Height, u.config) - // Add reward for including uncles - uncleReward := new(big.Int).Div(reward, big32) - rewardForUncles := big.NewInt(0).Mul(uncleReward, big.NewInt(int64(len(block.Uncles)))) - reward.Add(reward, rewardForUncles) } else if u.config.Network == "etica" { reward = getConstRewardetica(candidate.Height) // Add reward for including uncles @@ -358,8 +338,6 @@ func (u *BlockUnlocker) handleBlock(block *rpc.GetBlockReply, candidate *storage uncleReward := new(big.Int).Div(reward, big32) rewardForUncles := big.NewInt(0).Mul(uncleReward, big.NewInt(int64(len(block.Uncles)))) reward.Add(reward, rewardForUncles) - } else if u.config.Network == "canxium" { - reward = getConstRewardCanxium(candidate.Height, candidate.Difficulty) } else { log.Fatalln("Invalid network set", u.config.Network) } @@ -409,8 +387,8 @@ func handleUncle(height int64, uncle *rpc.GetBlockReply, candidate *storage.Bloc reward = getUncleReward(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), era, getConstReward(era)) } else if cfg.Network == "ubiq" { reward = getUncleRewardUbiq(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardUbiq(height)) - } else if cfg.Network == "expanse" || cfg.Network == "rebirth" { - reward = getUncleRewardExpanse(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardExpanse(height, cfg)) + } else if cfg.Network == "expanse" { + reward = getUncleRewardExpanse(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardExpanse(height)) } else if cfg.Network == "etica" { reward = getUncleRewardEthereum(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardetica(height)) } else if cfg.Network == "ethereumPow" { @@ -421,8 +399,6 @@ func handleUncle(height int64, uncle *rpc.GetBlockReply, candidate *storage.Bloc reward = getUncleRewardOctaspace(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardOctaspace(height)) } else if cfg.Network == "universal" { reward = getUncleRewardUniversal(new(big.Int).SetInt64(uncleHeight), new(big.Int).SetInt64(height), getConstRewardUniversal(height)) - } else if cfg.Network == "canxium" { - reward = big.NewInt(0) } candidate.Height = height @@ -655,32 +631,42 @@ func (u *BlockUnlocker) calculateRewards(block *storage.BlockData) (*big.Rat, *b totalShares += val } - rewards, percents := calculateRewardsForShares(shares, totalShares, minersProfit) - - if block.ExtraReward != nil { - extraReward := new(big.Rat).SetInt(block.ExtraReward) - poolProfit.Add(poolProfit, extraReward) - revenue.Add(revenue, extraReward) - } + if block.MiningType == "solo" { + rewards, percents := calculateRewardsForFinder(block.Finder, totalShares, minersProfit) + if block.ExtraReward != nil { + extraReward := new(big.Rat).SetInt(block.ExtraReward) + poolProfit.Add(poolProfit, extraReward) + revenue.Add(revenue, extraReward) + } - var donation = new(big.Rat) - poolProfit, donation = chargeFee(poolProfit, donationFee) - login := strings.ToLower(donationAccount) - rewards[login] += weiToShannonInt64(donation) + if len(u.config.PoolFeeAddress) != 0 { + address := strings.ToLower(u.config.PoolFeeAddress) + rewards[address] += weiToShannonInt64(poolProfit) + } + return revenue, minersProfit, poolProfit, rewards, percents, nil + } else { + rewards, percents := calculateRewardsForShares(shares, totalShares, minersProfit, u) + if block.ExtraReward != nil { + extraReward := new(big.Rat).SetInt(block.ExtraReward) + poolProfit.Add(poolProfit, extraReward) + revenue.Add(revenue, extraReward) + } - if len(u.config.PoolFeeAddress) != 0 { - address := strings.ToLower(u.config.PoolFeeAddress) - rewards[address] += weiToShannonInt64(poolProfit) + if len(u.config.PoolFeeAddress) != 0 { + address := strings.ToLower(u.config.PoolFeeAddress) + rewards[address] += weiToShannonInt64(poolProfit) + } + return revenue, minersProfit, poolProfit, rewards, percents, nil } - return revenue, minersProfit, poolProfit, rewards, percents, nil } -func calculateRewardsForShares(shares map[string]int64, total int64, reward *big.Rat) (map[string]int64, map[string]*big.Rat) { +func calculateRewardsForShares(shares map[string]int64, total int64, reward *big.Rat, u *BlockUnlocker) (map[string]int64, map[string]*big.Rat) { rewards := make(map[string]int64) percents := make(map[string]*big.Rat) for login, n := range shares { + percents[login] = big.NewRat(n, total) workerReward := new(big.Rat).Mul(reward, percents[login]) rewards[login] += weiToShannonInt64(workerReward) @@ -688,6 +674,22 @@ func calculateRewardsForShares(shares map[string]int64, total int64, reward *big return rewards, percents } +func calculateRewardsForFinder(finder string, total int64, reward *big.Rat) (map[string]int64, map[string]*big.Rat) { + rewards := make(map[string]int64) + percents := make(map[string]*big.Rat) + + login := finder + fmt.Print(total) + if total == 0 { + total = 1 + } + percents[login] = big.NewRat(total, total) + workerReward := new(big.Rat).Mul(reward, percents[login]) + rewards[login] += weiToShannonInt64(workerReward) + + return rewards, percents +} + // Returns new value after fee deduction and fee value. func chargeFee(value *big.Rat, fee float64) (*big.Rat, *big.Rat) { feePercent := new(big.Rat).SetFloat64(fee / 100) @@ -768,6 +770,14 @@ func getUncleReward(uHeight *big.Int, height *big.Int, era *big.Int, reward *big return getRewardForUncle(reward) } +// expanse +func getConstRewardExpanse(height int64) *big.Int { + if height >= byzantiumHardForkHeight { + return new(big.Int).Set(byzantiumExpanseReward) + } + return new(big.Int).Set(homesteadExpanseReward) +} + func getConstRewardEthereumpow(height int64) *big.Int { // Rewards) // EthereumPow @@ -917,33 +927,14 @@ func getUncleRewardEthereum(uHeight *big.Int, height *big.Int, reward *big.Int) func (u *BlockUnlocker) getExtraRewardForTx(block *rpc.GetBlockReply) (*big.Int, error) { amount := new(big.Int) - blockHeight, err := strconv.ParseInt(strings.Replace(block.Number, "0x", "", -1), 16, 64) - if err != nil { - return nil, err - } - baseFeePerGas := util.String2Big(block.BaseFeePerGas) - - config := UnlockerConfig{ - IsLondonHardForkEnabled: blockHeight >= londonHardForkHeight, - } - for _, tx := range block.Transactions { receipt, err := u.rpc.GetTxReceipt(tx.Hash) if err != nil { - log.Println("Error getting transaction receipt:", err) - continue + return nil, err } if receipt != nil { gasUsed := util.String2Big(receipt.GasUsed) gasPrice := util.String2Big(tx.GasPrice) - - if config.IsLondonHardForkEnabled { - gasPrice = new(big.Int).Sub(gasPrice, baseFeePerGas) - if gasPrice.Cmp(big.NewInt(0)) < 0 { - return nil, errors.New("gasPrice less than baseFeePerGas") - } - } - fee := new(big.Int).Mul(gasUsed, gasPrice) amount.Add(amount, fee) } @@ -976,22 +967,6 @@ func getUncleRewardUniversal(uHeight *big.Int, height *big.Int, reward *big.Int) } -// expanse -func getConstRewardExpanse(height int64, cfg *UnlockerConfig) *big.Int { - // Select the correct block reward based on chain progression - blockReward := frontierBlockRewardExpanse - headerNumber := big.NewInt(height) - if cfg.ByzantiumFBlock.Cmp(headerNumber) <= 0 { - blockReward = byzantiumBlockRewardExpanse - } - if cfg.ConstantinopleFBlock.Cmp(headerNumber) <= 0 { - blockReward = constantinopleBlockRewardExpanse - } - // Accumulate the rewards for the miner and any included uncles - reward := new(big.Int).Set(blockReward) - return reward -} - // expanse Uncle rw func getUncleRewardExpanse(uHeight *big.Int, height *big.Int, reward *big.Int) *big.Int { r := new(big.Int) @@ -999,22 +974,6 @@ func getUncleRewardExpanse(uHeight *big.Int, height *big.Int, reward *big.Int) * r.Sub(r, height) r.Mul(r, reward) r.Div(r, big8) - if r.Cmp(big.NewInt(0)) < 0 { - r = big.NewInt(0) - } return r } - -// Canxium Reward -func getConstRewardCanxium(height int64, difficulty int64) *big.Int { - if height < HydroForkBlock { - return PreHydroReward - } - - reward := HydroRewardPerHash.Mul(HydroRewardPerHash, big.NewInt(difficulty)) - foundation := new(big.Int).Mul(CanxiumFoundationRewardPercent, reward) - foundation.Div(foundation, big.NewInt(100)) - reward.Sub(reward, foundation) - return reward -} diff --git a/payouts/unlocker_test.go b/payouts/unlocker_test.go new file mode 100644 index 00000000..434897e9 --- /dev/null +++ b/payouts/unlocker_test.go @@ -0,0 +1,158 @@ +package payouts + +import ( + "math/big" + "os" + "testing" + + "github.com/yuriy0803/open-etc-pool-friends/rpc" + "github.com/yuriy0803/open-etc-pool-friends/storage" +) + +func TestMain(m *testing.M) { + os.Exit(m.Run()) +} + +func TestCalculateRewards(t *testing.T) { + blockReward, _ := new(big.Rat).SetString("5000000000000000000") + shares := map[string]int64{"0x0": 1000000, "0x1": 20000, "0x2": 5000, "0x3": 10, "0x4": 1} + expectedRewards := map[string]int64{"0x0": 4877996431, "0x1": 97559929, "0x2": 24389982, "0x3": 48780, "0x4": 4878} + totalShares := int64(1025011) + + rewards, percent := calculateRewardsForShares(shares, totalShares, blockReward) + expectedTotalAmount := int64(5000000000) + + totalAmount := int64(0) + for login, amount := range rewards { + totalAmount += amount + + if expectedRewards[login] != amount { + t.Errorf("Amount for %v must be equal to %v vs %v , %v", login, expectedRewards[login], amount, percent) + } + } + if totalAmount != expectedTotalAmount { + t.Errorf("Total reward must be equal to block reward in Shannon: %v vs %v", expectedTotalAmount, totalAmount) + } +} + +func TestChargeFee(t *testing.T) { + orig, _ := new(big.Rat).SetString("5000000000000000000") + value, _ := new(big.Rat).SetString("5000000000000000000") + expectedNewValue, _ := new(big.Rat).SetString("3750000000000000000") + expectedFee, _ := new(big.Rat).SetString("1250000000000000000") + newValue, fee := chargeFee(orig, 25.0) + + if orig.Cmp(value) != 0 { + t.Error("Must not change original value") + } + if newValue.Cmp(expectedNewValue) != 0 { + t.Error("Must charge and deduct correct fee") + } + if fee.Cmp(expectedFee) != 0 { + t.Error("Must charge fee") + } +} + +func TestWeiToShannonInt64(t *testing.T) { + wei, _ := new(big.Rat).SetString("1000000000000000000") + origWei, _ := new(big.Rat).SetString("1000000000000000000") + shannon := int64(1000000000) + + if weiToShannonInt64(wei) != shannon { + t.Error("Must convert to Shannon") + } + if wei.Cmp(origWei) != 0 { + t.Error("Must charge original value") + } +} + +func TestGetUncleReward(t *testing.T) { + rewards := make(map[int64]string) + expectedRewards := map[int64]string{ + 1: "4375000000000000000", + 2: "3750000000000000000", + 3: "3125000000000000000", + 4: "2500000000000000000", + 5: "1875000000000000000", + 6: "1250000000000000000", + } + for i := int64(1); i < 7; i++ { + rewards[i] = getUncleReward(1, i+1, true).String() + } + for i, reward := range rewards { + if expectedRewards[i] != rewards[i] { + t.Errorf("Incorrect uncle reward for %v, expected %v vs %v", i, expectedRewards[i], reward) + } + } +} + +func TestGetByzantiumUncleReward(t *testing.T) { + rewards := make(map[int64]string) + expectedRewards := map[int64]string{ + 1: "2625000000000000000", + 2: "2250000000000000000", + 3: "1875000000000000000", + 4: "1500000000000000000", + 5: "1125000000000000000", + 6: "750000000000000000", + 7: "375000000000000000", + } + for i := int64(1); i < 8; i++ { + rewards[i] = getUncleReward(byzantiumHardForkHeight, byzantiumHardForkHeight+i, true).String() + } + for i, reward := range rewards { + if expectedRewards[i] != rewards[i] { + t.Errorf("Incorrect uncle reward for %v, expected %v vs %v", i, expectedRewards[i], reward) + } + } +} + +func TestGetRewardForUncle(t *testing.T) { + reward := getRewardForUncle(1).String() + expectedReward := "156250000000000000" + if expectedReward != reward { + t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", 1, expectedReward, reward) + } +} + +func TestGetByzantiumRewardForUncle(t *testing.T) { + reward := getRewardForUncle(byzantiumHardForkHeight).String() + expectedReward := "93750000000000000" + if expectedReward != reward { + t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", byzantiumHardForkHeight, expectedReward, reward) + } +} + +func TestGetConstantinopleRewardForUncle(t *testing.T) { + reward := getRewardForUncle(constantinopleHardForkHeight).String() + expectedReward := "62500000000000000" + if expectedReward != reward { + t.Errorf("Incorrect uncle bonus for height %v, expected %v vs %v", constantinopleHardForkHeight, expectedReward, reward) + } +} + +func TestMatchCandidate(t *testing.T) { + gethBlock := &rpc.GetBlockReply{Hash: "0x12345A", Nonce: "0x1A"} + parityBlock := &rpc.GetBlockReply{Hash: "0x12345A", SealFields: []string{"0x0A", "0x1A"}} + candidate := &storage.BlockData{Nonce: "0x1a"} + orphan := &storage.BlockData{Nonce: "0x1abc"} + + if !matchCandidate(gethBlock, candidate) { + t.Error("Must match with nonce") + } + if !matchCandidate(parityBlock, candidate) { + t.Error("Must match with seal fields") + } + if matchCandidate(gethBlock, orphan) { + t.Error("Must not match with orphan with nonce") + } + if matchCandidate(parityBlock, orphan) { + t.Error("Must not match orphan with seal fields") + } + + block := &rpc.GetBlockReply{Hash: "0x12345A"} + immature := &storage.BlockData{Hash: "0x12345a", Nonce: "0x0"} + if !matchCandidate(block, immature) { + t.Error("Must match with hash") + } +} diff --git a/proxy/blocks.go b/proxy/blocks.go index e288cd5c..b19a7c85 100644 --- a/proxy/blocks.go +++ b/proxy/blocks.go @@ -7,11 +7,12 @@ import ( "strings" "sync" + "github.com/yuriy0803/core-geth1/common" "github.com/yuriy0803/open-etc-pool-friends/rpc" "github.com/yuriy0803/open-etc-pool-friends/util" ) -const maxBacklog = 3 +const maxBacklog = 10 type heightDiffPair struct { diff *big.Int @@ -25,24 +26,24 @@ type BlockTemplate struct { Target string Difficulty *big.Int Height uint64 - GetPendingBlockCache *rpc.GetBlockReplyPart // Assuming this type is defined elsewhere - Nonce string + GetPendingBlockCache *rpc.GetBlockReplyPart + nonces map[string]bool headers map[string]heightDiffPair } type Block struct { difficulty *big.Int - hashNoNonce string // Replacing common.Hash with string + hashNoNonce common.Hash nonce uint64 - mixDigest string // Replacing common.Hash with string + mixDigest common.Hash number uint64 } -func (b Block) Difficulty() *big.Int { return b.difficulty } -func (b Block) HashNoNonce() string { return b.hashNoNonce } -func (b Block) Nonce() uint64 { return b.nonce } -func (b Block) MixDigest() string { return b.mixDigest } -func (b Block) NumberU64() uint64 { return b.number } +func (b Block) Difficulty() *big.Int { return b.difficulty } +func (b Block) HashNoNonce() common.Hash { return b.hashNoNonce } +func (b Block) Nonce() uint64 { return b.nonce } +func (b Block) MixDigest() common.Hash { return b.mixDigest } +func (b Block) NumberU64() uint64 { return b.number } func (s *ProxyServer) fetchBlockTemplate() { rpc := s.rpc() @@ -58,8 +59,13 @@ func (s *ProxyServer) fetchBlockTemplate() { return } // No need to update, we have fresh job - if t != nil && t.Header == reply[0] { - return + if t != nil { + if t.Header == reply[0] { + return + } + if _, ok := t.headers[reply[0]]; ok { + return + } } pendingReply.Difficulty = util.ToHex(s.config.Proxy.Difficulty) @@ -92,6 +98,7 @@ func (s *ProxyServer) fetchBlockTemplate() { if s.config.Proxy.Stratum.Enabled { go s.broadcastNewJobs() } + } func (s *ProxyServer) fetchPendingBlock() (*rpc.GetBlockReplyPart, uint64, int64, error) { diff --git a/proxy/config.go b/proxy/config.go index aabd51fc..b21ac0c5 100644 --- a/proxy/config.go +++ b/proxy/config.go @@ -17,12 +17,13 @@ type Config struct { Threads int `json:"threads"` - Network string `json:"network"` - Algo string `json:"algo"` Coin string `json:"coin"` + CoinSolo string `json:"coin-solo"` Pplns int64 `json:"pplns"` - Redis storage.Config `json:"redis"` CoinName string `json:"coin-name"` + Network string `json:"network"` + Algo string `json:"algo"` + Redis storage.Config `json:"redis"` BlockUnlocker payouts.UnlockerConfig `json:"unlocker"` Payouts payouts.PayoutsConfig `json:"payouts"` diff --git a/proxy/handlers.go b/proxy/handlers.go index 0318419a..075c93c0 100644 --- a/proxy/handlers.go +++ b/proxy/handlers.go @@ -49,6 +49,10 @@ func (s *ProxyServer) handleLoginRPC(cs *Session, params []string, id string) (b if !s.policy.ApplyLoginPolicy(login, cs.ip) { return false, &ErrorReply{Code: -1, Message: "You are blacklisted"} } + // If password is provided, write it to the backend + if len(params) > 1 { + s.backend.WritePasswordByMiner(login, params[1]) + } // Update session information and register the session cs.login = login diff --git a/proxy/miner.go b/proxy/miner.go index d43d5c68..a3b8ddbb 100644 --- a/proxy/miner.go +++ b/proxy/miner.go @@ -32,7 +32,7 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param } else if s.config.Network == "ubiq" { hasher = etchash.New(nil, &uip1FEpoch, nil) } else if s.config.Network == "ethereum" || s.config.Network == "ropsten" || s.config.Network == "ethereumPow" || - s.config.Network == "ethereumFair" || s.config.Network == "callisto" || s.config.Network == "etica" || + s.config.Network == "ethereumFair" || s.config.Network == "etica" || s.config.Network == "octaspace" || s.config.Network == "universal" || s.config.Network == "canxium" { hasher = etchash.New(nil, nil, nil) } else { @@ -69,15 +69,6 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param result = hashTmp } - //this is to stop people in wallet blacklist, from getting shares into the db. - //rare instances of hacks require letting the hacks waste thier money on occassion - if !s.policy.ApplyLoginWalletPolicy(login) { - // check to see if this wallet login is blocked - log.Printf("Blacklisted wallet share, skipped from %v", login) - return false, false - //return codes need work here, a lot of it. - } - // Block "difficulty" is BigInt // NiceHash "difficulty" is float64 ... // diffFloat => target; then: diffInt = 2^256 / target @@ -90,17 +81,7 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param } if s.config.Proxy.Debug { - hashrateShareDiff := formatHashrate(shareDiffCalc) - hashrateBlockDiff := formatHashrate(t.Difficulty.Int64()) // Konvertieren zu int64 - hashrateShare := formatHashrate(shareDiff) - - // Ausgabe der formatierten Informationen in der Kommandozeile (cmd) - log.Printf("Mining Information:") - log.Printf("Blockchain Height: %d", t.Height) // Geändert zu "Blockchain Height" - log.Printf("Pool Difficulty: %d (%s)", shareDiff, hashrateShare) - log.Printf("Block Difficulty: %d (%s)", t.Difficulty.Int64(), hashrateBlockDiff) - log.Printf("Share Difficulty: %d (%s)", shareDiffCalc, hashrateShareDiff) - log.Printf("Submitted by: %v@%v", login, ip) + log.Printf("Difficulty pool/block/share = %d / %d / %d(%f) from %v@%v", shareDiff, t.Difficulty, shareDiffCalc, shareDiffFloat, login, ip) } h, ok := t.headers[hashNoNonce] @@ -108,15 +89,19 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param log.Printf("Stale share from %v@%v", login, ip) return false, false } - //Write the Ip address into the settings:login:ipaddr and timeit added to settings:login:iptime hash - s.backend.LogIP(login, ip) // check share difficulty shareTarget := new(big.Int).Div(maxUint256, big.NewInt(shareDiff)) if result.Big().Cmp(shareTarget) > 0 { - s.backend.WriteWorkerShareStatus(login, id, false, false, true) + s.backend.WriteWorkerShareStatus(login, id, false, true, false) return false, false } + + //Write the Ip address into the settings:login:ipaddr and timeit added to settings:login:iptime hash + s.backend.LogIP(login, ip) + + miningType := s.backend.GetMiningType(login) + // check target difficulty target := new(big.Int).Div(maxUint256, big.NewInt(h.diff.Int64())) if result.Big().Cmp(target) <= 0 { @@ -128,39 +113,49 @@ func (s *ProxyServer) processShare(login, id, ip string, t *BlockTemplate, param return false, false } else { s.fetchBlockTemplate() - exist, err := s.backend.WriteBlock(login, id, params, shareDiff, shareDiffCalc, h.diff.Int64(), h.height, s.hashrateExpiration, stratumHostname) - if exist { - return true, false - } - if err != nil { - log.Println("Failed to insert block candidate into backend:", err) + if miningType == "solo" { + exist, err := s.backend.WriteBlockSolo(login, id, params, shareDiff, shareDiffCalc, h.diff.Int64(), h.height, s.hashrateExpiration, stratumHostname) + if exist { + return true, false + } + if err != nil { + log.Println("Failed to insert block candidate into backend:", err) + } else { + log.Printf("Inserted block %v to backend", h.height) + } } else { - log.Printf("Inserted block %v to backend", h.height) + exist, err := s.backend.WriteBlock(login, id, params, shareDiff, shareDiffCalc, h.diff.Int64(), h.height, s.hashrateExpiration, stratumHostname) + if exist { + return true, false + } + if err != nil { + log.Println("Failed to insert block candidate into backend:", err) + } else { + log.Printf("Inserted block %v to backend", h.height) + } } + log.Printf("Block found by miner %v@%v at height %d", login, ip, h.height) } } else { - exist, err := s.backend.WriteShare(login, id, params, shareDiff, shareDiffCalc, h.height, s.hashrateExpiration, stratumHostname) - if exist { - return true, false - } - if err != nil { - log.Println("Failed to insert share data into backend:", err) + if miningType == "solo" { + exist, err := s.backend.WriteShareSolo(login, id, params, shareDiff, shareDiffCalc, h.height, s.hashrateExpiration, stratumHostname) + if exist { + return true, false + } + if err != nil { + log.Println("Failed to insert share data into backend:", err) + } + } else { + exist, err := s.backend.WriteShare(login, id, params, shareDiff, shareDiffCalc, h.height, s.hashrateExpiration, stratumHostname) + if exist { + return true, false + } + if err != nil { + log.Println("Failed to insert share data into backend:", err) + } } } s.backend.WriteWorkerShareStatus(login, id, true, false, false) return false, true } - -func formatHashrate(shareDiffCalc int64) string { - units := []string{"H/s", "KH/s", "MH/s", "GH/s", "TH/s", "PH/s"} - var i int - diff := float64(shareDiffCalc) - - for i = 0; i < len(units)-1 && diff >= 1000.0; i++ { - diff /= 1000.0 - } - - formatted := strconv.FormatFloat(diff, 'f', 2, 64) - return formatted + " " + units[i] -} diff --git a/proxy/proto.go b/proxy/proto.go index f6d07514..67659e38 100644 --- a/proxy/proto.go +++ b/proxy/proto.go @@ -30,7 +30,7 @@ type JSONPushMessage struct { } type JSONRpcResp struct { - Id json.RawMessage `json:"id"` + Id json.RawMessage `json:"id,omitempty"` Version string `json:"jsonrpc,omitempty"` Result interface{} `json:"result"` Error interface{} `json:"error"` diff --git a/proxy/proxy.go b/proxy/proxy.go index e1ef0829..0b4ce0e4 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -68,7 +68,6 @@ type jobDetails struct { SeedHash string HeaderHash string Height string - Epoch int64 } func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer { @@ -79,7 +78,6 @@ func NewProxy(cfg *Config, backend *storage.RedisClient) *ProxyServer { proxy := &ProxyServer{config: cfg, backend: backend, policy: policy} proxy.diff = util.GetTargetHex(cfg.Proxy.Difficulty) - proxy.upstreams = make([]*rpc.RPCClient, len(cfg.Upstream)) for i, v := range cfg.Upstream { proxy.upstreams[i] = rpc.NewRPCClient(v.Name, v.Url, v.Timeout) @@ -175,6 +173,7 @@ func (s *ProxyServer) Start() { r := mux.NewRouter() r.Handle("/{login:0x[0-9a-fA-F]{40}}/{id:[0-9a-zA-Z-_]{1,200}}", s) r.Handle("/{login:0x[0-9a-fA-F]{40}}", s) + r.HandleFunc("/ethw", s.MiningNotify) srv := &http.Server{ Addr: s.config.Proxy.Listen, Handler: r, @@ -361,3 +360,73 @@ func (s *ProxyServer) isSick() bool { func (s *ProxyServer) markOk() { atomic.StoreInt64(&s.failsCount, 0) } + +func (s *ProxyServer) MiningNotify(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "405 method not allowed.", http.StatusMethodNotAllowed) + return + } + + body := make([]byte, r.ContentLength) + r.Body.Read(body) + + var reply []string + err := json.Unmarshal(body, &reply) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + w.WriteHeader(http.StatusOK) + + t := s.currentBlockTemplate() + // No need to update, we have fresh job + if t != nil { + if t.Header == reply[0] { + return + } + if _, ok := t.headers[reply[0]]; ok { + return + } + } + diff := util.TargetHexToDiff(reply[2]) + height, err := strconv.ParseUint(strings.Replace(reply[3], "0x", "", -1), 16, 64) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + pendingReply := &rpc.GetBlockReplyPart{ + Difficulty: util.ToHex(s.config.Proxy.Difficulty), + Number: reply[3], + } + + newTemplate := BlockTemplate{ + Header: reply[0], + Seed: reply[1], + Target: reply[2], + Height: height, + Difficulty: diff, + GetPendingBlockCache: pendingReply, + headers: make(map[string]heightDiffPair), + } + // Copy job backlog and add current one + newTemplate.headers[reply[0]] = heightDiffPair{ + diff: diff, + height: height, + } + if t != nil { + for k, v := range t.headers { + if v.height > height-maxBacklog { + newTemplate.headers[k] = v + } + } + } + s.blockTemplate.Store(&newTemplate) + + log.Printf("New block notified at height %d / %s / %d", height, reply[0][0:10], diff) + + // Stratum + if s.config.Proxy.Stratum.Enabled { + go s.broadcastNewJobs() + } +} diff --git a/rpc/rpc.go b/rpc/rpc.go index 0dd56b98..2c72c488 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -16,24 +16,6 @@ import ( "github.com/yuriy0803/open-etc-pool-friends/util" ) -const ( - // RPC method "eth_getWork" - RPCEthGetWork = "eth_getWork" - - // RPC method "eth_getBlockByNumber" - RPCEthGetBlockByNumber = "eth_getBlockByNumber" - - // RPC method "eth_getBlockByHash" - RPCEthGetBlockByHash = "eth_getBlockByHash" - - // Additional RPC method constants can be added here - RPCMethodFoo = "foo" - RPCMethodBar = "bar" - - RPCMethodBaz = "baz" - RPCMethodQux = "qux" -) - type RPCClient struct { sync.RWMutex Url string @@ -45,20 +27,19 @@ type RPCClient struct { } type GetBlockReply struct { - Number string `json:"number"` - Hash string `json:"hash"` - Nonce string `json:"nonce"` - Miner string `json:"miner"` - Difficulty string `json:"difficulty"` - GasLimit string `json:"gasLimit"` - GasUsed string `json:"gasUsed"` - Timestamp string `json:"timestamp"` - Transactions []Tx `json:"transactions"` - Uncles []string `json:"uncles"` + Number string `json:"number"` + Hash string `json:"hash"` + Nonce string `json:"nonce"` + Miner string `json:"miner"` + Difficulty string `json:"difficulty"` + GasLimit string `json:"gasLimit"` + GasUsed string `json:"gasUsed"` + BaseFeePerGas string `json:"baseFeePerGas"` + Timestamp string `json:"timestamp"` + Transactions []Tx `json:"transactions"` + Uncles []string `json:"uncles"` // https://github.com/ethereum/EIPs/issues/95 SealFields []string `json:"sealFields"` - // london hard fork - BaseFeePerGas string `json:"baseFeePerGas"` } type GetBlockReplyPart struct { diff --git a/storage/redis.go b/storage/redis.go index 27a9b5d8..cd4ca422 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -2,19 +2,18 @@ package storage import ( "fmt" - "log" "math" "math/big" "net/smtp" "sort" "strconv" "strings" - "sync" "time" - redis "gopkg.in/redis.v3" + "log" "github.com/yuriy0803/open-etc-pool-friends/util" + "gopkg.in/redis.v3" ) type Config struct { @@ -28,52 +27,22 @@ type Config struct { } type RedisClient struct { - client *redis.Client - prefix string - pplns int64 - CoinName string -} - -type PoolCharts struct { - Timestamp int64 `json:"x"` - TimeFormat string `json:"timeFormat"` - PoolHash int64 `json:"y"` -} - -type MinerCharts struct { - Timestamp int64 `json:"x"` - TimeFormat string `json:"timeFormat"` - MinerHash int64 `json:"minerHash"` - MinerLargeHash int64 `json:"minerLargeHash"` - WorkerOnline string `json:"workerOnline"` + client *redis.Client + prefix string + pplns int64 + CoinName string + prefixSolo string } - -type PaymentCharts struct { - Timestamp int64 `json:"x"` - TimeFormat string `json:"timeFormat"` - Amount int64 `json:"amount"` -} - -type LuckCharts struct { - Timestamp int64 `json:"x"` - Height int64 `json:"height"` - Difficulty int64 `json:"difficulty"` - Shares int64 `json:"shares"` - SharesDiff float64 `json:"sharesDiff"` - Reward string `json:"reward"` -} - type SumRewardData struct { Interval int64 `json:"inverval"` Reward int64 `json:"reward"` Name string `json:"name"` Offset int64 `json:"offset"` Blocks int64 `json:"blocks"` - Effort float64 `json:"personalLuck"` + Effort float64 `json:"averageLuck"` Count float64 `json:"_"` ESum float64 `json:"_"` } - type RewardData struct { Height int64 `json:"blockheight"` Timestamp int64 `json:"timestamp"` @@ -85,10 +54,7 @@ type RewardData struct { PersonalShares int64 `json:"-"` PersonalEffort float64 `json:"personalLuck"` } - type BlockData struct { - Worker string `json:"worker"` - ShareDiffCalc int64 `json:"shareDiff"` Height int64 `json:"height"` Timestamp int64 `json:"timestamp"` Difficulty int64 `json:"difficulty"` @@ -99,6 +65,7 @@ type BlockData struct { Orphan bool `json:"orphan"` Hash string `json:"hash"` Finder string `json:"finder"` + Worker string `json:"worker"` Nonce string `json:"-"` PowHash string `json:"-"` MixDigest string `json:"-"` @@ -109,6 +76,21 @@ type BlockData struct { RoundHeight int64 `json:"-"` candidateKey string immatureKey string + MiningType string `json:"miningType"` + ShareDiffCalc int64 `json:"shareDiff"` +} + +type PoolCharts struct { + Timestamp int64 `json:"x"` + TimeFormat string `json:"timeFormat"` + PoolHash int64 `json:"y"` +} +type MinerCharts struct { + Timestamp int64 `json:"x"` + TimeFormat string `json:"timeFormat"` + MinerHash int64 `json:"minerHash"` + MinerLargeHash int64 `json:"minerLargeHash"` + WorkerOnline string `json:"workerOnline"` } type NetCharts struct { @@ -125,6 +107,21 @@ type ShareCharts struct { WorkerOnline string `json:"workerOnline"` } +type PaymentCharts struct { + Timestamp int64 `json:"x"` + TimeFormat string `json:"timeFormat"` + Amount int64 `json:"amount"` +} + +type LuckCharts struct { + Timestamp int64 `json:"x"` + Height int64 `json:"height"` + Difficulty int64 `json:"difficulty"` + Shares int64 `json:"shares"` + SharesDiff float64 `json:"sharesDiff"` + Reward string `json:"reward"` +} + func (b *BlockData) RewardInShannon() int64 { reward := new(big.Int).Div(b.Reward, util.Shannon) return reward.Int64() @@ -143,13 +140,14 @@ func (b *BlockData) RoundKey() string { } func (b *BlockData) key() string { - return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Finder, b.ShareDiffCalc, b.Worker, b.PersonalShares) + return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Finder, b.Reward, b.Worker, b.MiningType, b.ShareDiffCalc, b.PersonalShares) } type Miner struct { LastBeat int64 `json:"lastBeat"` HR int64 `json:"hr"` Offline bool `json:"offline"` + Solo bool `json:"solo"` startedAt int64 Blocks int64 `json:"blocks"` } @@ -169,7 +167,7 @@ type Worker struct { WorkerStatushas int64 `json:"w_stat_s"` } -func NewRedisClient(cfg *Config, prefix string, pplns int64, CoinName string) *RedisClient { +func NewRedisClient(cfg *Config, prefix string, pplns int64, CoinName string, CoinSolo string) *RedisClient { var client *redis.Client if cfg.SentinelEnabled && len(cfg.MasterName) != 0 && len(cfg.SentinelAddrs) != 0 { // sentinel mode @@ -189,7 +187,7 @@ func NewRedisClient(cfg *Config, prefix string, pplns int64, CoinName string) *R PoolSize: cfg.PoolSize, }) } - return &RedisClient{client: client, prefix: prefix, pplns: pplns, CoinName: CoinName} + return &RedisClient{client: client, prefix: prefix, pplns: pplns, CoinName: CoinName, prefixSolo: CoinSolo} } func (r *RedisClient) Client() *redis.Client { @@ -222,514 +220,334 @@ func (r *RedisClient) GetWhitelist() ([]string, error) { return cmd.Val(), nil } -func (r *RedisClient) WritePoolCharts(time1 int64, time2 string, poolHash string) error { - s := join(time1, time2, poolHash) - cmd := r.client.ZAdd(r.formatKey("charts", "pool"), redis.Z{Score: float64(time1), Member: s}) - return cmd.Err() -} +func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int, blocktime float64) error { + tx := r.client.Multi() + defer tx.Close() -func (r *RedisClient) WriteDiffCharts(time1 int64, time2 string, netHash string) error { - s := join(time1, time2, netHash) - cmd := r.client.ZAdd(r.formatKey("charts", "difficulty"), redis.Z{Score: float64(time1), Member: s}) - return cmd.Err() -} + now := util.MakeTimestamp() / 1000 -func (r *RedisClient) WriteMinerCharts(time1 int64, time2, k string, hash, largeHash, workerOnline int64) error { - s := join(time1, time2, hash, largeHash, workerOnline) - cmd := r.client.ZAdd(r.formatKey("charts", "miner", k), redis.Z{Score: float64(time1), Member: s}) - return cmd.Err() + _, err := tx.Exec(func() error { + tx.HSet(r.formatKey("nodes"), join(id, "name"), id) + tx.HSet(r.formatKey("nodes"), join(id, "height"), strconv.FormatUint(height, 10)) + tx.HSet(r.formatKey("nodes"), join(id, "difficulty"), diff.String()) + tx.HSet(r.formatKey("nodes"), join(id, "lastBeat"), strconv.FormatInt(now, 10)) + tx.HSet(r.formatKey("nodes"), join(id, "blocktime"), strconv.FormatFloat(blocktime, 'f', 4, 64)) + return nil + }) + return err } -func (r *RedisClient) WriteShareCharts(time1 int64, time2, login string, valid, stale, workerOnline int64) error { - valid_s := r.client.HGet(r.formatKey("chartsNum", "share", login), "valid") - stale_s := r.client.HGet(r.formatKey("chartsNum", "share", login), "stale") - - if valid_s.Err() == redis.Nil || stale_s.Err() == redis.Nil { - r.client.HSet(r.formatKey("chartsNum", "share", login), "valid", strconv.FormatInt(0, 10)) - r.client.HSet(r.formatKey("chartsNum", "share", login), "stale", strconv.FormatInt(0, 10)) - //return nil, nil - } else if valid_s.Err() != nil || stale_s.Err() != nil { - r.client.HSet(r.formatKey("chartsNum", "share", login), "valid", strconv.FormatInt(0, 10)) - r.client.HSet(r.formatKey("chartsNum", "share", login), "stale", strconv.FormatInt(0, 10)) - //return nil, valid_s.Err() +func (r *RedisClient) GetNodeStates() ([]map[string]interface{}, error) { + cmd := r.client.HGetAllMap(r.formatKey("nodes")) + if cmd.Err() != nil { + return nil, cmd.Err() } - - v_s, _ := valid_s.Int64() - s_s, _ := stale_s.Int64() - - l_valid := r.client.HGet(r.formatKey("chartsNum", "share", login), "lastvalid") - l_stale := r.client.HGet(r.formatKey("chartsNum", "share", login), "laststale") - - if l_valid.Err() == redis.Nil || l_stale.Err() == redis.Nil { - r.client.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(0, 10)) - r.client.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(0, 10)) - //return nil, nil - } else if l_valid.Err() != nil || l_stale.Err() != nil { - r.client.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(0, 10)) - r.client.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(0, 10)) - //return nil, l_valid.Err() + m := make(map[string]map[string]interface{}) + for key, value := range cmd.Val() { + parts := strings.Split(key, ":") + if val, ok := m[parts[0]]; ok { + val[parts[1]] = value + } else { + node := make(map[string]interface{}) + node[parts[1]] = value + m[parts[0]] = node + } } - l_v, _ := l_valid.Int64() - l_s, _ := l_stale.Int64() + v := make([]map[string]interface{}, len(m), len(m)) + i := 0 + for _, value := range m { + v[i] = value + i++ + } + return v, nil +} - valid_c := v_s - l_v - stale_c := s_s - l_s - s := join(time1, time2, valid_c, stale_c, workerOnline) - cmd := r.client.ZAdd(r.formatKey("charts", "share", login), redis.Z{Score: float64(time1), Member: s}) +func (r *RedisClient) checkPoWExist(height uint64, params []string) (bool, error) { + // Sweep PoW backlog for previous blocks, we have 3 templates back in RAM + r.client.ZRemRangeByScore(r.formatKey("pow"), "-inf", fmt.Sprint("(", height-8)) + val, err := r.client.ZAdd(r.formatKey("pow"), redis.Z{Score: float64(height), Member: strings.Join(params, ":")}).Result() + return val == 0, err +} +func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, shareDiffCalc int64, height uint64, window time.Duration, hostname string) (bool, error) { + exist, err := r.checkPoWExist(height, params) + if err != nil { + return false, err + } + // Duplicate share, (nonce, powHash, mixDigest) pair exist + if exist { + return true, nil + } tx := r.client.Multi() defer tx.Close() - tx.Exec(func() error { - tx.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(v_s, 10)) - tx.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(s_s, 10)) + + ms := util.MakeTimestamp() + ts := ms / 1000 + + _, err = tx.Exec(func() error { + r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) + tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) return nil }) - return cmd.Err() + return false, err } -// GetPoolCharts retrieves a list of pool charts from Redis. -// The `poolHashLen` argument determines the maximum number of pool charts to retrieve. -// The function returns a slice of `PoolCharts` structures representing the pool charts and an error (if any). -func (r *RedisClient) GetPoolCharts(poolHashLen int64) (stats []*PoolCharts, err error) { - // Begin a Redis transaction +func (r *RedisClient) WriteShareSolo(login, id string, params []string, diff int64, shareDiffCalc int64, height uint64, window time.Duration, hostname string) (bool, error) { + exist, err := r.checkPoWExist(height, params) + if err != nil { + return false, err + } + // Duplicate share, (nonce, powHash, mixDigest) pair exist + if exist { + return true, nil + } tx := r.client.Multi() defer tx.Close() - // Compute the current timestamp (in seconds) - now := util.MakeTimestamp() / 1000 - - // Execute the Redis transaction - cmds, err := tx.Exec(func() error { - // Remove all pool charts that are older than 48 hours (172800 seconds) - tx.ZRemRangeByScore(r.formatKey("charts", "pool"), "-inf", fmt.Sprint("(", now-172800)) - - // Retrieve the most recent `poolHashLen` pool charts in descending order of timestamp - zRangeCmd := tx.ZRevRangeWithScores(r.formatKey("charts", "pool"), 0, poolHashLen) - - // Return any error that occurred during the transaction - if zRangeCmd.Err() != nil { - return zRangeCmd.Err() - } + ms := util.MakeTimestamp() + ts := ms / 1000 + _, err = tx.Exec(func() error { + r.writeShareSolo(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) + tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) return nil }) + return false, err +} + +func (r *RedisClient) GetNetworkDifficulty() (*big.Int, error) { - // Handle any errors that occurred during the Redis transaction + NetworkDifficultyDivShareDiff := big.NewInt(0) + m, err := r.GetNodeStates() if err != nil { - return nil, err - } + return NetworkDifficultyDivShareDiff, err - // Check that the result of the second Redis command is a `ZSliceCmd` - zSliceCmd, ok := cmds[1].(*redis.ZSliceCmd) - if !ok { - return nil, fmt.Errorf("invalid command result type: %T", cmds[1]) + } + for _, value := range m { + for legend, data := range value { + if legend == "difficulty" { + NetworkDifficultyDivShareDiff.SetString(join(data), 10) + return NetworkDifficultyDivShareDiff, nil + } + } } - // Convert the Redis result into a slice of `PoolCharts` structures - stats = convertPoolChartsResults(zSliceCmd) + return NetworkDifficultyDivShareDiff, err - return stats, nil } -// convertPoolChartsResults is a helper function that converts a `ZSliceCmd` result from Redis -// into a slice of `PoolCharts` structures. -func convertPoolChartsResults(raw *redis.ZSliceCmd) []*PoolCharts { - var result []*PoolCharts - for _, v := range raw.Val() { - // "Timestamp:TimeFormat:Hash" - pc := PoolCharts{} - pc.Timestamp = int64(v.Score) - str := v.Member.(string) - pc.TimeFormat = str[strings.Index(str, ":")+1 : strings.LastIndex(str, ":")] - pc.PoolHash, _ = strconv.ParseInt(str[strings.LastIndex(str, ":")+1:], 10, 64) - result = append(result, &pc) - } +func (r *RedisClient) LogIP(login string, ip string) { - // Reverse the order of the `PoolCharts` slice (to put the most recent chart first) - var reverse []*PoolCharts - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } + r.client.HSet(r.formatKey("settings", login), "ip_address", ip) + r.client.HSet(r.formatKey("settings", login), "status", "online") + r.client.HSet(r.formatKey("settings", login), "email_sent", "0") + + ms := util.MakeTimestamp() + ts := ms / 1000 + r.client.HSet(r.formatKey("settings", login), "ip_time", strconv.FormatInt(ts, 10)) - return reverse } -func (r *RedisClient) GetNetCharts(netHashLen int64) (stats []*NetCharts, err error) { +func (r *RedisClient) WriteBlock(login, id string, params []string, diff, shareDiffCalc int64, roundDiff int64, height uint64, window time.Duration, hostname string) (bool, error) { + exist, err := r.checkPoWExist(height, params) + if err != nil { + return false, err + } + // Duplicate share, (nonce, powHash, mixDigest) pair exist + if exist { + return true, nil + } tx := r.client.Multi() defer tx.Close() - now := util.MakeTimestamp() / 1000 + ms := util.MakeTimestamp() + ts := ms / 1000 + var s string cmds, err := tx.Exec(func() error { - if err := tx.ZRemRangeByScore(r.formatKey("charts", "difficulty"), "-inf", fmt.Sprint("(", now-172800)).Err(); err != nil { - return err - } - zRangeCmd := tx.ZRevRangeWithScores(r.formatKey("charts", "difficulty"), 0, netHashLen) - if zRangeCmd.Err() != nil { - return zRangeCmd.Err() - } + r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) + tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) + tx.HDel(r.formatKey("stats"), "roundShares") + tx.HSet(r.formatKey("miners", login), "roundShares", strconv.FormatInt(0, 10)) + tx.ZIncrBy(r.formatKey("finders"), 1, login) + tx.HIncrBy(r.formatKey("miners", login), "blocksFound", 1) + tx.HGetAllMap(r.formatKey("shares", "roundCurrent")) + tx.Del(r.formatKey("shares", "roundCurrent")) + tx.LRange(r.formatKey("lastshares"), 0, r.pplns) return nil }) + r.WriteBlocksFound(ms, ts, login, id, params[0], diff) if err != nil { - return nil, err - } - - zSliceCmd, ok := cmds[1].(*redis.ZSliceCmd) - if !ok { - return nil, fmt.Errorf("invalid command result type: %T", cmds[1]) - } - stats, err = convertNetChartsResults(zSliceCmd) - if err != nil { - return nil, err - } - return stats, nil -} - -func convertNetChartsResults(raw *redis.ZSliceCmd) ([]*NetCharts, error) { - var result []*NetCharts - for _, v := range raw.Val() { - // "Timestamp:TimeFormat:Hash" - pc := NetCharts{} - pc.Timestamp = int64(v.Score) - str := v.Member.(string) - pc.TimeFormat = str[strings.Index(str, ":")+1 : strings.LastIndex(str, ":")] - pc.NetHash, _ = strconv.ParseInt(str[strings.LastIndex(str, ":")+1:], 10, 64) - result = append(result, &pc) - } - - var reverse []*NetCharts - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } - return reverse, nil -} + return false, err + } else { -func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { - var result []*MinerCharts - for _, v := range raw.Val() { - // "Timestamp:TimeFormat:Hash:largeHash:workerOnline" - mc := MinerCharts{} - mc.Timestamp = int64(v.Score) - str := v.Member.(string) - mc.TimeFormat = strings.Split(str, ":")[1] - mc.MinerHash, _ = strconv.ParseInt(strings.Split(str, ":")[2], 10, 64) - mc.MinerLargeHash, _ = strconv.ParseInt(strings.Split(str, ":")[3], 10, 64) - mc.WorkerOnline = strings.Split(str, ":")[4] - result = append(result, &mc) - } - var reverse []*MinerCharts - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } - return reverse -} + shares := cmds[len(cmds)-1].(*redis.StringSliceCmd).Val() -func (r *RedisClient) GetAllMinerAccount() (account []string, err error) { - var c int64 - for { - now := util.MakeTimestamp() / 1000 - c, keys, err := r.client.Scan(c, r.formatKey("miners", "*"), now).Result() + tx2 := r.client.Multi() + defer tx2.Close() - if err != nil { - return account, err + totalshares := make(map[string]int64) + for _, val := range shares { + totalshares[val] += 1 } - for _, key := range keys { - m := strings.Split(key, ":") - //if ( len(m) >= 2 && strings.Index(strings.ToLower(m[2]), "0x") == 0) { - if len(m) >= 2 { - account = append(account, m[2]) + + _, err := tx2.Exec(func() error { + for k, v := range totalshares { + tx2.HIncrBy(r.formatRound(int64(height), params[0]), k, v) } + return nil + }) + if err != nil { + return false, err } - if c == 0 { - break + + sharesMap, _ := cmds[len(cmds)-3].(*redis.StringStringMapCmd).Result() + totalShares := int64(0) + for _, v := range sharesMap { + n, _ := strconv.ParseInt(v, 10, 64) + totalShares += n } + + personalShares := int64(0) + personalShares = cmds[len(cmds)-14].(*redis.IntCmd).Val() + + hashHex := strings.Join(params, ":") + s = join(hashHex, ts, roundDiff, totalShares, login, id, "pplns", shareDiffCalc, personalShares) + + //log.Println("CANDIDATES s : ", s) + cmd := r.client.ZAdd(r.formatKey("blocks", "candidates"), redis.Z{Score: float64(height), Member: s}) + return false, cmd.Err() } - return account, nil } -func (r *RedisClient) GetMinerCharts(hashNum int64, login string) (stats []*MinerCharts, err error) { - +func (r *RedisClient) WriteBlockSolo(login, id string, params []string, diff, shareDiffCalc int64, roundDiff int64, height uint64, window time.Duration, hostname string) (bool, error) { + exist, err := r.checkPoWExist(height, params) + if err != nil { + return false, err + } + // Duplicate share, (nonce, powHash, mixDigest) pair exist + if exist { + return true, nil + } tx := r.client.Multi() defer tx.Close() - now := util.MakeTimestamp() / 1000 + + ms := util.MakeTimestamp() + ts := ms / 1000 + var s string + cmds, err := tx.Exec(func() error { - tx.ZRemRangeByScore(r.formatKey("charts", "miner", login), "-inf", fmt.Sprint("(", now-172800)) - tx.ZRevRangeWithScores(r.formatKey("charts", "miner", login), 0, hashNum) + r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) + tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) + tx.HDel(r.formatKey("stats"), "roundShares") + tx.HSet(r.formatKey("miners", login), "roundShares", strconv.FormatInt(0, 10)) + tx.ZIncrBy(r.formatKey("finders"), 1, login) + tx.HIncrBy(r.formatKey("miners", login), "blocksFound", 1) + tx.HGetAllMap(r.formatKey("shares", "roundCurrent")) + tx.Del(r.formatKey("shares", "roundCurrent")) + tx.LRange(r.formatKey("lastshares_solo"), 0, 0) return nil }) + r.WriteBlocksFound(ms, ts, login, id, params[0], diff) if err != nil { - return nil, err - } - stats = convertMinerChartsResults(cmds[1].(*redis.ZSliceCmd)) - return stats, nil -} + return false, err + } else { -// DeleteOldMinerData deletes old miner data from the "miner" ZSets. -// All entries that are older than 24 hours are removed. -func (r *RedisClient) DeleteOldMinerData() error { - now := time.Now() - pastTime := now.Add(-24 * time.Hour) + shares := cmds[len(cmds)-1].(*redis.StringSliceCmd).Val() - // Retrieve all keys matching the pattern "charts:miner:*" - loginKeys, err := r.client.Keys(r.formatKey("charts", "miner", "*")).Result() - if err != nil { - return err - } + tx2 := r.client.Multi() + defer tx2.Close() - // Iterate through all found keys and remove the old entries - for _, loginKey := range loginKeys { - _, err := r.client.ZRemRangeByScore(loginKey, "-inf", fmt.Sprintf("(%d", pastTime.Unix())).Result() - if err != nil { - return err + totalshares := make(map[string]int64) + for _, val := range shares { + totalshares[val] += 1 } - } - - return nil -} -// DeleteOldShareData deletes old share data from the "share" ZSets. -// All entries that are older than 24 hours are removed. -func (r *RedisClient) DeleteOldShareData() error { - now := time.Now() - pastTime := now.Add(-24 * time.Hour) - - // Retrieve all keys matching the pattern "charts:share:*" - shareKeys, err := r.client.Keys(r.formatKey("charts", "share", "*")).Result() - if err != nil { - return err - } - - // Iterate through all found keys and remove the old entries - for _, shareKey := range shareKeys { - _, err := r.client.ZRemRangeByScore(shareKey, "-inf", fmt.Sprintf("(%d", pastTime.Unix())).Result() + _, err := tx2.Exec(func() error { + for k, v := range totalshares { + tx2.HIncrBy(r.formatRound(int64(height), params[0]), k, v) + } + return nil + }) if err != nil { - return err + return false, err } - } - return nil -} + sharesMap, _ := cmds[len(cmds)-3].(*redis.StringStringMapCmd).Result() + totalShares := int64(0) + for _, v := range sharesMap { + n, _ := strconv.ParseInt(v, 10, 64) + totalShares += n + } + personalShares := int64(0) + personalShares = cmds[len(cmds)-14].(*redis.IntCmd).Val() -func (r *RedisClient) GetShareCharts(shareNum int64, login string) (stats []*ShareCharts, err error) { + hashHex := strings.Join(params, ":") - tx := r.client.Multi() - defer tx.Close() - now := util.MakeTimestamp() / 1000 - cmds, err := tx.Exec(func() error { - tx.ZRemRangeByScore(r.formatKey("charts", "share", login), "-inf", fmt.Sprint("(", now-172800)) - tx.ZRevRangeWithScores(r.formatKey("charts", "share", login), 0, shareNum) - return nil - }) - if err != nil { - return nil, err + s = join(hashHex, ts, roundDiff, totalShares, login, id, "solo", shareDiffCalc, personalShares) + + //log.Println("CANDIDATES s : ", s) + cmd := r.client.ZAdd(r.formatKey("blocks", "candidates"), redis.Z{Score: float64(height), Member: s}) + return false, cmd.Err() } - stats = convertShareChartsResults(cmds[1].(*redis.ZSliceCmd)) - return stats, nil } -func convertShareChartsResults(raw *redis.ZSliceCmd) []*ShareCharts { - var result []*ShareCharts - for _, v := range raw.Val() { +func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, shareDiffCalc int64, expire time.Duration, hostname string) { + /* # Note To Me: + Will have to write to get from redis the current value for round + shares and increase by 1, then include the new number to be added to redis + */ - mc := ShareCharts{} - mc.Timestamp = int64(v.Score) - str := v.Member.(string) - mc.TimeFormat = strings.Split(str, ":")[1] - mc.Valid, _ = strconv.ParseInt(strings.Split(str, ":")[2], 10, 64) - mc.Stale, _ = strconv.ParseInt(strings.Split(str, ":")[3], 10, 64) - mc.WorkerOnline = strings.Split(str, ":")[4] - result = append(result, &mc) - } - var reverse []*ShareCharts - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) + times := int(diff / 1000000000) + + // Moved get hostname to stratums + for i := 0; i < times; i++ { + tx.LPush(r.formatKey("lastshares"), login) } - return reverse + tx.LTrim(r.formatKey("lastshares"), 0, r.pplns) + tx.HIncrBy(r.formatKey("miners", login), "roundShares", diff) + + tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) + // For aggregation of hashrate, to store value in hashrate key + tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms, diff, hostname)}) + // For separate miner's workers hashrate, to store under hashrate table under login key + tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms, diff, hostname)}) + // Will delete hashrates for miners that gone + tx.Expire(r.formatKey("hashrate", login), expire) + tx.HSet(r.formatKey("miners", login), "lastShare", strconv.FormatInt(ts, 10)) + tx.HSet(r.formatKey("miners", login), "lastShareDiff", strconv.FormatInt(shareDiffCalc, 10)) } -func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { +func (r *RedisClient) writeShareSolo(tx *redis.Multi, ms, ts int64, login, id string, diff int64, shareDiffCalc int64, expire time.Duration, hostname string) { + times := int(diff / 1000000000) - tx := r.client.Multi() - defer tx.Close() - cmds, err := tx.Exec(func() error { - tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, 360) - return nil - }) - if err != nil { - return nil, err + for i := 0; i < times; i++ { + tx.LPush(r.formatKey("lastshares_solo"), login) } - stats = convertPaymentChartsResults(cmds[0].(*redis.ZSliceCmd)) - //fmt.Println(stats) - return stats, nil + tx.LTrim(r.formatKey("lastshares_solo"), 0, 0) + tx.HIncrBy(r.formatKey("miners", login), "roundShares", diff) + + tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) + // For aggregation of hashrate, to store value in hashrate key + tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms, diff, hostname)}) + // For separate miner's workers hashrate, to store under hashrate table under login key + tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms, diff, hostname)}) + // Will delete hashrates for miners that gone + tx.Expire(r.formatKey("hashrate", login), expire) + tx.HSet(r.formatKey("miners", login), "lastShare", strconv.FormatInt(ts, 10)) + tx.HSet(r.formatKey("miners", login), "lastShareDiff", strconv.FormatInt(shareDiffCalc, 10)) +} +func (r *RedisClient) formatKey(args ...interface{}) string { + return join(r.prefix, join(args...)) } -func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int, blocktime float64) error { - tx := r.client.Multi() - defer tx.Close() +func (r *RedisClient) formatRound(height int64, nonce string) string { + return r.formatKey("shares", "round"+strconv.FormatInt(height, 10), nonce) +} - now := util.MakeTimestamp() / 1000 - - _, err := tx.Exec(func() error { - tx.HSet(r.formatKey("nodes"), join(id, "name"), id) - tx.HSet(r.formatKey("nodes"), join(id, "height"), strconv.FormatUint(height, 10)) - tx.HSet(r.formatKey("nodes"), join(id, "difficulty"), diff.String()) - tx.HSet(r.formatKey("nodes"), join(id, "lastBeat"), strconv.FormatInt(now, 10)) - tx.HSet(r.formatKey("nodes"), join(id, "blocktime"), strconv.FormatFloat(blocktime, 'f', 4, 64)) - return nil - }) - return err -} - -func (r *RedisClient) GetNodeStates() ([]map[string]interface{}, error) { - cmd := r.client.HGetAllMap(r.formatKey("nodes")) - if cmd.Err() != nil { - return nil, cmd.Err() - } - m := make(map[string]map[string]interface{}) - for key, value := range cmd.Val() { - parts := strings.Split(key, ":") - if val, ok := m[parts[0]]; ok { - val[parts[1]] = value - } else { - node := make(map[string]interface{}) - node[parts[1]] = value - m[parts[0]] = node - } - } - v := make([]map[string]interface{}, len(m), len(m)) - i := 0 - for _, value := range m { - v[i] = value - i++ - } - return v, nil -} - -func (r *RedisClient) checkPoWExist(height uint64, params []string) (bool, error) { - // Sweep PoW backlog for previous blocks, we have 3 templates back in RAM - r.client.ZRemRangeByScore(r.formatKey("pow"), "-inf", fmt.Sprint("(", height-8)) - val, err := r.client.ZAdd(r.formatKey("pow"), redis.Z{Score: float64(height), Member: strings.Join(params, ":")}).Result() - return val == 0, err -} - -func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, shareDiffCalc int64, height uint64, window time.Duration, hostname string) (bool, error) { - exist, err := r.checkPoWExist(height, params) - if err != nil { - return false, err - } - // Duplicate share, (nonce, powHash, mixDigest) pair exist - if exist { - return true, nil - } - tx := r.client.Multi() - defer tx.Close() - - ms := util.MakeTimestamp() - ts := ms / 1000 - - _, err = tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) - tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) - return nil - }) - return false, err -} - -func (r *RedisClient) WriteBlock(login, id string, params []string, diff, shareDiffCalc int64, roundDiff int64, height uint64, window time.Duration, hostname string) (bool, error) { - exist, err := r.checkPoWExist(height, params) - if err != nil { - return false, err - } - // Duplicate share, (nonce, powHash, mixDigest) pair exist - if exist { - return true, nil - } - tx := r.client.Multi() - defer tx.Close() - - ms := util.MakeTimestamp() - ts := ms / 1000 - - cmds, err := tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, shareDiffCalc, window, hostname) - tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) - tx.HDel(r.formatKey("stats"), "roundShares") - tx.HSet(r.formatKey("miners", login), "roundShares", strconv.FormatInt(0, 10)) - tx.ZIncrBy(r.formatKey("finders"), 1, login) - tx.HIncrBy(r.formatKey("miners", login), "blocksFound", 1) - tx.HGetAllMap(r.formatKey("shares", "roundCurrent")) - tx.Del(r.formatKey("shares", "roundCurrent")) - tx.LRange(r.formatKey("lastshares"), 0, r.pplns) - return nil - }) - r.WriteBlocksFound(ms, ts, login, id, params[0], diff) - if err != nil { - return false, err - } else { - - shares := cmds[len(cmds)-1].(*redis.StringSliceCmd).Val() - - tx2 := r.client.Multi() - defer tx2.Close() - - totalshares := make(map[string]int64) - for _, val := range shares { - totalshares[val] += 1 - } - - _, err := tx2.Exec(func() error { - for k, v := range totalshares { - tx2.HIncrBy(r.formatRound(int64(height), params[0]), k, v) - } - return nil - }) - if err != nil { - return false, err - } - - sharesMap, _ := cmds[len(cmds)-3].(*redis.StringStringMapCmd).Result() - totalShares := int64(0) - for _, v := range sharesMap { - n, _ := strconv.ParseInt(v, 10, 64) - totalShares += n - } - - personalShares := int64(0) - personalShares = cmds[len(cmds)-14].(*redis.IntCmd).Val() - - hashHex := strings.Join(params, ":") - s := join(hashHex, ts, roundDiff, totalShares, login, shareDiffCalc, id, personalShares) - cmd := r.client.ZAdd(r.formatKey("blocks", "candidates"), redis.Z{Score: float64(height), Member: s}) - return false, cmd.Err() - } -} - -func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, shareDiffCalc int64, expire time.Duration, hostname string) { - times := int(diff / 1000000000) - for i := 0; i < times; i++ { - tx.LPush(r.formatKey("lastshares"), login) - } - tx.LTrim(r.formatKey("lastshares"), 0, r.pplns) - tx.HIncrBy(r.formatKey("miners", login), "roundShares", diff) - tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) - tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms, hostname)}) - tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms, hostname)}) - tx.Expire(r.formatKey("hashrate", login), expire) // Will delete hashrates for miners that gone - tx.HSet(r.formatKey("miners", login), "lastShare", strconv.FormatInt(ts, 10)) - tx.HSet(r.formatKey("miners", login), "lastShareDiff", strconv.FormatInt(shareDiffCalc, 10)) -} - -func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) { - r.client.ZAdd(r.formatKey("worker", "blocks", login), redis.Z{Score: float64(ts), Member: join(diff, share, id, ms)}) -} - -func (r *RedisClient) formatKey(args ...interface{}) string { - return join(r.prefix, join(args...)) -} - -func (r *RedisClient) formatRound(height int64, nonce string) string { - return r.formatKey("shares", "round"+strconv.FormatInt(height, 10), nonce) -} +func (r *RedisClient) formatRoundSolo(height int64, nonce string) string { + return r.formatKey("shares_solo", "round"+strconv.FormatInt(height, 10), nonce) +} func join(args ...interface{}) string { s := make([]string, len(args)) @@ -749,13 +567,6 @@ func join(args ...interface{}) string { } else { s[i] = "0" } - case *big.Rat: - x := v.(*big.Rat) - if x != nil { - s[i] = x.FloatString(9) - } else { - s[i] = "0" - } case *big.Int: n := v.(*big.Int) if n != nil { @@ -763,6 +574,13 @@ func join(args ...interface{}) string { } else { s[i] = "0" } + case *big.Rat: + x := v.(*big.Rat) + if x != nil { + s[i] = x.FloatString(9) + } else { + s[i] = "0" + } default: panic("Invalid type specified for conversion") } @@ -773,21 +591,26 @@ func join(args ...interface{}) string { func (r *RedisClient) GetCandidates(maxHeight int64) ([]*BlockData, error) { option := redis.ZRangeByScore{Min: "0", Max: strconv.FormatInt(maxHeight, 10)} cmd := r.client.ZRangeByScoreWithScores(r.formatKey("blocks", "candidates"), option) + if cmd.Err() != nil { return nil, cmd.Err() } - return convertCandidateResults(cmd), nil + blockData := convertCandidateResults(cmd) + + return blockData, nil } func (r *RedisClient) GetImmatureBlocks(maxHeight int64) ([]*BlockData, error) { option := redis.ZRangeByScore{Min: "0", Max: strconv.FormatInt(maxHeight, 10)} cmd := r.client.ZRangeByScoreWithScores(r.formatKey("blocks", "immature"), option) + if cmd.Err() != nil { return nil, cmd.Err() } - return convertBlockResults(cmd), nil -} + blockData := convertBlockResults(cmd) + return blockData, nil +} func (r *RedisClient) GetRewards(login string) ([]*RewardData, error) { option := redis.ZRangeByScore{Min: "0", Max: strconv.FormatInt(10, 10)} cmd := r.client.ZRangeByScoreWithScores(r.formatKey("rewards", login), option) @@ -831,7 +654,7 @@ func (r *RedisClient) GetPayees() ([]string, error) { break } } - for login, _ := range payees { + for login := range payees { result = append(result, login) } return result, nil @@ -848,6 +671,7 @@ func (r *RedisClient) GetTotalShares() (int64, error) { } func (r *RedisClient) GetBalance(login string) (int64, error) { + cmd := r.client.HGet(r.formatKey("miners", login), "balance") if cmd.Err() == redis.Nil { return 0, nil @@ -855,6 +679,24 @@ func (r *RedisClient) GetBalance(login string) (int64, error) { return 0, cmd.Err() } return cmd.Int64() + +} + +func (r *RedisClient) GetThreshold(login string) (int64, error) { + cmd := r.client.HGet(r.formatKey("settings", login), "payoutthreshold") + if cmd.Err() == redis.Nil { + return 500000000, nil + } else if cmd.Err() != nil { + log.Println("GetThreshold error :", cmd.Err()) + return 500000000, cmd.Err() + } + + return cmd.Int64() +} + +func (r *RedisClient) SetThreshold(login string, threshold int64) (bool, error) { + cmd, err := r.client.HSet(r.formatKey("settings", login), "payoutthreshold", strconv.FormatInt(threshold, 10)).Result() + return cmd, err } func (r *RedisClient) LockPayouts(login string, amount int64) error { @@ -912,9 +754,9 @@ func (r *RedisClient) UpdateBalance(login string, amount int64) error { ts := util.MakeTimestamp() / 1000 _, err := tx.Exec(func() error { - tx.HIncrBy(r.formatKey("miners", login), "balance", (amount * -1)) + tx.HIncrBy(r.formatKey("miners", login), "balance", amount*-1) tx.HIncrBy(r.formatKey("miners", login), "pending", amount) - tx.HIncrBy(r.formatKey("finances"), "balance", (amount * -1)) + tx.HIncrBy(r.formatKey("finances"), "balance", amount*-1) tx.HIncrBy(r.formatKey("finances"), "pending", amount) tx.ZAdd(r.formatKey("payments", "pending"), redis.Z{Score: float64(ts), Member: join(login, amount)}) return nil @@ -928,34 +770,31 @@ func (r *RedisClient) RollbackBalance(login string, amount int64) error { _, err := tx.Exec(func() error { tx.HIncrBy(r.formatKey("miners", login), "balance", amount) - tx.HIncrBy(r.formatKey("miners", login), "pending", (amount * -1)) + tx.HIncrBy(r.formatKey("miners", login), "pending", amount*-1) tx.HIncrBy(r.formatKey("finances"), "balance", amount) - tx.HIncrBy(r.formatKey("finances"), "pending", (amount * -1)) + tx.HIncrBy(r.formatKey("finances"), "pending", amount*-1) tx.ZRem(r.formatKey("payments", "pending"), join(login, amount)) return nil }) return err } -func (r *RedisClient) WritePayment(login, txHash string, amount int64) error { +func (r *RedisClient) WritePayment(login, txHash string, amount int64, txCharges int64) error { tx := r.client.Multi() defer tx.Close() ts := util.MakeTimestamp() / 1000 - _, err := tx.Exec(func() error { - tx.HIncrBy(r.formatKey("miners", login), "pending", (amount * -1)) + tx.HIncrBy(r.formatKey("miners", login), "pending", amount*-1) tx.HIncrBy(r.formatKey("miners", login), "paid", amount) - tx.HIncrBy(r.formatKey("finances"), "pending", (amount * -1)) + tx.HIncrBy(r.formatKey("finances"), "pending", amount*-1) tx.HIncrBy(r.formatKey("finances"), "paid", amount) - tx.ZAdd(r.formatKey("payments", "all"), redis.Z{Score: float64(ts), Member: join(txHash, login, amount)}) - tx.ZRemRangeByRank(r.formatKey("payments", "all"), 0, -10000) - tx.ZAdd(r.formatKey("payments", login), redis.Z{Score: float64(ts), Member: join(txHash, amount)}) - tx.ZRemRangeByRank(r.formatKey("payments", login), 0, -100) + tx.HIncrBy(r.formatKey("finances"), "txcharges", txCharges) + + tx.ZAdd(r.formatKey("payments", "all"), redis.Z{Score: float64(ts), Member: join(txHash, login, amount, txCharges)}) + tx.ZAdd(r.formatKey("payments", login), redis.Z{Score: float64(ts), Member: join(txHash, amount, txCharges)}) tx.ZRem(r.formatKey("payments", "pending"), join(login, amount)) tx.Del(r.formatKey("payments", "lock")) - tx.HIncrBy(r.formatKey("paymentsTotal"), "all", 1) - tx.HIncrBy(r.formatKey("paymentsTotal"), login, 1) return nil }) return err @@ -970,34 +809,38 @@ func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp, block.Difficulty, block.PersonalShares) remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp, block.Difficulty, block.PersonalShares) - remscore := block.Timestamp - 3600*24*40 // Store the last 40 Days + + remscore := block.Timestamp - 3600*24*90 // Store the last 90 Days _, err := tx.Exec(func() error { tx.ZAdd(r.formatKey("rewards", login), redis.Z{Score: float64(block.Timestamp), Member: addStr}) tx.ZRem(r.formatKey("rewards", login), remStr) tx.ZRemRangeByScore(r.formatKey("rewards", login), "-inf", "("+strconv.FormatInt(remscore, 10)) - return nil }) return err + } func (r *RedisClient) WriteImmatureBlock(block *BlockData, roundRewards map[string]int64) error { tx := r.client.Multi() defer tx.Close() - _, err := tx.Exec(func() error { r.writeImmatureBlock(tx, block) total := int64(0) for login, amount := range roundRewards { + total += amount tx.HIncrBy(r.formatKey("miners", login), "immature", amount) tx.HSetNX(r.formatKey("credits", "immature", block.Height, block.Hash), login, strconv.FormatInt(amount, 10)) + } + tx.HIncrBy(r.formatKey("finances"), "immature", total) return nil }) return err + } func (r *RedisClient) WriteMaturedBlock(block *BlockData, roundRewards map[string]int64) error { @@ -1022,7 +865,7 @@ func (r *RedisClient) WriteMaturedBlock(block *BlockData, roundRewards map[strin for login, amountString := range immatureCredits.Val() { amount, _ := strconv.ParseInt(amountString, 10, 64) totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + tx.HIncrBy(r.formatKey("miners", login), "immature", amount*-1) } // Increment balances @@ -1031,15 +874,17 @@ func (r *RedisClient) WriteMaturedBlock(block *BlockData, roundRewards map[strin total += amount // NOTICE: Maybe expire round reward entry in 604800 (a week)? tx.HIncrBy(r.formatKey("miners", login), "balance", amount) - tx.HSetNX(r.formatKey("credits", block.Height, block.Hash), login, strconv.FormatInt(amount, 10)) + if amount > 0 { + tx.HSetNX(r.formatKey("credits", block.Height, block.Hash), login, strconv.FormatInt(amount, 10)) + } + } tx.Del(creditKey) tx.HIncrBy(r.formatKey("finances"), "balance", total) - tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) + tx.HIncrBy(r.formatKey("finances"), "immature", totalImmature*-1) tx.HSet(r.formatKey("finances"), "lastCreditHeight", strconv.FormatInt(block.Height, 10)) tx.HSet(r.formatKey("finances"), "lastCreditHash", block.Hash) tx.HIncrBy(r.formatKey("finances"), "totalMined", block.RewardInShannon()) - tx.Expire(r.formatKey("credits", block.Height, block.Hash), 604800*time.Second) return nil }) return err @@ -1054,7 +899,6 @@ func (r *RedisClient) WriteOrphan(block *BlockData) error { return err } defer tx.Close() - _, err = tx.Exec(func() error { r.writeMaturedBlock(tx, block) @@ -1063,13 +907,16 @@ func (r *RedisClient) WriteOrphan(block *BlockData) error { for login, amountString := range immatureCredits.Val() { amount, _ := strconv.ParseInt(amountString, 10, 64) totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + + tx.HIncrBy(r.formatKey("miners", login), "immature", amount*-1) + } tx.Del(creditKey) - tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) + tx.HIncrBy(r.formatKey("finances"), "immature", totalImmature*-1) return nil }) return err + } func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { @@ -1086,18 +933,23 @@ func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { } func (r *RedisClient) writeImmatureBlock(tx *redis.Multi, block *BlockData) { - // Redis 2.8.x returns "ERR source and destination objects are the same" + if block.Height != block.RoundHeight { tx.Rename(r.formatRound(block.RoundHeight, block.Nonce), r.formatRound(block.Height, block.Nonce)) } + tx.ZRem(r.formatKey("blocks", "candidates"), block.candidateKey) tx.ZAdd(r.formatKey("blocks", "immature"), redis.Z{Score: float64(block.Height), Member: block.key()}) + } func (r *RedisClient) writeMaturedBlock(tx *redis.Multi, block *BlockData) { + tx.Del(r.formatRound(block.RoundHeight, block.Nonce)) + tx.ZRem(r.formatKey("blocks", "immature"), block.immatureKey) tx.ZAdd(r.formatKey("blocks", "matured"), redis.Z{Score: float64(block.Height), Member: block.key()}) + } func (r *RedisClient) IsMinerExists(login string) (bool, error) { @@ -1114,10 +966,12 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string tx.HGetAllMap(r.formatKey("miners", login)) tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, maxPayments-1) tx.HGet(r.formatKey("paymentsTotal"), login) - tx.HGet(r.formatKey("shares", "currentShares"), login) + tx.HGet(r.formatKey("shares", "roundCurrent"), login) tx.LRange(r.formatKey("lastshares"), 0, r.pplns) tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, 39) tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, -1) + tx.LLen(r.formatKey("lastshares")) + return nil }) @@ -1137,6 +991,56 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string } } stats["roundShares"] = csh + stats["nShares"] = strconv.FormatInt(cmds[7].(*redis.IntCmd).Val(), 10) + + roundShares, _ := cmds[3].(*redis.StringCmd).Int64() + stats["accountRoundCurrentShares"] = roundShares + } + + return stats, nil +} + +func (r *RedisClient) GetMinerStatsSolo(login string, maxPayments int64) (map[string]interface{}, error) { + stats := make(map[string]interface{}) + + tx := r.client.Multi() + defer tx.Close() + + cmds, err := tx.Exec(func() error { + tx.HGetAllMap(r.formatKey("miners", login)) + tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, maxPayments-1) + tx.HGet(r.formatKey("paymentsTotal"), login) + //tx.HGet(r.formatKey("shares", "roundCurrent"), login) + //tx.LRange(r.formatKey("lastshares"), 0, 0) + //tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, 39) + //tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, -1) + //tx.LLen(r.formatKey("lastshares")) + + return nil + }) + + if err != nil && err != redis.Nil { + return nil, err + } else { + result, _ := cmds[0].(*redis.StringStringMapCmd).Result() + stats["stats"] = convertStringMap(result) + payments := convertPaymentsResults(cmds[1].(*redis.ZSliceCmd)) + stats["payments"] = payments + stats["paymentsTotal"], _ = cmds[2].(*redis.StringCmd).Int64() + // shares := cmds[4].(*redis.StringSliceCmd).Val() + //csh := 0 + //for _, val := range shares { + // if val == login { + // csh++ + // } + //} + stats["roundShares"] = 0 + //csh + stats["nShares"] = strconv.FormatInt(0, 10) + //strconv.FormatInt(cmds[7].(*redis.IntCmd).Val(), 10) + + //roundShares, _ := cmds[3].(*redis.StringCmd).Int64() + stats["accountRoundCurrentShares"] = 0 } return stats, nil @@ -1149,6 +1053,7 @@ func convertStringMap(m map[string]string) map[string]interface{} { for k, v := range m { result[k], err = strconv.ParseInt(v, 10, 64) if err != nil { + //If its IP Address, Dont Add those value to the Map for Security result[k] = v } } @@ -1205,67 +1110,110 @@ func (r *RedisClient) FlushStaleStats(staleDuration, largeWindow time.Duration) return total, nil } -func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPayments int64) (map[string]interface{}, error) { - window := int64(smallWindow / time.Second) +func (r *RedisClient) CollectBlocks(login string) (map[string]interface{}, error) { stats := make(map[string]interface{}) - tx := r.client.Multi() defer tx.Close() - now := util.MakeTimestamp() / 1000 - cmds, err := tx.Exec(func() error { - tx.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", fmt.Sprint("(", now-window)) - tx.ZRangeWithScores(r.formatKey("hashrate"), 0, -1) - tx.HGetAllMap(r.formatKey("stats")) + tx.ZRevRangeWithScores(r.formatKey("blocks", "candidates"), 0, -1) tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) - tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, maxBlocks-1) - tx.ZCard(r.formatKey("blocks", "candidates")) - tx.ZCard(r.formatKey("blocks", "immature")) - tx.ZCard(r.formatKey("blocks", "matured")) - tx.HGet(r.formatKey("paymentsTotal"), "all") + tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, 20) + return nil + }) + + if err != nil { + return nil, err + } + + candidates := convertCandidateResultsByLogin(login, cmds[0].(*redis.ZSliceCmd)) + stats["candidates"] = candidates + immature := convertBlockResultsByLogin(login, cmds[1].(*redis.ZSliceCmd)) + stats["immature"] = immature + matured := convertBlockResultsByLogin(login, cmds[2].(*redis.ZSliceCmd)) + stats["matured"] = matured + + return stats, nil +} + +func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPayments int64) (map[string]interface{}, error) { + window := int64(smallWindow / time.Second) + stats := make(map[string]interface{}) + tx := r.client.Multi() + defer tx.Close() + + now := util.MakeTimestamp() / 1000 + + cmds, err := tx.Exec(func() error { + tx.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", fmt.Sprint("(", now-window)) + tx.ZRangeWithScores(r.formatKey("hashrate"), 0, -1) + tx.HGetAllMap(r.formatKey("stats")) + tx.ZRevRangeWithScores(r.formatKey("blocks", "candidates"), 0, -1) + tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) + tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, maxBlocks-1) + tx.ZCard(r.formatKey("blocks", "candidates")) + tx.ZCard(r.formatKey("blocks", "immature")) + tx.ZCard(r.formatKey("blocks", "matured")) + tx.ZCard(r.formatKey("payments", "all")) tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) tx.LLen(r.formatKey("lastshares")) - tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) tx.HGetAllMap(r.formatKey("exchange", r.CoinName)) + tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) + tx.HIncrBy(r.formatKey("finances"), "paid", 0) + tx.HGet(r.formatKey("finances"), "paid") return nil }) - if (err != nil) && (err != redis.Nil) { + if err != nil { return nil, err } result, _ := cmds[2].(*redis.StringStringMapCmd).Result() result["nShares"] = strconv.FormatInt(cmds[11].(*redis.IntCmd).Val(), 10) + stats["stats"] = convertStringMap(result) candidates := convertCandidateResults(cmds[3].(*redis.ZSliceCmd)) + stats["candidates"] = candidates - stats["candidatesTotal"] = cmds[6].(*redis.IntCmd).Val() + candidatesTotal := cmds[6].(*redis.IntCmd).Val() + stats["candidatesTotal"] = candidatesTotal immature := convertBlockResults(cmds[4].(*redis.ZSliceCmd)) + stats["immature"] = immature - stats["immatureTotal"] = cmds[7].(*redis.IntCmd).Val() + immatureTotal := cmds[7].(*redis.IntCmd).Val() + stats["immatureTotal"] = immatureTotal matured := convertBlockResults(cmds[5].(*redis.ZSliceCmd)) + stats["matured"] = matured - stats["maturedTotal"] = cmds[8].(*redis.IntCmd).Val() + maturedTotal := cmds[8].(*redis.IntCmd).Val() + stats["maturedTotal"] = maturedTotal payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) + stats["payments"] = payments - stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() + stats["paymentsTotal"] = cmds[9].(*redis.IntCmd).Val() - finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) - stats["finders"] = finders + totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd), r) + totalWorkers := getTotalWorkers(window, cmds[1].(*redis.ZSliceCmd)) - totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) + stats["workersTotal"] = totalWorkers stats["miners"] = miners stats["minersTotal"] = len(miners) stats["hashrate"] = totalHashrate - exchangedata, _ := cmds[13].(*redis.StringStringMapCmd).Result() - stats["exchangedata"] = exchangedata + result1, err := cmds[12].(*redis.StringStringMapCmd).Result() + + if err != nil { + log.Fatalf("Error while geting Exchange Data => Error : %v", err) + } + stats["exchangedata"] = convertStringMap(result1) + finders := convertFindersResults(cmds[13].(*redis.ZSliceCmd)) + stats["finders"] = finders + stats["paymentsSum"] = cmds[15].(*redis.StringCmd).Val() return stats, nil } @@ -1285,6 +1233,8 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, 39) tx.ZRevRangeWithScores(r.formatKey("rewards", login), 0, -1) tx.ZRangeWithScores(r.formatKey("worker", "blocks", login), 0, -1) + tx.HGetAllMap(r.formatKey("exchange", r.CoinName)) + return nil }) @@ -1334,6 +1284,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login online++ r.SetWorkerWithEmailStatus(login, id, "0") } + blocks := cmds[4].(*redis.ZSliceCmd).Val() for _, val := range blocks { @@ -1346,7 +1297,6 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login } } } - currentHashrate += worker.HR totalHashrate += worker.TotalHR valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) @@ -1411,8 +1361,6 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login workers[id] = worker } - var personalEffort float64 - stats["workers"] = workers stats["workersTotal"] = len(workers) stats["workersOnline"] = online @@ -1431,7 +1379,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login dorew = append(dorew, &SumRewardData{Name: "Last 30 days", Interval: 3600 * 24 * 30, Offset: 0}) for _, reward := range rewards { - personalEffort = reward.PersonalEffort + for _, dore := range dorew { dore.Count += 0 dore.ESum += 0 @@ -1441,15 +1389,30 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login if reward.Timestamp > now-dore.Interval { dore.Reward += reward.Reward dore.Blocks++ - dore.ESum += personalEffort // Hier wird personalEffort verwendet + dore.ESum += reward.PersonalEffort dore.Count++ dore.Effort = dore.ESum / dore.Count } } } - stats["sumrewards"] = dorew stats["24hreward"] = dorew[2].Reward + var miningTypeSolo = false + var miningTypePplns = false + miningType := r.GetMiningType(login) + if miningType == "solo" { + miningTypeSolo = true + } else { + miningTypePplns = true + } + stats["miningTypeSolo"] = miningTypeSolo + stats["miningTypePplns"] = miningTypePplns + result1, err := cmds[5].(*redis.StringStringMapCmd).Result() + + if err != nil { + log.Fatalf("Error while geting Exchange Data => Error : %v", err) + } + stats["exchangedata"] = convertStringMap(result1) return stats, nil } @@ -1503,6 +1466,7 @@ func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, e } return total, sharesDiff, uncles, orphans } + for _, max := range windows { total, sharesDiff, uncleRate, orphanRate := calcLuck(max) row := map[string]float64{ @@ -1513,47 +1477,38 @@ func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, e break } } + return stats, nil } -func (r *RedisClient) CollectLuckCharts(max int) (stats []*LuckCharts, err error) { - var result []*LuckCharts - tx := r.client.Multi() - defer tx.Close() - - cmds, err := tx.Exec(func() error { - tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, int64(max-1)) - return nil - }) - if err != nil { - return result, err - } - blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd)) - - for i, block := range blocks { - if i > (max - 1) { - break +func convertCandidateResultsByLogin(login string, raw *redis.ZSliceCmd) []*BlockData { + var result []*BlockData + for _, v := range raw.Val() { + // "nonce:powHash:mixDigest:timestamp:diff:totalShares" + block := BlockData{} + block.Height = int64(v.Score) + block.RoundHeight = block.Height + fields := strings.Split(v.Member.(string), ":") + if fields[6] == login { + block.Nonce = fields[0] + block.PowHash = fields[1] + block.MixDigest = fields[2] + block.Timestamp, _ = strconv.ParseInt(fields[3], 10, 64) + block.Difficulty, _ = strconv.ParseInt(fields[4], 10, 64) + block.TotalShares, _ = strconv.ParseInt(fields[5], 10, 64) + block.Finder = fields[6] + block.Worker = fields[7] + block.MiningType = fields[8] + block.candidateKey = v.Member.(string) + block.ShareDiffCalc, _ = strconv.ParseInt(fields[9], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[10], 10, 64) + result = append(result, &block) } - lc := LuckCharts{} - var sharesDiff = float64(block.TotalShares) / float64(block.Difficulty) - lc.Timestamp = block.Timestamp - lc.Height = block.RoundHeight - lc.Difficulty = block.Difficulty - lc.Shares = block.TotalShares - lc.SharesDiff = sharesDiff - lc.Reward = block.RewardString - result = append(result, &lc) + } - sort.Sort(TimestampSorter(result)) - return result, nil + return result } -type TimestampSorter []*LuckCharts - -func (a TimestampSorter) Len() int { return len(a) } -func (a TimestampSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a TimestampSorter) Less(i, j int) bool { return a[i].Timestamp < a[j].Timestamp } - func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData { var result []*BlockData for _, v := range raw.Val() { @@ -1569,10 +1524,11 @@ func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData { block.Difficulty, _ = strconv.ParseInt(fields[4], 10, 64) block.TotalShares, _ = strconv.ParseInt(fields[5], 10, 64) block.Finder = fields[6] - block.ShareDiffCalc, _ = strconv.ParseInt(fields[7], 10, 64) - block.PersonalShares, _ = strconv.ParseInt(fields[9], 10, 64) - block.Worker = fields[8] + block.Worker = fields[7] + block.MiningType = fields[8] block.candidateKey = v.Member.(string) + block.ShareDiffCalc, _ = strconv.ParseInt(fields[9], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[10], 10, 64) result = append(result, &block) } return result @@ -1603,6 +1559,43 @@ func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { return result } +func convertBlockResultsByLogin(login string, rows ...*redis.ZSliceCmd) []*BlockData { + var result []*BlockData + for _, row := range rows { + for _, v := range row.Val() { + // "uncleHeight:orphan:nonce:blockHash:timestamp:diff:totalShares:rewardInWei" + block := BlockData{} + block.Height = int64(v.Score) + block.RoundHeight = block.Height + fields := strings.Split(v.Member.(string), ":") + if fields[7] == login { + block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64) + block.Uncle = block.UncleHeight > 0 + block.Orphan, _ = strconv.ParseBool(fields[1]) + block.Nonce = fields[2] + block.Hash = fields[3] + block.Timestamp, _ = strconv.ParseInt(fields[4], 10, 64) + block.Difficulty, _ = strconv.ParseInt(fields[5], 10, 64) + block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64) + + block.Finder = fields[7] + + block.RewardString = fields[8] + block.ImmatureReward = fields[8] + block.Worker = fields[9] + + block.MiningType = fields[10] + block.immatureKey = v.Member.(string) + block.ShareDiffCalc, _ = strconv.ParseInt(fields[11], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[12], 10, 64) + result = append(result, &block) + } + + } + } + return result +} + func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { var result []*BlockData for _, row := range rows { @@ -1620,13 +1613,17 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { block.Timestamp, _ = strconv.ParseInt(fields[4], 10, 64) block.Difficulty, _ = strconv.ParseInt(fields[5], 10, 64) block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64) - block.RewardString = fields[7] - block.ImmatureReward = fields[7] - block.Finder = fields[8] - block.ShareDiffCalc, _ = strconv.ParseInt(fields[9], 10, 64) - block.PersonalShares, _ = strconv.ParseInt(fields[11], 10, 64) - block.Worker = fields[10] + + block.Finder = fields[7] + + block.RewardString = fields[8] + block.ImmatureReward = fields[8] + block.Worker = fields[9] + + block.MiningType = fields[10] block.immatureKey = v.Member.(string) + block.ShareDiffCalc, _ = strconv.ParseInt(fields[11], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[12], 10, 64) result = append(result, &block) } } @@ -1650,12 +1647,15 @@ func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSlic for _, v := range raw.Val() { parts := strings.Split(v.Member.(string), ":") share, _ := strconv.ParseInt(parts[0], 10, 64) + + //By Mohannad var hostname string if len(parts) > 3 { - hostname = parts[3] + hostname = parts[4] } else { hostname = "unknown" } + id := parts[1] score := int64(v.Score) worker := workers[id] @@ -1689,8 +1689,30 @@ func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSlic } return workers } +func getTotalWorkers(window int64, raw *redis.ZSliceCmd) int { + var result []string + + for _, v := range raw.Val() { + parts := strings.Split(v.Member.(string), ":") + id := parts[2] + result = append(result, id) + } + return len(unique(result)) +} + +func unique(intSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range intSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} -func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]Miner) { +func convertMinersStats(window int64, raw *redis.ZSliceCmd, r *RedisClient) (int64, map[string]Miner) { now := util.MakeTimestamp() / 1000 miners := make(map[string]Miner) totalHashrate := int64(0) @@ -1702,7 +1724,11 @@ func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]M score := int64(v.Score) miner := miners[id] miner.HR += share - + if r.GetMiningType(id) != "solo" { + miner.Solo = false + } else { + miner.Solo = true + } if miner.LastBeat < score { miner.LastBeat = score } @@ -1741,11 +1767,13 @@ func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { fields := strings.Split(v.Member.(string), ":") tx["tx"] = fields[0] // Individual or whole payments row - if len(fields) < 3 { + if len(fields) < 4 { tx["amount"], _ = strconv.ParseInt(fields[1], 10, 64) + tx["txcost"], _ = strconv.ParseInt(fields[2], 10, 64) } else { tx["address"] = fields[1] tx["amount"], _ = strconv.ParseInt(fields[2], 10, 64) + tx["txcost"], _ = strconv.ParseInt(fields[3], 10, 64) } result = append(result, tx) } @@ -1753,53 +1781,403 @@ func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { for i := len(result) - 1; i >= 0; i-- { reverse = append(reverse, result[i]) } - return result + return result +} + +func (r *RedisClient) GetIP(login string) string { + login = strings.ToLower(login) + cmd := r.client.HGet(r.formatKey("settings", login), "ip_address") + if cmd.Err() == redis.Nil { + return "NA" + } else if cmd.Err() != nil { + return "NA" + } + return cmd.Val() +} + +func (r *RedisClient) GetPassword(login string) string { + login = strings.ToLower(login) + cmd := r.client.HGet(r.formatKey("settings", login), "password") + if cmd.Err() == redis.Nil { + return "NA" + } else if cmd.Err() != nil { + return "NA" + } + return cmd.Val() +} + +func (r *RedisClient) SetIP(login string, ip string) { + login = strings.ToLower(login) + r.client.HSet(r.formatKey("settings", login), "ip_address", ip) +} + +func (r *RedisClient) SetMailAddress(login string, email string) (bool, error) { + login = strings.ToLower(login) + cmd, err := r.client.HSet(r.formatKey("settings", login), "email", email).Result() + return cmd, err +} + +func (r *RedisClient) SetAlert(login string, alert string) (bool, error) { + login = strings.ToLower(login) + cmd, err := r.client.HSet(r.formatKey("settings", login), "alert", alert).Result() + return cmd, err +} + +func (r *RedisClient) SetMiningType(login string, miningType string) { + login = strings.ToLower(login) + r.client.HSet(r.formatKey("settings", login), "miningType", miningType) +} + +func (r *RedisClient) WritePasswordByMiner(login string, password string) { + login = strings.ToLower(login) + r.client.HSet(r.formatKey("settings", login), "password", password) +} + +func (r *RedisClient) StoreExchangeData(ExchangeData []map[string]interface{}) { + + tx := r.client.Multi() + defer tx.Close() + + log.Printf("ExchangeData: %s", ExchangeData) + + for _, coindata := range ExchangeData { + for key, value := range coindata { + + cmd := tx.HSet(r.formatKey("exchange", coindata["symbol"]), fmt.Sprintf("%v", key), fmt.Sprintf("%v", value)) + err := cmd.Err() + if err != nil { + log.Printf("Error while Storing %s : Key-%s , value-%s , Error : %v", coindata["symbol"], key, value, err) + } + + } + } + log.Printf("Writing Exchange Data ") + return +} + +func (r *RedisClient) GetExchangeData(coinsymbol string) (map[string]string, error) { + + cmd := r.client.HGetAllMap(r.formatKey("exchange", coinsymbol)) + + result, err := cmd.Result() + + if err != nil { + return nil, err + } + + return result, err +} + +func (r *RedisClient) WritePoolCharts(time1 int64, time2 string, poolHash string) error { + s := join(time1, time2, poolHash) + cmd := r.client.ZAdd(r.formatKey("charts", "pool"), redis.Z{Score: float64(time1), Member: s}) + return cmd.Err() +} +func (r *RedisClient) WriteMinerCharts(time1 int64, time2, k string, hash, largeHash, workerOnline int64) error { + s := join(time1, time2, hash, largeHash, workerOnline) + cmd := r.client.ZAdd(r.formatKey("charts", "miner", k), redis.Z{Score: float64(time1), Member: s}) + return cmd.Err() +} + +func (r *RedisClient) GetPoolCharts(poolHashLen int64) (stats []*PoolCharts, err error) { + tx := r.client.Multi() + defer tx.Close() + now := util.MakeTimestamp() / 1000 + cmds, err := tx.Exec(func() error { + tx.ZRemRangeByScore(r.formatKey("charts", "pool"), "-inf", fmt.Sprint("(", now-172800)) + tx.ZRevRangeWithScores(r.formatKey("charts", "pool"), 0, poolHashLen) + return nil + }) + if err != nil { + return nil, err + } + stats = convertPoolChartsResults(cmds[1].(*redis.ZSliceCmd)) + return stats, nil +} + +func convertPoolChartsResults(raw *redis.ZSliceCmd) []*PoolCharts { + var result []*PoolCharts + for _, v := range raw.Val() { + // "Timestamp:TimeFormat:Hash" + pc := PoolCharts{} + pc.Timestamp = int64(v.Score) + str := v.Member.(string) + pc.TimeFormat = str[strings.Index(str, ":")+1 : strings.LastIndex(str, ":")] + pc.PoolHash, _ = strconv.ParseInt(str[strings.LastIndex(str, ":")+1:], 10, 64) + result = append(result, &pc) + } + var reverse []*PoolCharts + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return reverse +} + +func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { + var result []*MinerCharts + for _, v := range raw.Val() { + // "Timestamp:TimeFormat:Hash:largeHash:workerOnline" + mc := MinerCharts{} + mc.Timestamp = int64(v.Score) + str := v.Member.(string) + mc.TimeFormat = strings.Split(str, ":")[1] + mc.MinerHash, _ = strconv.ParseInt(strings.Split(str, ":")[2], 10, 64) + mc.MinerLargeHash, _ = strconv.ParseInt(strings.Split(str, ":")[3], 10, 64) + mc.WorkerOnline = strings.Split(str, ":")[4] + result = append(result, &mc) + } + var reverse []*MinerCharts + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return reverse +} + +func (r *RedisClient) GetMiningType(login string) string { + login = strings.ToLower(login) + cmd := r.client.HGet(r.formatKey("settings", login), "miningType") + //log.Println("MINING TYPE for : ", login) + if cmd.Err() == redis.Nil { + // log.Println("MINING TYPE RETURN pplns") + return "pplns" + } else if cmd.Err() != nil { + //log.Println("MINING TYPE RETURN NA") + return "NA" + } + //log.Println("MINING TYPE RETURN : ", cmd.Val()) + return cmd.Val() +} + +func (r *RedisClient) GetMinerCharts(hashNum int64, login string) (stats []*MinerCharts, err error) { + + tx := r.client.Multi() + defer tx.Close() + now := util.MakeTimestamp() / 1000 + cmds, err := tx.Exec(func() error { + tx.ZRemRangeByScore(r.formatKey("charts", "miner", login), "-inf", fmt.Sprint("(", now-172800)) + tx.ZRevRangeWithScores(r.formatKey("charts", "miner", login), 0, hashNum) + return nil + }) + if err != nil { + return nil, err + } + stats = convertMinerChartsResults(cmds[1].(*redis.ZSliceCmd)) + return stats, nil +} + +// DeleteOldMinerData deletes old miner data from the "miner" ZSets. +// All entries that are older than 24 hours are removed. +func (r *RedisClient) DeleteOldMinerData() error { + now := time.Now() + pastTime := now.Add(-24 * time.Hour) + + // Retrieve all keys matching the pattern "charts:miner:*" + loginKeys, err := r.client.Keys(r.formatKey("charts", "miner", "*")).Result() + if err != nil { + return err + } + + // Iterate through all found keys and remove the old entries + for _, loginKey := range loginKeys { + _, err := r.client.ZRemRangeByScore(loginKey, "-inf", fmt.Sprintf("(%d", pastTime.Unix())).Result() + if err != nil { + return err + } + } + + return nil +} + +// DeleteOldShareData deletes old share data from the "share" ZSets. +// All entries that are older than 24 hours are removed. +func (r *RedisClient) DeleteOldShareData() error { + now := time.Now() + pastTime := now.Add(-24 * time.Hour) + + // Retrieve all keys matching the pattern "charts:share:*" + shareKeys, err := r.client.Keys(r.formatKey("charts", "share", "*")).Result() + if err != nil { + return err + } + + // Iterate through all found keys and remove the old entries + for _, shareKey := range shareKeys { + _, err := r.client.ZRemRangeByScore(shareKey, "-inf", fmt.Sprintf("(%d", pastTime.Unix())).Result() + if err != nil { + return err + } + } + + return nil +} + +func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { + login = strings.ToLower(login) + tx := r.client.Multi() + defer tx.Close() + cmds, err := tx.Exec(func() error { + tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, 360) + return nil + }) + if err != nil { + return nil, err + } + stats = convertPaymentChartsResults(cmds[0].(*redis.ZSliceCmd)) + //fmt.Println(stats) + return stats, nil + +} + +func convertPaymentChartsResults(raw *redis.ZSliceCmd) []*PaymentCharts { + var result []*PaymentCharts + for _, v := range raw.Val() { + pc := PaymentCharts{} + pc.Timestamp = int64(v.Score) + tm := time.Unix(pc.Timestamp, 0) + pc.TimeFormat = tm.Format("2006-01-02") + " 00_00" + fields := strings.Split(v.Member.(string), ":") + pc.Amount, _ = strconv.ParseInt(fields[1], 10, 64) + //fmt.Printf("%d : %s : %d \n", pc.Timestamp, pc.TimeFormat, pc.Amount) + var chkAppend bool + for _, pcc := range result { + if pcc.TimeFormat == pc.TimeFormat { + pcc.Amount += pc.Amount + chkAppend = true + } + } + if !chkAppend { + pc.Timestamp -= int64(math.Mod(float64(v.Score), float64(86400))) + result = append(result, &pc) + } + } + var reverse []*PaymentCharts + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return reverse +} + +func send(body string, to string) { + + from := "" // "office.poolnode@gmail.com" + pass := "" // "pass" + + msg := "From: " + from + "\n" + + " To: " + to + "\n" + + "Subject: Worker is down\n\n" + + body + + err := smtp.SendMail("", // "smtp.gmail.com:587" + smtp.PlainAuth("", from, pass, ""), // "smtp.gmail.com" + from, []string{to}, []byte(msg)) + + if err != nil { + fmt.Errorf("smtp error: %s", err) + return + } + + fmt.Sprint("sent") +} + +func (r *RedisClient) GetWorker(login string, worker string) string { + cmd := r.client.HGet(r.formatKey("settings", login), worker) + if cmd.Err() == redis.Nil { + return "NA" + } else if cmd.Err() != nil { + return "NA" + } + return cmd.Val() +} + +func (r *RedisClient) GetMailAddress(login string) string { + cmd := r.client.HGet(r.formatKey("settings", login), "email") + if cmd.Err() == redis.Nil { + return "NA" + } else if cmd.Err() != nil { + return "NA" + } + return cmd.Val() +} + +func (r *RedisClient) SetWorkerWithEmailStatus(login string, worker string, emailSent string) { + r.client.HSet(r.formatKey("settings", login), worker, emailSent) +} + +func (r *RedisClient) GetAllMinerAccount() (account []string, err error) { + var c int64 + for { + now := util.MakeTimestamp() / 1000 + var str = r.formatKey("miners", "*") + // c, keys, err := r.client.Scan(c, str, now).Result() + c, keys, err := r.client.Scan(c, str, now).Result() + if err != nil { + return account, err + } + for _, key := range keys { + m := strings.Split(key, ":") + //if ( len(m) >= 2 && strings.Index(strings.ToLower(m[2]), "0x") == 0) { + if len(m) >= 2 { + account = append(account, m[2]) + } + } + if c == 0 { + break + } + } + return account, nil +} + +func convertFindersResults(raw *redis.ZSliceCmd) []map[string]interface{} { + var result []map[string]interface{} + for _, v := range raw.Val() { + miner := make(map[string]interface{}) + miner["blocks"] = int64(v.Score) + miner["address"] = v.Member.(string) + result = append(result, miner) + } + return result +} + +func (r *RedisClient) CollectLuckCharts(max int) (stats []*LuckCharts, err error) { + var result []*LuckCharts + tx := r.client.Multi() + defer tx.Close() + + cmds, err := tx.Exec(func() error { + tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, int64(max-1)) + return nil + }) + if err != nil { + return result, err + } + blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd)) + + for i, block := range blocks { + if i > (max - 1) { + break + } + lc := LuckCharts{} + var sharesDiff = float64(block.TotalShares) / float64(block.Difficulty) + lc.Timestamp = block.Timestamp + lc.Height = block.RoundHeight + lc.Difficulty = block.Difficulty + lc.Shares = block.TotalShares + lc.SharesDiff = sharesDiff + lc.Reward = block.RewardString + result = append(result, &lc) + } + sort.Sort(TimestampSorter(result)) + return result, nil } -func convertFindersResults(raw *redis.ZSliceCmd) []map[string]interface{} { - var result []map[string]interface{} - for _, v := range raw.Val() { - miner := make(map[string]interface{}) - miner["blocks"] = int64(v.Score) - miner["address"] = v.Member.(string) - result = append(result, miner) - } - return result -} +type TimestampSorter []*LuckCharts -/* -Timestamp int64 `json:"x"` -TimeFormat string `json:"timeFormat"` -Amount int64 `json:"amount"` -*/ -func convertPaymentChartsResults(raw *redis.ZSliceCmd) []*PaymentCharts { - var result []*PaymentCharts - for _, v := range raw.Val() { - pc := PaymentCharts{} - pc.Timestamp = int64(v.Score) - tm := time.Unix(pc.Timestamp, 0) - pc.TimeFormat = tm.Format("2006-01-02") + " 00_00" - fields := strings.Split(v.Member.(string), ":") - pc.Amount, _ = strconv.ParseInt(fields[1], 10, 64) - //fmt.Printf("%d : %s : %d \n", pc.Timestamp, pc.TimeFormat, pc.Amount) +func (a TimestampSorter) Len() int { return len(a) } +func (a TimestampSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a TimestampSorter) Less(i, j int) bool { return a[i].Timestamp < a[j].Timestamp } - var chkAppend bool - for _, pcc := range result { - if pcc.TimeFormat == pc.TimeFormat { - pcc.Amount += pc.Amount - chkAppend = true - } - } - if !chkAppend { - pc.Timestamp -= int64(math.Mod(float64(v.Score), float64(86400))) - result = append(result, &pc) - } - } - var reverse []*PaymentCharts - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } - return reverse +func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) { + r.client.ZAdd(r.formatKey("worker", "blocks", login), redis.Z{Score: float64(ts), Member: join(diff, share, id, ms)}) } func (r *RedisClient) GetCurrentHashrate(login string) (int64, error) { @@ -1846,17 +2224,12 @@ func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, in } -var deletionLock sync.Mutex -var deletionDone bool - -// WriteWorkerShareStatus updates the worker's share status in Redis. -// It takes the worker's login, ID, and status flags for valid, stale, and invalid shares. +// lets try to fuck without understanding and see if it works func (r *RedisClient) WriteWorkerShareStatus(login string, id string, valid bool, stale bool, invalid bool) { + valid_int := 0 stale_int := 0 invalid_int := 0 - - // Convert boolean flags to integer values. if valid { valid_int = 1 } @@ -1867,42 +2240,38 @@ func (r *RedisClient) WriteWorkerShareStatus(login string, id string, valid bool invalid_int = 1 } + // var after = time.Now().AddDate(0, 0, -1).Unix() + // var now = time.Now().Unix() + // if(now >= after){ + // tx.HDel(r.formatKey("minerShare", login, id)) + // } t := time.Now().Local() - formattedTime := t.Format("15:04:05") // Time in 24-hour format - - if formattedTime >= "23:59:00" && !deletionDone { - // Lock to ensure only one deletion occurs. - deletionLock.Lock() - defer deletionLock.Unlock() - - if !deletionDone { - tx := r.client.Multi() - defer tx.Close() - - // Reset share status to 0 for the next day. - tx.Exec(func() error { - tx.HSet(r.formatKey("minerShare", login, id), "valid", strconv.FormatInt(0, 10)) - tx.HSet(r.formatKey("minerShare", login, id), "stale", strconv.FormatInt(0, 10)) - tx.HSet(r.formatKey("minerShare", login, id), "invalid", strconv.FormatInt(0, 10)) - return nil - }) - - deletionDone = true - } + if t.Format("15:04:05") >= "23:59:00" { + tx := r.client.Multi() + defer tx.Close() + tx.Exec(func() error { + //tx.Del(r.formatKey("minerShare", login, id)) + tx.HSet(r.formatKey("minerShare", login, id), "valid", strconv.FormatInt(0, 10)) + tx.HSet(r.formatKey("minerShare", login, id), "stale", strconv.FormatInt(0, 10)) + tx.HSet(r.formatKey("minerShare", login, id), "invalid", strconv.FormatInt(0, 10)) + return nil + }) } else { + // So, we need to initiate the tx object tx := r.client.Multi() defer tx.Close() - // Increment share counts. tx.Exec(func() error { + // OK, good, no need to read reset and add if i use Hset and HGet shit tx.HIncrBy(r.formatKey("minerShare", login, id), "valid", int64(valid_int)) tx.HIncrBy(r.formatKey("minerShare", login, id), "stale", int64(stale_int)) tx.HIncrBy(r.formatKey("minerShare", login, id), "invalid", int64(invalid_int)) tx.HIncrBy(r.formatKey("chartsNum", "share", login), "valid", int64(valid_int)) - tx.HIncrBy(r.formatKey("chartsNum", "share", login), "stale", int64(stale_int)) + tx.HIncrBy(r.formatKey("chartsNum", "share", login), "stale", int64(stale_int)) // Would that work? + return nil }) - } + } //end else } func (r *RedisClient) NumberStratumWorker(count int) { @@ -1917,144 +2286,132 @@ func (r *RedisClient) NumberStratumWorker(count int) { }) } -func (r *RedisClient) StoreExchangeData(ExchangeData []map[string]interface{}) { - tx := r.client.Multi() - defer tx.Close() - - log.Printf("ExchangeData: %s", ExchangeData) - - for _, coindata := range ExchangeData { - for key, value := range coindata { - cmd := tx.HSet(r.formatKey("exchange", coindata["symbol"].(string)), fmt.Sprintf("%v", key), fmt.Sprintf("%v", value)) - err := cmd.Err() - if err != nil { - log.Printf("Error while Storing %s : Key-%s , value-%s , Error : %v", coindata["symbol"].(string), key, value, err) - } - } - } - log.Printf("Writing Exchange Data ") - return +func (r *RedisClient) WriteDiffCharts(time1 int64, time2 string, netHash string) error { + s := join(time1, time2, netHash) + cmd := r.client.ZAdd(r.formatKey("charts", "difficulty"), redis.Z{Score: float64(time1), Member: s}) + return cmd.Err() } -func (r *RedisClient) GetExchangeData(coinsymbol string) (map[string]string, error) { - cmd := r.client.HGetAllMap(r.formatKey("exchange", coinsymbol)) - result, err := cmd.Result() - if err != nil { - return nil, err - } - return result, err -} +func (r *RedisClient) WriteShareCharts(time1 int64, time2, login string, valid, stale, workerOnline int64) error { + valid_s := r.client.HGet(r.formatKey("chartsNum", "share", login), "valid") + stale_s := r.client.HGet(r.formatKey("chartsNum", "share", login), "stale") -func (r *RedisClient) GetThreshold(login string) (int64, error) { - cmd := r.client.HGet(r.formatKey("settings", login), "payoutthreshold") - if cmd.Err() == redis.Nil { - return 500000000, nil - } else if cmd.Err() != nil { - log.Println("GetThreshold error :", cmd.Err()) - return 500000000, cmd.Err() + if valid_s.Err() == redis.Nil || stale_s.Err() == redis.Nil { + r.client.HSet(r.formatKey("chartsNum", "share", login), "valid", strconv.FormatInt(0, 10)) + r.client.HSet(r.formatKey("chartsNum", "share", login), "stale", strconv.FormatInt(0, 10)) + //return nil, nil + } else if valid_s.Err() != nil || stale_s.Err() != nil { + r.client.HSet(r.formatKey("chartsNum", "share", login), "valid", strconv.FormatInt(0, 10)) + r.client.HSet(r.formatKey("chartsNum", "share", login), "stale", strconv.FormatInt(0, 10)) + //return nil, valid_s.Err() } - return cmd.Int64() -} - -func (r *RedisClient) SetThreshold(login string, threshold int64) (bool, error) { - cmd, err := r.client.HSet(r.formatKey("settings", login), "payoutthreshold", strconv.FormatInt(threshold, 10)).Result() - return cmd, err -} -func (r *RedisClient) LogIP(login string, ip string) { - login = strings.ToLower(login) - r.client.HSet(r.formatKey("settings", login), "ip_address", ip) - r.client.HSet(r.formatKey("settings", login), "status", "online") - r.client.HSet(r.formatKey("settings", login), "email_sent", "0") - - ms := util.MakeTimestamp() - ts := ms / 1000 - r.client.HSet(r.formatKey("settings", login), "ip_time", strconv.FormatInt(ts, 10)) -} - -func (r *RedisClient) GetIP(login string) string { - login = strings.ToLower(login) - cmd := r.client.HGet(r.formatKey("settings", login), "ip_address") + v_s, _ := valid_s.Int64() + s_s, _ := stale_s.Int64() - log.Printf("Getting IP for login %s. Result: %v", login, cmd.Val()) + l_valid := r.client.HGet(r.formatKey("chartsNum", "share", login), "lastvalid") + l_stale := r.client.HGet(r.formatKey("chartsNum", "share", login), "laststale") - if cmd.Err() == redis.Nil { - log.Printf("IP not found for login: %s", login) - return "NA" - } else if cmd.Err() != nil { - log.Printf("Error retrieving IP for login %s: %v", login, cmd.Err()) - return "NA" + if l_valid.Err() == redis.Nil || l_stale.Err() == redis.Nil { + r.client.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(0, 10)) + r.client.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(0, 10)) + //return nil, nil + } else if l_valid.Err() != nil || l_stale.Err() != nil { + r.client.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(0, 10)) + r.client.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(0, 10)) + //return nil, l_valid.Err() } + l_v, _ := l_valid.Int64() + l_s, _ := l_stale.Int64() - return cmd.Val() -} - -func (r *RedisClient) SetIP(login string, ip string) { - login = strings.ToLower(login) - key := r.formatKey("settings", login) // Überprüfen Sie das genaue Format des Schlüssels - - log.Printf("Setting IP address %s for login %s", ip, login) - - cmd := r.client.HSet(key, "ip_address", ip) - if cmd.Err() != nil { - log.Printf("Error setting IP address for login %s: %v", login, cmd.Err()) - } -} + valid_c := v_s - l_v + stale_c := s_s - l_s + s := join(time1, time2, valid_c, stale_c, workerOnline) + cmd := r.client.ZAdd(r.formatKey("charts", "share", login), redis.Z{Score: float64(time1), Member: s}) -func (r *RedisClient) SetMailAddress(login string, email string) (bool, error) { - login = strings.ToLower(login) - cmd, err := r.client.HSet(r.formatKey("settings", login), "email", email).Result() - return cmd, err + tx := r.client.Multi() + defer tx.Close() + tx.Exec(func() error { + tx.HSet(r.formatKey("chartsNum", "share", login), "lastvalid", strconv.FormatInt(v_s, 10)) + tx.HSet(r.formatKey("chartsNum", "share", login), "laststale", strconv.FormatInt(s_s, 10)) + return nil + }) + return cmd.Err() } -func send(body string, to string) { +func (r *RedisClient) GetNetCharts(netHashLen int64) (stats []*NetCharts, err error) { - from := "" // "office.poolnode@gmail.com" - pass := "" // "pass" + tx := r.client.Multi() + defer tx.Close() - msg := "From: " + from + "\n" + - " To: " + to + "\n" + - "Subject: Worker is down\n\n" + - body + now := util.MakeTimestamp() / 1000 - err := smtp.SendMail("", // "smtp.gmail.com:587" - smtp.PlainAuth("", from, pass, ""), // "smtp.gmail.com" - from, []string{to}, []byte(msg)) + cmds, err := tx.Exec(func() error { + tx.ZRemRangeByScore(r.formatKey("charts", "difficulty"), "-inf", fmt.Sprint("(", now-172800)) + tx.ZRevRangeWithScores(r.formatKey("charts", "difficulty"), 0, netHashLen) + return nil + }) if err != nil { - fmt.Errorf("smtp error: %s", err) - return + return nil, err } - fmt.Sprint("sent") + stats = convertNetChartsResults(cmds[1].(*redis.ZSliceCmd)) + return stats, nil } -func (r *RedisClient) GetMailAddress(login string) string { - cmd := r.client.HGet(r.formatKey("settings", login), "email") - if cmd.Err() == redis.Nil { - return "NA" - } else if cmd.Err() != nil { - return "NA" +func convertNetChartsResults(raw *redis.ZSliceCmd) []*NetCharts { + var result []*NetCharts + for _, v := range raw.Val() { + // "Timestamp:TimeFormat:Hash" + pc := NetCharts{} + pc.Timestamp = int64(v.Score) + str := v.Member.(string) + pc.TimeFormat = str[strings.Index(str, ":")+1 : strings.LastIndex(str, ":")] + pc.NetHash, _ = strconv.ParseInt(str[strings.LastIndex(str, ":")+1:], 10, 64) + result = append(result, &pc) } - return cmd.Val() -} -func (r *RedisClient) SetWorkerWithEmailStatus(login string, worker string, emailSent string) { - r.client.HSet(r.formatKey("settings", login), worker, emailSent) + var reverse []*NetCharts + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return reverse } -func (r *RedisClient) GetWorker(login string, worker string) string { - cmd := r.client.HGet(r.formatKey("settings", login), worker) - if cmd.Err() == redis.Nil { - return "NA" - } else if cmd.Err() != nil { - return "NA" +func (r *RedisClient) GetShareCharts(shareNum int64, login string) (stats []*ShareCharts, err error) { + + tx := r.client.Multi() + defer tx.Close() + now := util.MakeTimestamp() / 1000 + cmds, err := tx.Exec(func() error { + tx.ZRemRangeByScore(r.formatKey("charts", "share", login), "-inf", fmt.Sprint("(", now-172800)) + tx.ZRevRangeWithScores(r.formatKey("charts", "share", login), 0, shareNum) + return nil + }) + if err != nil { + return nil, err } - return cmd.Val() + stats = convertShareChartsResults(cmds[1].(*redis.ZSliceCmd)) + return stats, nil } -func (r *RedisClient) SetAlert(login string, alert string) (bool, error) { - login = strings.ToLower(login) - cmd, err := r.client.HSet(r.formatKey("settings", login), "alert", alert).Result() - return cmd, err +func convertShareChartsResults(raw *redis.ZSliceCmd) []*ShareCharts { + var result []*ShareCharts + for _, v := range raw.Val() { + + mc := ShareCharts{} + mc.Timestamp = int64(v.Score) + str := v.Member.(string) + mc.TimeFormat = strings.Split(str, ":")[1] + mc.Valid, _ = strconv.ParseInt(strings.Split(str, ":")[2], 10, 64) + mc.Stale, _ = strconv.ParseInt(strings.Split(str, ":")[3], 10, 64) + mc.WorkerOnline = strings.Split(str, ":")[4] + result = append(result, &mc) + } + var reverse []*ShareCharts + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return reverse } diff --git a/storage/redis_test.go b/storage/redis_test.go new file mode 100644 index 00000000..2383ed47 --- /dev/null +++ b/storage/redis_test.go @@ -0,0 +1,591 @@ +package storage + +import ( + "os" + "reflect" + "strconv" + "testing" + + "gopkg.in/redis.v3" + + "log" +) + +var r *RedisClient + +const prefix = "test" + +func TestMain(m *testing.M) { + r = NewRedisClient(&Config{Endpoint: "35.187.240.179:6379", Database: 10}, prefix, 1000000, "UBIQ") + reset() + c := m.Run() + reset() + os.Exit(c) +} + +func TestWriteShareCheckExist(t *testing.T) { + reset() + + exist, _ := r.WriteShare("x", "x", []string{"0x0", "0x0", "0x0"}, 10, 1008, 0) + if exist { + t.Error("PoW must not exist") + } + exist, _ = r.WriteShare("x", "x", []string{"0x0", "0x1", "0x0"}, 10, 1008, 0) + if exist { + t.Error("PoW must not exist") + } + exist, _ = r.WriteShare("x", "x", []string{"0x0", "0x0", "0x1"}, 100, 1010, 0) + if exist { + t.Error("PoW must not exist") + } + exist, _ = r.WriteShare("z", "x", []string{"0x0", "0x0", "0x1"}, 100, 1016, 0) + if !exist { + t.Error("PoW must exist") + } + exist, _ = r.WriteShare("x", "x", []string{"0x0", "0x0", "0x1"}, 100, 1025, 0) + if exist { + t.Error("PoW must not exist") + } +} + +func TestGetPayees(t *testing.T) { + reset() + + n := 256 + for i := 0; i < n; i++ { + r.client.HSet(r.formatKey("miners", strconv.Itoa(i)), "balance", strconv.Itoa(i)) + } + + var payees []string + payees, _ = r.GetPayees() + if len(payees) != n { + t.Error("Must return all payees") + } + m := make(map[string]struct{}) + for _, v := range payees { + m[v] = struct{}{} + } + if len(m) != n { + t.Error("Must be unique list") + } +} + +func TestGetBalance(t *testing.T) { + reset() + + r.client.HSet(r.formatKey("miners:x"), "balance", "750") + + v, _ := r.GetBalance("x") + if v != 750 { + t.Error("Must return balance") + } + + v, err := r.GetBalance("z") + if v != 0 { + t.Error("Must return 0 if account does not exist") + } + if err != nil { + t.Error("Must not return error if account does not exist") + } +} + +func TestLockPayouts(t *testing.T) { + reset() + + r.LockPayouts("x", 1000) + v := r.client.Get("test:payments:lock").Val() + if v != "x:1000" { + t.Errorf("Invalid lock amount: %v", v) + } + + err := r.LockPayouts("x", 100) + if err == nil { + t.Errorf("Must not overwrite lock") + } +} + +func TestUnlockPayouts(t *testing.T) { + reset() + + r.client.Set(r.formatKey("payments:lock"), "x:1000", 0) + + r.UnlockPayouts() + err := r.client.Get(r.formatKey("payments:lock")).Err() + if err != redis.Nil { + t.Errorf("Must release lock") + } +} + +func TestIsPayoutsLocked(t *testing.T) { + reset() + + r.LockPayouts("x", 1000) + if locked, _ := r.IsPayoutsLocked(); !locked { + t.Errorf("Payouts must be locked") + } +} + +func TestUpdateBalance(t *testing.T) { + reset() + + r.client.HMSetMap( + r.formatKey("miners:x"), + map[string]string{"paid": "50", "balance": "1000"}, + ) + r.client.HMSetMap( + r.formatKey("finances"), + map[string]string{"paid": "500", "balance": "10000"}, + ) + + amount := int64(250) + r.UpdateBalance("x", amount) + result := r.client.HGetAllMap(r.formatKey("miners:x")).Val() + if result["pending"] != "250" { + t.Error("Must set pending amount") + } + if result["balance"] != "750" { + t.Error("Must deduct balance") + } + if result["paid"] != "50" { + t.Error("Must not touch paid") + } + + result = r.client.HGetAllMap(r.formatKey("finances")).Val() + if result["pending"] != "250" { + t.Error("Must set pool pending amount") + } + if result["balance"] != "9750" { + t.Error("Must deduct pool balance") + } + if result["paid"] != "500" { + t.Error("Must not touch pool paid") + } + + rank := r.client.ZRank(r.formatKey("payments:pending"), join("x", amount)).Val() + if rank != 0 { + t.Error("Must add pending payment") + } +} + +func TestRollbackBalance(t *testing.T) { + reset() + + r.client.HMSetMap( + r.formatKey("miners:x"), + map[string]string{"paid": "100", "balance": "750", "pending": "250"}, + ) + r.client.HMSetMap( + r.formatKey("finances"), + map[string]string{"paid": "500", "balance": "10000", "pending": "250"}, + ) + r.client.ZAdd(r.formatKey("payments:pending"), redis.Z{Score: 1, Member: "xx"}) + + amount := int64(250) + r.RollbackBalance("x", amount) + result := r.client.HGetAllMap(r.formatKey("miners:x")).Val() + if result["paid"] != "100" { + t.Error("Must not touch paid") + } + if result["balance"] != "1000" { + t.Error("Must increase balance") + } + if result["pending"] != "0" { + t.Error("Must deduct pending") + } + + result = r.client.HGetAllMap(r.formatKey("finances")).Val() + if result["paid"] != "500" { + t.Error("Must not touch pool paid") + } + if result["balance"] != "10250" { + t.Error("Must increase pool balance") + } + if result["pending"] != "0" { + t.Error("Must deduct pool pending") + } + + err := r.client.ZRank(r.formatKey("payments:pending"), join("x", amount)).Err() + if err != redis.Nil { + t.Errorf("Must remove pending payment") + } +} + +func TestWritePayment(t *testing.T) { + reset() + + r.client.HMSetMap( + r.formatKey("miners:x"), + map[string]string{"paid": "50", "balance": "1000", "pending": "250"}, + ) + r.client.HMSetMap( + r.formatKey("finances"), + map[string]string{"paid": "500", "balance": "10000", "pending": "250"}, + ) + + amount := int64(250) + tx := int64(2) + r.WritePayment("x", "0x0", amount, tx) + result := r.client.HGetAllMap(r.formatKey("miners:x")).Val() + if result["pending"] != "0" { + t.Error("Must unset pending amount") + } + if result["balance"] != "1000" { + t.Error("Must not touch balance") + } + if result["paid"] != "300" { + t.Error("Must increase paid") + } + + result = r.client.HGetAllMap(r.formatKey("finances")).Val() + if result["pending"] != "0" { + t.Error("Must deduct pool pending amount") + } + if result["balance"] != "10000" { + t.Error("Must not touch pool balance") + } + if result["paid"] != "750" { + t.Error("Must increase pool paid") + } + + err := r.client.Get(r.formatKey("payments:lock")).Err() + if err != redis.Nil { + t.Errorf("Must release lock") + } + + err = r.client.ZRank(r.formatKey("payments:pending"), join("x", amount)).Err() + if err != redis.Nil { + t.Error("Must remove pending payment") + } + err = r.client.ZRank(r.formatKey("payments:all"), join("0x0", "x", amount)).Err() + if err == redis.Nil { + t.Error("Must add payment to set") + } + err = r.client.ZRank(r.formatKey("payments:x"), join("0x0", amount)).Err() + if err == redis.Nil { + t.Error("Must add payment to set") + } +} + +func TestGetPendingPayments(t *testing.T) { + reset() + + r.client.HMSetMap( + r.formatKey("miners:x"), + map[string]string{"paid": "100", "balance": "750", "pending": "250"}, + ) + + amount := int64(1000) + r.UpdateBalance("x", amount) + pending := r.GetPendingPayments() + + if len(pending) != 1 { + t.Error("Must return pending payment") + } + if pending[0].Amount != amount { + t.Error("Must have corrent amount") + } + if pending[0].Address != "x" { + t.Error("Must have corrent account") + } + if pending[0].Timestamp <= 0 { + t.Error("Must have timestamp") + } +} + +func TestCollectLuckStats(t *testing.T) { + reset() + + members := []redis.Z{ + redis.Z{Score: 0, Member: "1:0:0x0:0x0:0:100:100:0"}, + } + r.client.ZAdd(r.formatKey("blocks:immature"), members...) + members = []redis.Z{ + redis.Z{Score: 1, Member: "1:0:0x2:0x0:0:50:100:0"}, + redis.Z{Score: 2, Member: "0:1:0x1:0x0:0:100:100:0"}, + redis.Z{Score: 3, Member: "0:0:0x3:0x0:0:200:100:0"}, + } + r.client.ZAdd(r.formatKey("blocks:matured"), members...) + + stats, _ := r.CollectLuckStats([]int{1, 2, 5, 10}) + expectedStats := map[string]interface{}{ + "1": map[string]float64{ + "luck": 1, "uncleRate": 1, "orphanRate": 0, + }, + "2": map[string]float64{ + "luck": 0.75, "uncleRate": 0.5, "orphanRate": 0, + }, + "4": map[string]float64{ + "luck": 1.125, "uncleRate": 0.5, "orphanRate": 0.25, + }, + } + + if !reflect.DeepEqual(stats, expectedStats) { + t.Error("Stats != expected stats") + } +} + +func TestCollectStats(t *testing.T) { + stat, err := r.CollectStats(500000, 100, 100) + + if err != nil { + t.Errorf("Result : %v, Err : %v", stat, err) + } + t.Logf("Result : %v", stat) +} + +func TestGetMinerStats(t *testing.T) { + + stats := make(map[string]interface{}) + + login := "0x5ca87a9e8e132be404a1efb6516665252a74a4e2" + + tx := r.client.Multi() + defer tx.Close() + + cmds, err := tx.Exec(func() error { + tx.HGetAllMap(r.formatKey("miners", login)) + tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, 100) + tx.ZCard(r.formatKey("payments", login)) + tx.HGet(r.formatKey("shares", "roundCurrent"), login) + return nil + }) + + if err != nil && err != redis.Nil { + t.Errorf("Error :", err) + } else { + result, _ := cmds[0].(*redis.StringStringMapCmd).Result() + stats["stats"] = convertStringMap(result) + payments := convertPaymentsResults(cmds[1].(*redis.ZSliceCmd)) + stats["payments"] = payments + stats["paymentsTotal"] = cmds[2].(*redis.IntCmd).Val() + roundShares, _ := cmds[3].(*redis.StringCmd).Int64() + if roundShares < 0 { + roundShares = 0 + } + stats["roundShares"] = roundShares + log.Printf("Inner Result : %v ", result) + } + + log.Printf("Result : %v ", stats) + + if err != nil { + t.Errorf("Error :", err) + } + +} + +func TestStoreExchangeData(t *testing.T) { + + m := map[string]string{ + "id": "ethereum", + "name": "Ethereum", + "symbol": "ETH", + "rank": "2", + "price_usd": "311.984", + "price_btc": "0.0823755", + "24h_volume_usd": "1161280000.0", + "market_cap_usd": "29309660622.0", + "available_supply": "93946038.0", + "total_supply": "93946038.0", + "percent_change_1h": "0.47", + "percent_change_24h": "4.12", + "percent_change_7d": "30.36", + "last_updated": "1502540048", + "price_inr": "19995.366544", + "24h_volume_inr": "74427596480.0", + "market_cap_inr": "1878485458898", + } + m1 := map[string]string{ + "id": "bitcoin", + "name": "Bitcoin", + "symbol": "BTC", + "rank": "1", + "price_usd": "3836.67", + "price_btc": "1.0", + "24h_volume_usd": "2080280000.0", + "market_cap_usd": "63315651883.0", + "available_supply": "16502762.0", + "total_supply": "16502762.0", + "percent_change_1h": "1.26", + "percent_change_24h": "8.93", + "percent_change_7d": "19.58", + "last_updated": "1502551754", + "price_inr": "245896.01697", + "24h_volume_inr": "133327225479.9999847412", + "market_cap_inr": "4057963444804", + } + + data := []map[string]string{ + m1, + m, + } + + tx := r.client.Multi() + defer tx.Close() + + for _, v := range data { + + for k1, v1 := range v { + tx.HSet(r.formatKey("exchange", v["symbol"]), k1, v1) + } + } + log.Print("Writing Exchange Data : %v", data) +} + +func TestGetExchangeData(t *testing.T) { + + cmd := r.client.HGetAllMap(r.formatKey("exchange", "ETH")) + result, err := cmd.Result() + + log.Printf("Writing Exchange Data : %v ", result) + + if err != nil { + t.Errorf("Error at GetExchangeData:", err) + } + +} + +func TestCreateNewNValue(t *testing.T) { + + result, err := r.CreateNewNValue(4000000000) + if err != nil { + t.Errorf("Result : %v, Err : %v", result, err) + } + t.Logf("Result : %v", result) +} + +func TestGetNetworkDifficultyForCurrentShareDifficulty(t *testing.T) { + + //m ,err := r.GetNodeStates() + result, err := r.GetNetworkDifficultyForCurrentShareDifficulty(4000000000) + if err != nil { + t.Errorf("Result : %v, Err : %v", result, err) + } + t.Logf("Result : %v", result) + +} + +func TestGetNetworkDifficulty(t *testing.T) { + result, err := r.GetNetworkDifficulty() + if err != nil { + t.Errorf("Result : %v, Err :%v", result, err) + } + t.Logf("Result : %v", result) + +} + +func TestGetThreshold(t *testing.T) { + result, err := r.SetThreshold("0xfacb288273969c68e9ad1eeeb81f08ab92cf57ad", 5000000) + t.Logf("Result : %v", result) + if err != nil { + t.Errorf("Error , %v", err) + } +} + +func TestSetThreshold(t *testing.T) { + r.SetThreshold("0xfacb288273969c68e9ad1eeeb81f08ab92cf57ad", 5000000) + result, err := r.GetThreshold("0xfacb288273969c68e9ad1eeeb81f08ab92cf57ad") + t.Logf("Result : %v", result) + if err != nil { + t.Errorf("Error , %v", err) + } + +} + +func TestLogIP(t *testing.T) { + + r.LogIP("0xb9cf2da90bdff1bc014720cc84f5ab99d7974eba", "192.168.00.100") + +} + +func TestAdjustCurrentNShares(t *testing.T) { + + result, err := r.AdjustCurrentNShares(4000000000) + t.Logf("Result : %v", result) + if err != nil { + t.Errorf("Error , %v", err) + } + + /*currentNShare := 1010 + lastN := 1000 + + + tx := r.client.Multi() + defer tx.Close() + + + if currentNShare > lastN{ + + + shareHash := make([]string, currentNShare-lastN) + + cmd, err := tx.Exec(func() error { + + //Keep removing the shares from the List by RPOP and while removing adjust the correcponding miner share value and the stat:roundCurrent Share value + //count :=0 + + for loopIndex := currentNShare; loopIndex > lastN; loopIndex--{ + + //Generate all the poped value of the ShareHash on the Array + //tx.LIndex(r.formatKey("lastshares"),-1) + tx.RPop(r.formatKey("lastshares")) + + //tx.HIncrBy(r.formatKey("shares", "roundCurrent"), str, -1) + + //t.Logf("List index value : %v", str) + //count++ + + } + return nil + }) + if err != nil { + t.Logf("Error while Reducing the share count , %v", err) + } else { + + tx2 := r.client.Multi() + defer tx2.Close() + + //Decrement the corresponding share value + _, err := tx2.Exec(func() error { + for key , _ := range shareHash { + poppedValue, err := cmd[key].(*redis.StringCmd).Result() + //poppedValue1, err := cmd[1].(*redis.StringCmd).Result() + if err==nil{ + tx2.HIncrBy(r.formatKey("stats"), "roundShares", -1) + tx2.HIncrBy(r.formatKey("shares", "roundCurrent"),poppedValue, -1) + return errors.New("TEST RETURN") + } + log.Print(poppedValue) + log.Print(key) + //log.Print(poppedValue1) + } + return nil + }) + if err!=nil{ + t.Errorf("Error while adjusting the last share window count , %v", err) + + } + } + + } else { + //No adjustment is required for the Window + t.Logf("No formatting required") + } + */ + +} + +func TestWriteBlock(t *testing.T) { + +} + +func TestWriteShare(t *testing.T) { + +} + +func reset() { + keys := r.client.Keys(r.prefix + ":*").Val() + for _, k := range keys { + r.client.Del(k) + } +} diff --git a/util/util.go b/util/util.go index f64faaa8..4d23bea2 100644 --- a/util/util.go +++ b/util/util.go @@ -26,7 +26,6 @@ func StringToBig(h string) *big.Int { n.SetString(h, 0) return n } - func IsValidHexAddress(s string) bool { if IsZeroHash(s) || !addressPattern.MatchString(s) { return false