From 159b730eebe49c6db01ddcd708b5f32e25f251b9 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:19:25 +0100 Subject: [PATCH 01/11] Update redis.go --- storage/redis.go | 452 ++++++++++++++++++++--------------------------- 1 file changed, 188 insertions(+), 264 deletions(-) diff --git a/storage/redis.go b/storage/redis.go index 98f67f1..9a12801 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -4,14 +4,15 @@ import ( "fmt" "math" "math/big" - "sort" + "sort" "strconv" "strings" "time" + "log" "gopkg.in/redis.v3" - "github.com/yuriy0803/open-etc-pool-friends/util" + "github.com/feeleep75/open-ethereum-pool/util" ) type Config struct { @@ -25,12 +26,13 @@ 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"` + Timestamp int64 `json:"x"` + PoolHash int64 `json:"y"` + Diff int64 `json:"d"` } type MinerCharts struct { @@ -57,24 +59,29 @@ type LuckCharts struct { } type SumRewardData struct { - Interval int64 `json:"inverval"` - Reward int64 `json:"reward"` - Name string `json:"name"` - Offset int64 `json:"offset"` - Blocks int64 `json:"blocks"` + Interval int64 `json:"inverval"` + Reward int64 `json:"reward"` + Name string `json:"name"` + Offset int64 `json:"offset"` + Blocks int64 `json:"blocks"` + Effort float64 `json:"personalEffort"` + Count float64 `json:"_"` + ESum float64 `json:"_"` } - type RewardData struct { - Height int64 `json:"blockheight"` - Timestamp int64 `json:"timestamp"` - BlockHash string `json:"blockhash"` - Reward int64 `json:"reward"` - Percent float64 `json:"percent"` - Immature bool `json:"immature"` + Height int64 `json:"blockheight"` + Timestamp int64 `json:"timestamp"` + BlockHash string `json:"blockhash"` + Reward int64 `json:"reward"` + Percent float64 `json:"percent"` + Immature bool `json:"immature"` + Difficulty int64 `json:"-"` + PersonalShares int64 `json:"-"` + PersonalEffort float64 `json:"personalEffort"` } - type BlockData struct { - Login string `json:"login"` + Finder string `json:"finder"` + ActualDiff int64 `json:"shareDiff"` Height int64 `json:"height"` Timestamp int64 `json:"timestamp"` Difficulty int64 `json:"difficulty"` @@ -93,6 +100,7 @@ type BlockData struct { RoundHeight int64 `json:"-"` candidateKey string immatureKey string + PersonalShares int64 `json:"-"` } func (b *BlockData) RewardInShannon() int64 { @@ -113,7 +121,7 @@ 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.Login) + return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Finder, b.ActualDiff, b.PersonalShares) } type Miner struct { @@ -126,25 +134,20 @@ type Miner struct { type Worker struct { Miner - TotalHR int64 `json:"hr2"` - ValidShares int64 `json:"valid"` - StaleShares int64 `json:"stale"` - InvalidShares int64 `json:"invalid"` - ValidPercent float64 `json:"v_per"` - StalePercent float64 `json:"s_per"` - InvalidPercent float64 `json:"i_per"` - WorkerStatus int64 `json:"w_stat"` - WorkerStatushas int64 `json:"w_stat_s"` -} - -func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { + TotalHR int64 `json:"hr2"` + PortDiff string `json:"portDiff"` + WorkerDiff int64 `json:"difficulty"` + WorkerHostname string `json:"hostname"` +} + +func NewRedisClient(cfg *Config, prefix string, pplns int64, CoinName string) *RedisClient { client := redis.NewClient(&redis.Options{ Addr: cfg.Endpoint, Password: cfg.Password, DB: cfg.Database, PoolSize: cfg.PoolSize, }) - return &RedisClient{client: client, prefix: prefix, pplns: pplns} + return &RedisClient{client: client, prefix: prefix, pplns: pplns, CoinName: CoinName} } func (r *RedisClient) Client() *redis.Client { @@ -177,9 +180,9 @@ 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}) +func (r *RedisClient) WritePoolCharts(time int64, poolHash string, diff string) error { + s := join(time, poolHash, diff) + cmd := r.client.ZAdd(r.formatKey("charts", "pool"), redis.Z{Score: float64(time), Member: s}) return cmd.Err() } @@ -190,22 +193,17 @@ func (r *RedisClient) WriteMinerCharts(time1 int64, time2, k string, hash, large } 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 } @@ -213,12 +211,11 @@ func (r *RedisClient) GetPoolCharts(poolHashLen int64) (stats []*PoolCharts, err 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) + fields := strings.Split(v.Member.(string), ":") + pc.PoolHash, _ = strconv.ParseInt(fields[1], 10, 64) + pc.Diff, _ = strconv.ParseInt(fields[2], 10, 64) result = append(result, &pc) } var reverse []*PoolCharts @@ -228,6 +225,7 @@ func convertPoolChartsResults(raw *redis.ZSliceCmd) []*PoolCharts { return reverse } + func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { var result []*MinerCharts for _, v := range raw.Val() { @@ -243,7 +241,7 @@ func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { } var reverse []*MinerCharts for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) + reverse = append(reverse, result[i]); } return reverse } @@ -304,7 +302,7 @@ func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, er return stats, nil } -func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { +func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int, blocktime float64) error { tx := r.client.Multi() defer tx.Close() @@ -315,6 +313,7 @@ func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) er 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 @@ -352,7 +351,7 @@ func (r *RedisClient) checkPoWExist(height uint64, params []string) (bool, error return val == 0, err } -func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, height uint64, window time.Duration) (bool, error) { +func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, actualDiff int64, netDiff int64, height, topHeight uint64, window time.Duration, hostname string) (bool, error) { exist, err := r.checkPoWExist(height, params) if err != nil { return false, err @@ -368,14 +367,14 @@ func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, ts := ms / 1000 _, err = tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, window) + r.writeShare(tx, ms, ts, login, id, diff, actualDiff, height, topHeight, netDiff, window, hostname) tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) return nil }) return false, err } -func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (bool, error) { +func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actualDiff int64, roundDiff int64, height, topHeight uint64, window time.Duration, hostname string) (bool, error) { exist, err := r.checkPoWExist(height, params) if err != nil { return false, err @@ -391,9 +390,10 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD ts := ms / 1000 cmds, err := tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, window) + r.writeShare(tx, ms, ts, login, id, diff, actualDiff, height, topHeight, roundDiff, 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")) @@ -432,14 +432,18 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD 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) + s := join(hashHex, ts, roundDiff, totalShares, login, actualDiff, 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, expire time.Duration) { +func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, actualDiff int64, height, topHeight uint64, netDiff 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 @@ -450,11 +454,13 @@ func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string 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)}) - tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) + 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(actualDiff, 10)) } func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) { @@ -487,13 +493,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 { @@ -501,6 +500,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") } @@ -525,7 +531,6 @@ func (r *RedisClient) GetImmatureBlocks(maxHeight int64) ([]*BlockData, error) { } return convertBlockResults(cmd), 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) @@ -594,6 +599,15 @@ func (r *RedisClient) GetBalance(login string) (int64, error) { } return cmd.Int64() } +func (r *RedisClient) GetTreshold(login string) (int64, error) { + cmd := r.client.HGet(r.formatKey("miners", login), "payouttreshold") + if cmd.Err() == redis.Nil { + return 0, nil + } else if cmd.Err() != nil { + return 0, cmd.Err() + } + return cmd.Int64() +} func (r *RedisClient) LockPayouts(login string, amount int64) error { key := r.formatKey("payments", "lock") @@ -687,18 +701,13 @@ func (r *RedisClient) WritePayment(login, txHash string, amount int64) error { 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.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 } - func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { if amount <= 0 { return nil @@ -706,15 +715,15 @@ func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, tx := r.client.Multi() defer tx.Close() - addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) - remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) + 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 _, 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 @@ -777,7 +786,6 @@ func (r *RedisClient) WriteMaturedBlock(block *BlockData, roundRewards map[strin 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 @@ -851,11 +859,9 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string 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.ZCard(r.formatKey("payments", login)) tx.HGet(r.formatKey("shares", "currentShares"), 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) return nil }) @@ -866,7 +872,7 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string stats["stats"] = convertStringMap(result) payments := convertPaymentsResults(cmds[1].(*redis.ZSliceCmd)) stats["payments"] = payments - stats["paymentsTotal"], _ = cmds[2].(*redis.StringCmd).Int64() + stats["paymentsTotal"] = cmds[2].(*redis.IntCmd).Val() shares := cmds[4].(*redis.StringSliceCmd).Val() csh := 0 for _, val := range shares { @@ -950,14 +956,15 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym 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.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) return nil }) - if (err != nil) && (err != redis.Nil) { + if err != nil { return nil, err } @@ -978,15 +985,21 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym 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)) stats["miners"] = miners stats["minersTotal"] = len(miners) stats["hashrate"] = totalHashrate + + exchangedata, _ := cmds[12].(*redis.StringStringMapCmd).Result() + stats["exchangedata"] = exchangedata + + finders := convertFindersResults(cmds[13].(*redis.ZSliceCmd)) + stats["finders"] = finders + + return stats, nil } @@ -1003,6 +1016,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login cmds, err := tx.Exec(func() error { tx.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", fmt.Sprint("(", now-largeWindow)) tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) + 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.ZRangeWithScores(r.formatKey("worker", "blocks", login), 0, -1) @@ -1017,7 +1031,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login currentHashrate := int64(0) online := int64(0) offline := int64(0) - workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) + workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[5].(*redis.ZSliceCmd)) for id, worker := range workers { timeOnline := now - worker.startedAt @@ -1044,7 +1058,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login online++ } - blocks := cmds[4].(*redis.ZSliceCmd).Val() + blocks := cmds[5].(*redis.ZSliceCmd).Val() for _, val := range blocks { parts := strings.Split(val.Member.(string), ":") @@ -1059,68 +1073,25 @@ 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) - worker.ValidShares = int64(5) - worker.StaleShares = int64(5) - worker.InvalidShares = int64(5) - worker.ValidShares = valid_share - worker.StaleShares = stale_share - worker.InvalidShares = invalid_share - //test percentage - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - tot_share := int64(0) - tot_share += valid_share - tot_share += stale_share - tot_share += invalid_share - if tot_share > 0 { - d := float64(100) - //tot_share += ////error - cost_per := float64(tot_share) / d - v_per := float64(valid_share) / cost_per - worker.ValidPercent = toFixed(v_per, 1) - s_per := float64(stale_share) / cost_per - worker.StalePercent = toFixed(s_per, 1) - i_per := float64(invalid_share) / cost_per - worker.InvalidPercent = toFixed(i_per, 1) - } else { - worker.ValidPercent = toFixed(0, 1) - worker.StalePercent = toFixed(0, 1) - worker.InvalidPercent = toFixed(0, 1) - } - w_stat := int64(0) //test worker large hashrate indicator - if worker.HR >= worker.TotalHR { - w_stat = 1 - worker.WorkerStatus = w_stat - } else if worker.HR < worker.TotalHR { - w_stat = 0 - worker.WorkerStatus = w_stat - } - ///test small hr - tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") - - if tot_w.Err() == redis.Nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, nil - } else if tot_w.Err() != nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, tot_w.Err() - } - - last_hr, _ := tot_w.Int64() - w_stat_s := int64(0) //test worker hashrate indicator - if worker.HR > last_hr { - w_stat_s = 1 - worker.WorkerStatushas = w_stat_s - } else if worker.HR <= last_hr { - w_stat_s = 0 - worker.WorkerStatushas = w_stat_s - } - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) workers[id] = worker } + shares := cmds[2].(*redis.StringSliceCmd).Val() + + csh := 0 + //var myshares []string + for _, val := range shares { + //text := "█" + if val != login { + //text = "▁" + } else { + csh++ + } + //myshares = append(myshares, strconv.FormatInt(int64(ind) 10)) + //myshares = append(myshares, text) + } + stats["roundShares"] = csh + //stats["shares"] = myshares stats["workers"] = workers stats["workersTotal"] = len(workers) stats["workersOnline"] = online @@ -1128,8 +1099,8 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login stats["hashrate"] = totalHashrate stats["currentHashrate"] = currentHashrate - stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 - rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all + stats["rewards"] = convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // last 40 + rewards := convertRewardResults(cmds[4].(*redis.ZSliceCmd)) // all var dorew []*SumRewardData dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) @@ -1139,30 +1110,27 @@ 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 { - for _, dore := range dorew { + dore.Count += 0 + dore.ESum += 0 dore.Reward += 0 dore.Blocks += 0 + dore.Effort += 0 if reward.Timestamp > now-dore.Interval { dore.Reward += reward.Reward dore.Blocks++ + dore.ESum += reward.PersonalEffort + dore.Count++ + dore.Effort = dore.ESum / dore.Count } } } + stats["sumrewards"] = dorew stats["24hreward"] = dorew[2].Reward return stats, nil } -func round(num float64) int { - return int(num + math.Copysign(0.5, num)) -} - -func toFixed(num float64, precision int) float64 { - output := math.Pow(10, float64(precision)) - return float64(round(num*output)) / output -} - func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { stats := make(map[string]interface{}) @@ -1269,7 +1237,9 @@ func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData { 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.Login = fields[6] + block.Finder = fields[6] + block.ActualDiff, _ = strconv.ParseInt(fields[7], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[8], 10, 64) block.candidateKey = v.Member.(string) result = append(result, &block) } @@ -1290,6 +1260,11 @@ func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { reward.Percent, _ = strconv.ParseFloat(fields[1], 64) reward.Immature, _ = strconv.ParseBool(fields[2]) reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) + reward.Difficulty, _ = strconv.ParseInt(fields[6], 10, 64) + reward.PersonalShares, _ = strconv.ParseInt(fields[7], 10, 64) + Difficulty, _ := strconv.ParseFloat(fields[6], 64) + PersonalShares, _ := strconv.ParseFloat(fields[7], 64) + reward.PersonalEffort = float64(PersonalShares / Difficulty) result = append(result, &reward) } } @@ -1315,7 +1290,9 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64) block.RewardString = fields[7] block.ImmatureReward = fields[7] - block.Login = fields[8] + block.Finder = fields[8] + block.ActualDiff, _ = strconv.ParseInt(fields[9], 10, 64) + block.PersonalShares, _ = strconv.ParseInt(fields[10], 10, 64) block.immatureKey = v.Member.(string) result = append(result, &block) } @@ -1325,7 +1302,7 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { // Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} // TS => diff, id, ms -func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { +func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd) map[string]Worker { now := util.MakeTimestamp() / 1000 workers := make(map[string]Worker) @@ -1340,26 +1317,34 @@ 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] + }else{ + hostname = "unknown" + } + id := parts[1] score := int64(v.Score) worker := workers[id] // Add for large window worker.TotalHR += share - worker.ValidShares = int64(4) - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - worker.WorkerStatus = int64(0) - worker.WorkerStatushas = int64(0) - //worker.StatleShares = int64(4) - //worker.InvalidShares = int64(4) + + // Addition from Mohannad Otaibi to report Difficulty + worker.WorkerDiff = share + worker.WorkerHostname = hostname + // End Mohannad Adjustments + // Add for small window if matches if score >= now-window { worker.HR += share } + if worker.LastBeat < score { worker.LastBeat = score } @@ -1432,7 +1417,7 @@ func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { } var reverse []map[string]interface{} for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) + reverse = append(reverse, result[i]); } return result } @@ -1448,6 +1433,41 @@ func convertFindersResults(raw *redis.ZSliceCmd) []map[string]interface{} { return result } +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 +} + /* Timestamp int64 `json:"x"` TimeFormat string `json:"timeFormat"` @@ -1478,7 +1498,7 @@ func convertPaymentChartsResults(raw *redis.ZSliceCmd) []*PaymentCharts { } var reverse []*PaymentCharts for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) + reverse = append(reverse, result[i]); } return reverse } @@ -1492,99 +1512,3 @@ func (r *RedisClient) GetCurrentHashrate(login string) (int64, error) { } return hashrate.Int64() } - -// Need a function to delete on round end or whatever, and another function to get. -func (r *RedisClient) ResetWorkerShareStatus() { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.HDel(r.formatKey("minerShare")) - return nil - }) - - // THis should do it ay ? - // fuck it -} - -// Don't know if this will work, returning three values, but let's see - -func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { - valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") - stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") - invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") - - if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { - return 0, 0, 0, nil - } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { - return 0, 0, 0, valid_shares.Err() - } - - v_c, _ := valid_shares.Int64() - s_c, _ := stale_shares.Int64() - i_c, _ := invalid_shares.Int64() - return v_c, s_c, i_c, nil - -} - -//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 - if valid { - valid_int = 1 - } - if stale { - stale_int = 1 - } - if invalid { - 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() - 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() - - 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)) // Would that work? - - return nil - }) - } //end else -} - -func (r *RedisClient) NumberStratumWorker(count int) { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.Del(r.formatKey("WorkersTot")) - tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) - //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) - return nil - }) -} From 893a29859c933bc28c48e39ddc4357079187cb0d Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:21:11 +0100 Subject: [PATCH 02/11] Update redis.go --- storage/redis.go | 465 +++++++++++++++++++++++++++-------------------- 1 file changed, 268 insertions(+), 197 deletions(-) diff --git a/storage/redis.go b/storage/redis.go index 9a12801..86c87db 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -4,15 +4,14 @@ import ( "fmt" "math" "math/big" - "sort" + "sort" "strconv" "strings" "time" - "log" "gopkg.in/redis.v3" - "github.com/feeleep75/open-ethereum-pool/util" + "github.com/yuriy0803/open-etc-pool-friends/util" ) type Config struct { @@ -26,13 +25,12 @@ type RedisClient struct { client *redis.Client prefix string pplns int64 - CoinName string } type PoolCharts struct { - Timestamp int64 `json:"x"` - PoolHash int64 `json:"y"` - Diff int64 `json:"d"` + Timestamp int64 `json:"x"` + TimeFormat string `json:"timeFormat"` + PoolHash int64 `json:"y"` } type MinerCharts struct { @@ -59,29 +57,24 @@ type LuckCharts struct { } 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:"personalEffort"` - Count float64 `json:"_"` - ESum float64 `json:"_"` + Interval int64 `json:"inverval"` + Reward int64 `json:"reward"` + Name string `json:"name"` + Offset int64 `json:"offset"` + Blocks int64 `json:"blocks"` } + type RewardData struct { - Height int64 `json:"blockheight"` - Timestamp int64 `json:"timestamp"` - BlockHash string `json:"blockhash"` - Reward int64 `json:"reward"` - Percent float64 `json:"percent"` - Immature bool `json:"immature"` - Difficulty int64 `json:"-"` - PersonalShares int64 `json:"-"` - PersonalEffort float64 `json:"personalEffort"` + Height int64 `json:"blockheight"` + Timestamp int64 `json:"timestamp"` + BlockHash string `json:"blockhash"` + Reward int64 `json:"reward"` + Percent float64 `json:"percent"` + Immature bool `json:"immature"` } + type BlockData struct { - Finder string `json:"finder"` - ActualDiff int64 `json:"shareDiff"` + Login string `json:"login"` Height int64 `json:"height"` Timestamp int64 `json:"timestamp"` Difficulty int64 `json:"difficulty"` @@ -100,7 +93,6 @@ type BlockData struct { RoundHeight int64 `json:"-"` candidateKey string immatureKey string - PersonalShares int64 `json:"-"` } func (b *BlockData) RewardInShannon() int64 { @@ -121,7 +113,7 @@ 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.ActualDiff, b.PersonalShares) + return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login) } type Miner struct { @@ -134,20 +126,25 @@ type Miner struct { type Worker struct { Miner - TotalHR int64 `json:"hr2"` - PortDiff string `json:"portDiff"` - WorkerDiff int64 `json:"difficulty"` - WorkerHostname string `json:"hostname"` -} - -func NewRedisClient(cfg *Config, prefix string, pplns int64, CoinName string) *RedisClient { + TotalHR int64 `json:"hr2"` + ValidShares int64 `json:"valid"` + StaleShares int64 `json:"stale"` + InvalidShares int64 `json:"invalid"` + ValidPercent float64 `json:"v_per"` + StalePercent float64 `json:"s_per"` + InvalidPercent float64 `json:"i_per"` + WorkerStatus int64 `json:"w_stat"` + WorkerStatushas int64 `json:"w_stat_s"` +} + +func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { client := redis.NewClient(&redis.Options{ Addr: cfg.Endpoint, Password: cfg.Password, DB: cfg.Database, PoolSize: cfg.PoolSize, }) - return &RedisClient{client: client, prefix: prefix, pplns: pplns, CoinName: CoinName} + return &RedisClient{client: client, prefix: prefix, pplns: pplns} } func (r *RedisClient) Client() *redis.Client { @@ -180,9 +177,9 @@ func (r *RedisClient) GetWhitelist() ([]string, error) { return cmd.Val(), nil } -func (r *RedisClient) WritePoolCharts(time int64, poolHash string, diff string) error { - s := join(time, poolHash, diff) - cmd := r.client.ZAdd(r.formatKey("charts", "pool"), redis.Z{Score: float64(time), Member: s}) +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() } @@ -193,17 +190,22 @@ func (r *RedisClient) WriteMinerCharts(time1 int64, time2, k string, hash, large } 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 } @@ -211,11 +213,12 @@ func (r *RedisClient) GetPoolCharts(poolHashLen int64) (stats []*PoolCharts, err func convertPoolChartsResults(raw *redis.ZSliceCmd) []*PoolCharts { var result []*PoolCharts for _, v := range raw.Val() { + // "Timestamp:TimeFormat:Hash" pc := PoolCharts{} pc.Timestamp = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - pc.PoolHash, _ = strconv.ParseInt(fields[1], 10, 64) - pc.Diff, _ = strconv.ParseInt(fields[2], 10, 64) + 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 @@ -225,7 +228,6 @@ func convertPoolChartsResults(raw *redis.ZSliceCmd) []*PoolCharts { return reverse } - func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { var result []*MinerCharts for _, v := range raw.Val() { @@ -241,7 +243,7 @@ func convertMinerChartsResults(raw *redis.ZSliceCmd) []*MinerCharts { } var reverse []*MinerCharts for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]); + reverse = append(reverse, result[i]) } return reverse } @@ -302,7 +304,7 @@ func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, er return stats, nil } -func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int, blocktime float64) error { +func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { tx := r.client.Multi() defer tx.Close() @@ -313,7 +315,6 @@ func (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int, bl 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 @@ -351,7 +352,7 @@ func (r *RedisClient) checkPoWExist(height uint64, params []string) (bool, error return val == 0, err } -func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, actualDiff int64, netDiff int64, height, topHeight uint64, window time.Duration, hostname string) (bool, error) { +func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, height uint64, window time.Duration) (bool, error) { exist, err := r.checkPoWExist(height, params) if err != nil { return false, err @@ -367,14 +368,14 @@ func (r *RedisClient) WriteShare(login, id string, params []string, diff int64, ts := ms / 1000 _, err = tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, actualDiff, height, topHeight, netDiff, window, hostname) + r.writeShare(tx, ms, ts, login, id, diff, window) tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) return nil }) return false, err } -func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actualDiff int64, roundDiff int64, height, topHeight uint64, window time.Duration, hostname string) (bool, error) { +func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (bool, error) { exist, err := r.checkPoWExist(height, params) if err != nil { return false, err @@ -390,10 +391,9 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actual ts := ms / 1000 cmds, err := tx.Exec(func() error { - r.writeShare(tx, ms, ts, login, id, diff, actualDiff, height, topHeight, roundDiff, window, hostname) + r.writeShare(tx, ms, ts, login, id, diff, window) 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")) @@ -432,35 +432,24 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, actual 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, actualDiff, personalShares) + s := join(hashHex, ts, roundDiff, totalShares, login) 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, actualDiff int64, height, topHeight uint64, netDiff 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 - */ - -// Moved get hostname to stratums - - - tx.LPush(r.formatKey("lastshares"), login) +func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, expire time.Duration) { + 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.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) + tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) 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(actualDiff, 10)) } func (r *RedisClient) WriteBlocksFound(ms, ts int64, login, id, share string, diff int64) { @@ -493,13 +482,6 @@ func join(args ...interface{}) string { } else { s[i] = "0" } - case *big.Int: - n := v.(*big.Int) - if n != nil { - s[i] = n.String() - } else { - s[i] = "0" - } case *big.Rat: x := v.(*big.Rat) if x != nil { @@ -507,6 +489,13 @@ func join(args ...interface{}) string { } else { s[i] = "0" } + case *big.Int: + n := v.(*big.Int) + if n != nil { + s[i] = n.String() + } else { + s[i] = "0" + } default: panic("Invalid type specified for conversion") } @@ -531,6 +520,7 @@ func (r *RedisClient) GetImmatureBlocks(maxHeight int64) ([]*BlockData, error) { } return convertBlockResults(cmd), 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) @@ -599,15 +589,6 @@ func (r *RedisClient) GetBalance(login string) (int64, error) { } return cmd.Int64() } -func (r *RedisClient) GetTreshold(login string) (int64, error) { - cmd := r.client.HGet(r.formatKey("miners", login), "payouttreshold") - if cmd.Err() == redis.Nil { - return 0, nil - } else if cmd.Err() != nil { - return 0, cmd.Err() - } - return cmd.Int64() -} func (r *RedisClient) LockPayouts(login string, amount int64) error { key := r.formatKey("payments", "lock") @@ -701,13 +682,18 @@ func (r *RedisClient) WritePayment(login, txHash string, amount int64) error { 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.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 } + func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { if amount <= 0 { return nil @@ -715,15 +701,15 @@ func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, tx := r.client.Multi() defer tx.Close() - 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) - + addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) + remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) remscore := block.Timestamp - 3600*24*40 // Store the last 40 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 @@ -786,6 +772,7 @@ func (r *RedisClient) WriteMaturedBlock(block *BlockData, roundRewards map[strin 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 @@ -859,9 +846,11 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string cmds, err := tx.Exec(func() error { tx.HGetAllMap(r.formatKey("miners", login)) tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, maxPayments-1) - tx.ZCard(r.formatKey("payments", login)) + tx.HGet(r.formatKey("paymentsTotal"), login) tx.HGet(r.formatKey("shares", "currentShares"), 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) return nil }) @@ -872,7 +861,7 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string stats["stats"] = convertStringMap(result) payments := convertPaymentsResults(cmds[1].(*redis.ZSliceCmd)) stats["payments"] = payments - stats["paymentsTotal"] = cmds[2].(*redis.IntCmd).Val() + stats["paymentsTotal"], _ = cmds[2].(*redis.StringCmd).Int64() shares := cmds[4].(*redis.StringSliceCmd).Val() csh := 0 for _, val := range shares { @@ -956,15 +945,14 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym 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.HGet(r.formatKey("paymentsTotal"), "all") tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) tx.LLen(r.formatKey("lastshares")) - tx.HGetAllMap(r.formatKey("exchange", r.CoinName)) - tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) + tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) return nil }) - if err != nil { + if (err != nil) && (err != redis.Nil) { return nil, err } @@ -985,21 +973,15 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) stats["payments"] = payments - stats["paymentsTotal"] = cmds[9].(*redis.IntCmd).Val() + stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() + finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) + stats["finders"] = finders totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) stats["miners"] = miners stats["minersTotal"] = len(miners) stats["hashrate"] = totalHashrate - - exchangedata, _ := cmds[12].(*redis.StringStringMapCmd).Result() - stats["exchangedata"] = exchangedata - - finders := convertFindersResults(cmds[13].(*redis.ZSliceCmd)) - stats["finders"] = finders - - return stats, nil } @@ -1016,7 +998,6 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login cmds, err := tx.Exec(func() error { tx.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", fmt.Sprint("(", now-largeWindow)) tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) - 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.ZRangeWithScores(r.formatKey("worker", "blocks", login), 0, -1) @@ -1031,7 +1012,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login currentHashrate := int64(0) online := int64(0) offline := int64(0) - workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[5].(*redis.ZSliceCmd)) + workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) for id, worker := range workers { timeOnline := now - worker.startedAt @@ -1058,7 +1039,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login online++ } - blocks := cmds[5].(*redis.ZSliceCmd).Val() + blocks := cmds[4].(*redis.ZSliceCmd).Val() for _, val := range blocks { parts := strings.Split(val.Member.(string), ":") @@ -1073,25 +1054,68 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login currentHashrate += worker.HR totalHashrate += worker.TotalHR - workers[id] = worker - } - - shares := cmds[2].(*redis.StringSliceCmd).Val() - - csh := 0 - //var myshares []string - for _, val := range shares { - //text := "█" - if val != login { - //text = "▁" + valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) + worker.ValidShares = int64(5) + worker.StaleShares = int64(5) + worker.InvalidShares = int64(5) + worker.ValidShares = valid_share + worker.StaleShares = stale_share + worker.InvalidShares = invalid_share + //test percentage + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + tot_share := int64(0) + tot_share += valid_share + tot_share += stale_share + tot_share += invalid_share + if tot_share > 0 { + d := float64(100) + //tot_share += ////error + cost_per := float64(tot_share) / d + v_per := float64(valid_share) / cost_per + worker.ValidPercent = toFixed(v_per, 1) + s_per := float64(stale_share) / cost_per + worker.StalePercent = toFixed(s_per, 1) + i_per := float64(invalid_share) / cost_per + worker.InvalidPercent = toFixed(i_per, 1) } else { - csh++ + worker.ValidPercent = toFixed(0, 1) + worker.StalePercent = toFixed(0, 1) + worker.InvalidPercent = toFixed(0, 1) + } + w_stat := int64(0) //test worker large hashrate indicator + if worker.HR >= worker.TotalHR { + w_stat = 1 + worker.WorkerStatus = w_stat + } else if worker.HR < worker.TotalHR { + w_stat = 0 + worker.WorkerStatus = w_stat } - //myshares = append(myshares, strconv.FormatInt(int64(ind) 10)) - //myshares = append(myshares, text) + ///test small hr + tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") + + if tot_w.Err() == redis.Nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, nil + } else if tot_w.Err() != nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, tot_w.Err() + } + + last_hr, _ := tot_w.Int64() + w_stat_s := int64(0) //test worker hashrate indicator + if worker.HR > last_hr { + w_stat_s = 1 + worker.WorkerStatushas = w_stat_s + } else if worker.HR <= last_hr { + w_stat_s = 0 + worker.WorkerStatushas = w_stat_s + } + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) + workers[id] = worker } - stats["roundShares"] = csh - //stats["shares"] = myshares + stats["workers"] = workers stats["workersTotal"] = len(workers) stats["workersOnline"] = online @@ -1099,8 +1123,8 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login stats["hashrate"] = totalHashrate stats["currentHashrate"] = currentHashrate - stats["rewards"] = convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // last 40 - rewards := convertRewardResults(cmds[4].(*redis.ZSliceCmd)) // all + stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 + rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all var dorew []*SumRewardData dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) @@ -1110,27 +1134,30 @@ 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 { + for _, dore := range dorew { - dore.Count += 0 - dore.ESum += 0 dore.Reward += 0 dore.Blocks += 0 - dore.Effort += 0 if reward.Timestamp > now-dore.Interval { dore.Reward += reward.Reward dore.Blocks++ - dore.ESum += reward.PersonalEffort - dore.Count++ - dore.Effort = dore.ESum / dore.Count } } } - stats["sumrewards"] = dorew stats["24hreward"] = dorew[2].Reward return stats, nil } +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +func toFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} + func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { stats := make(map[string]interface{}) @@ -1237,9 +1264,7 @@ func convertCandidateResults(raw *redis.ZSliceCmd) []*BlockData { 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.ActualDiff, _ = strconv.ParseInt(fields[7], 10, 64) - block.PersonalShares, _ = strconv.ParseInt(fields[8], 10, 64) + block.Login = fields[6] block.candidateKey = v.Member.(string) result = append(result, &block) } @@ -1260,11 +1285,6 @@ func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { reward.Percent, _ = strconv.ParseFloat(fields[1], 64) reward.Immature, _ = strconv.ParseBool(fields[2]) reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) - reward.Difficulty, _ = strconv.ParseInt(fields[6], 10, 64) - reward.PersonalShares, _ = strconv.ParseInt(fields[7], 10, 64) - Difficulty, _ := strconv.ParseFloat(fields[6], 64) - PersonalShares, _ := strconv.ParseFloat(fields[7], 64) - reward.PersonalEffort = float64(PersonalShares / Difficulty) result = append(result, &reward) } } @@ -1290,9 +1310,7 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { block.TotalShares, _ = strconv.ParseInt(fields[6], 10, 64) block.RewardString = fields[7] block.ImmatureReward = fields[7] - block.Finder = fields[8] - block.ActualDiff, _ = strconv.ParseInt(fields[9], 10, 64) - block.PersonalShares, _ = strconv.ParseInt(fields[10], 10, 64) + block.Login = fields[8] block.immatureKey = v.Member.(string) result = append(result, &block) } @@ -1302,7 +1320,7 @@ func convertBlockResults(rows ...*redis.ZSliceCmd) []*BlockData { // Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} // TS => diff, id, ms -func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd) map[string]Worker { +func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { now := util.MakeTimestamp() / 1000 workers := make(map[string]Worker) @@ -1317,34 +1335,26 @@ 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] - }else{ - hostname = "unknown" - } - id := parts[1] score := int64(v.Score) worker := workers[id] // Add for large window worker.TotalHR += share - - // Addition from Mohannad Otaibi to report Difficulty - worker.WorkerDiff = share - worker.WorkerHostname = hostname - // End Mohannad Adjustments - + worker.ValidShares = int64(4) + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + worker.WorkerStatus = int64(0) + worker.WorkerStatushas = int64(0) + //worker.StatleShares = int64(4) + //worker.InvalidShares = int64(4) // Add for small window if matches if score >= now-window { worker.HR += share } - if worker.LastBeat < score { worker.LastBeat = score } @@ -1417,7 +1427,7 @@ func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { } var reverse []map[string]interface{} for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]); + reverse = append(reverse, result[i]) } return result } @@ -1433,41 +1443,6 @@ func convertFindersResults(raw *redis.ZSliceCmd) []map[string]interface{} { return result } -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 -} - /* Timestamp int64 `json:"x"` TimeFormat string `json:"timeFormat"` @@ -1498,7 +1473,7 @@ func convertPaymentChartsResults(raw *redis.ZSliceCmd) []*PaymentCharts { } var reverse []*PaymentCharts for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]); + reverse = append(reverse, result[i]) } return reverse } @@ -1512,3 +1487,99 @@ func (r *RedisClient) GetCurrentHashrate(login string) (int64, error) { } return hashrate.Int64() } + +// Need a function to delete on round end or whatever, and another function to get. +func (r *RedisClient) ResetWorkerShareStatus() { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.HDel(r.formatKey("minerShare")) + return nil + }) + + // THis should do it ay ? + // fuck it +} + +// Don't know if this will work, returning three values, but let's see + +func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { + valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") + stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") + invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") + + if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { + return 0, 0, 0, nil + } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { + return 0, 0, 0, valid_shares.Err() + } + + v_c, _ := valid_shares.Int64() + s_c, _ := stale_shares.Int64() + i_c, _ := invalid_shares.Int64() + return v_c, s_c, i_c, nil + +} + +//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 + if valid { + valid_int = 1 + } + if stale { + stale_int = 1 + } + if invalid { + 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() + 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() + + 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)) // Would that work? + + return nil + }) + } //end else +} + +func (r *RedisClient) NumberStratumWorker(count int) { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.Del(r.formatKey("WorkersTot")) + tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) + //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) + return nil + }) +} From 3dcec9e88b7faaf4acee551df75e53664d16ffd3 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:39:21 +0100 Subject: [PATCH 03/11] Update redis.go --- storage/redis.go | 1585 ---------------------------------------------- 1 file changed, 1585 deletions(-) delete mode 100644 storage/redis.go diff --git a/storage/redis.go b/storage/redis.go deleted file mode 100644 index 86c87db..0000000 --- a/storage/redis.go +++ /dev/null @@ -1,1585 +0,0 @@ -package storage - -import ( - "fmt" - "math" - "math/big" - "sort" - "strconv" - "strings" - "time" - - "gopkg.in/redis.v3" - - "github.com/yuriy0803/open-etc-pool-friends/util" -) - -type Config struct { - Endpoint string `json:"endpoint"` - Password string `json:"password"` - Database int64 `json:"database"` - PoolSize int `json:"poolSize"` -} - -type RedisClient struct { - client *redis.Client - prefix string - pplns int64 -} - -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 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"` -} - -type RewardData struct { - Height int64 `json:"blockheight"` - Timestamp int64 `json:"timestamp"` - BlockHash string `json:"blockhash"` - Reward int64 `json:"reward"` - Percent float64 `json:"percent"` - Immature bool `json:"immature"` -} - -type BlockData struct { - Login string `json:"login"` - Height int64 `json:"height"` - Timestamp int64 `json:"timestamp"` - Difficulty int64 `json:"difficulty"` - TotalShares int64 `json:"shares"` - Uncle bool `json:"uncle"` - UncleHeight int64 `json:"uncleHeight"` - Orphan bool `json:"orphan"` - Hash string `json:"hash"` - Nonce string `json:"-"` - PowHash string `json:"-"` - MixDigest string `json:"-"` - Reward *big.Int `json:"-"` - ExtraReward *big.Int `json:"-"` - ImmatureReward string `json:"-"` - RewardString string `json:"reward"` - RoundHeight int64 `json:"-"` - candidateKey string - immatureKey string -} - -func (b *BlockData) RewardInShannon() int64 { - reward := new(big.Int).Div(b.Reward, util.Shannon) - return reward.Int64() -} - -func (b *BlockData) serializeHash() string { - if len(b.Hash) > 0 { - return b.Hash - } else { - return "0x0" - } -} - -func (b *BlockData) RoundKey() string { - return join(b.RoundHeight, b.Hash) -} - -func (b *BlockData) key() string { - return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login) -} - -type Miner struct { - LastBeat int64 `json:"lastBeat"` - HR int64 `json:"hr"` - Offline bool `json:"offline"` - startedAt int64 - Blocks int64 `json:"blocks"` -} - -type Worker struct { - Miner - TotalHR int64 `json:"hr2"` - ValidShares int64 `json:"valid"` - StaleShares int64 `json:"stale"` - InvalidShares int64 `json:"invalid"` - ValidPercent float64 `json:"v_per"` - StalePercent float64 `json:"s_per"` - InvalidPercent float64 `json:"i_per"` - WorkerStatus int64 `json:"w_stat"` - WorkerStatushas int64 `json:"w_stat_s"` -} - -func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { - client := redis.NewClient(&redis.Options{ - Addr: cfg.Endpoint, - Password: cfg.Password, - DB: cfg.Database, - PoolSize: cfg.PoolSize, - }) - return &RedisClient{client: client, prefix: prefix, pplns: pplns} -} - -func (r *RedisClient) Client() *redis.Client { - return r.client -} - -func (r *RedisClient) Check() (string, error) { - return r.client.Ping().Result() -} - -func (r *RedisClient) BgSave() (string, error) { - return r.client.BgSave().Result() -} - -// Always returns list of addresses. If Redis fails it will return empty list. -func (r *RedisClient) GetBlacklist() ([]string, error) { - cmd := r.client.SMembers(r.formatKey("blacklist")) - if cmd.Err() != nil { - return []string{}, cmd.Err() - } - return cmd.Val(), nil -} - -// Always returns list of IPs. If Redis fails it will return empty list. -func (r *RedisClient) GetWhitelist() ([]string, error) { - cmd := r.client.SMembers(r.formatKey("whitelist")) - if cmd.Err() != nil { - return []string{}, cmd.Err() - } - 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) 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) 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() - - 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 (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 -} - -func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { - - 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 (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { - tx := r.client.Multi() - defer tx.Close() - - 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)) - 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, height uint64, window time.Duration) (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, window) - tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) - return nil - }) - return false, err -} - -func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (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, window) - tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) - tx.HDel(r.formatKey("stats"), "roundShares") - 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 - } - hashHex := strings.Join(params, ":") - s := join(hashHex, ts, roundDiff, totalShares, login) - 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, expire time.Duration) { - 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("shares", "roundCurrent"), login, diff) - tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) - tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) - 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)) -} - -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 join(args ...interface{}) string { - s := make([]string, len(args)) - for i, v := range args { - switch v.(type) { - case string: - s[i] = v.(string) - case int64: - s[i] = strconv.FormatInt(v.(int64), 10) - case uint64: - s[i] = strconv.FormatUint(v.(uint64), 10) - case float64: - s[i] = strconv.FormatFloat(v.(float64), 'f', 0, 64) - case bool: - if v.(bool) { - s[i] = "1" - } 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 { - s[i] = n.String() - } else { - s[i] = "0" - } - default: - panic("Invalid type specified for conversion") - } - } - return strings.Join(s, ":") -} - -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 -} - -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 -} - -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) - if cmd.Err() != nil { - return nil, cmd.Err() - } - return convertRewardResults(cmd), nil -} - -func (r *RedisClient) GetRoundShares(height int64, nonce string) (map[string]int64, error) { - result := make(map[string]int64) - cmd := r.client.HGetAllMap(r.formatRound(height, nonce)) - if cmd.Err() != nil { - return nil, cmd.Err() - } - sharesMap, _ := cmd.Result() - for login, v := range sharesMap { - n, _ := strconv.ParseInt(v, 10, 64) - result[login] = n - } - return result, nil -} - -func (r *RedisClient) GetPayees() ([]string, error) { - payees := make(map[string]struct{}) - var result []string - var c int64 - - for { - var keys []string - var err error - c, keys, err = r.client.Scan(c, r.formatKey("miners", "*"), 100).Result() - if err != nil { - return nil, err - } - for _, row := range keys { - login := strings.Split(row, ":")[2] - payees[login] = struct{}{} - } - if c == 0 { - break - } - } - for login, _ := range payees { - result = append(result, login) - } - return result, nil -} - -func (r *RedisClient) GetTotalShares() (int64, error) { - cmd := r.client.LLen(r.formatKey("lastshares")) - if cmd.Err() == redis.Nil { - return 0, nil - } else if cmd.Err() != nil { - return 0, cmd.Err() - } - return cmd.Val(), nil -} - -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 - } else if cmd.Err() != nil { - return 0, cmd.Err() - } - return cmd.Int64() -} - -func (r *RedisClient) LockPayouts(login string, amount int64) error { - key := r.formatKey("payments", "lock") - result := r.client.SetNX(key, join(login, amount), 0).Val() - if !result { - return fmt.Errorf("Unable to acquire lock '%s'", key) - } - return nil -} - -func (r *RedisClient) UnlockPayouts() error { - key := r.formatKey("payments", "lock") - _, err := r.client.Del(key).Result() - return err -} - -func (r *RedisClient) IsPayoutsLocked() (bool, error) { - _, err := r.client.Get(r.formatKey("payments", "lock")).Result() - if err == redis.Nil { - return false, nil - } else if err != nil { - return false, err - } else { - return true, nil - } -} - -type PendingPayment struct { - Timestamp int64 `json:"timestamp"` - Amount int64 `json:"amount"` - Address string `json:"login"` -} - -func (r *RedisClient) GetPendingPayments() []*PendingPayment { - raw := r.client.ZRevRangeWithScores(r.formatKey("payments", "pending"), 0, -1) - var result []*PendingPayment - for _, v := range raw.Val() { - // timestamp -> "address:amount" - payment := PendingPayment{} - payment.Timestamp = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - payment.Address = fields[0] - payment.Amount, _ = strconv.ParseInt(fields[1], 10, 64) - result = append(result, &payment) - } - return result -} - -// Deduct miner's balance for payment -func (r *RedisClient) UpdateBalance(login string, amount int64) error { - tx := r.client.Multi() - defer tx.Close() - - ts := util.MakeTimestamp() / 1000 - - _, err := tx.Exec(func() error { - 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"), "pending", amount) - tx.ZAdd(r.formatKey("payments", "pending"), redis.Z{Score: float64(ts), Member: join(login, amount)}) - return nil - }) - return err -} - -func (r *RedisClient) RollbackBalance(login string, amount int64) error { - tx := r.client.Multi() - defer tx.Close() - - _, 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("finances"), "balance", amount) - 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 { - 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), "paid", amount) - 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.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 -} - -func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { - if amount <= 0 { - return nil - } - tx := r.client.Multi() - defer tx.Close() - - addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) - remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) - remscore := block.Timestamp - 3600*24*40 // Store the last 40 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 { - creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) - tx, err := r.client.Watch(creditKey) - // Must decrement immatures using existing log entry - immatureCredits := tx.HGetAllMap(creditKey) - if err != nil { - return err - } - defer tx.Close() - - ts := util.MakeTimestamp() / 1000 - value := join(block.Hash, ts, block.Reward) - - _, err = tx.Exec(func() error { - r.writeMaturedBlock(tx, block) - tx.ZAdd(r.formatKey("credits", "all"), redis.Z{Score: float64(block.Height), Member: value}) - - // Decrement immature balances - totalImmature := int64(0) - for login, amountString := range immatureCredits.Val() { - amount, _ := strconv.ParseInt(amountString, 10, 64) - totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) - } - - // Increment balances - total := int64(0) - for login, amount := range roundRewards { - 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)) - } - tx.Del(creditKey) - tx.HIncrBy(r.formatKey("finances"), "balance", total) - 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 -} - -func (r *RedisClient) WriteOrphan(block *BlockData) error { - creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) - tx, err := r.client.Watch(creditKey) - // Must decrement immatures using existing log entry - immatureCredits := tx.HGetAllMap(creditKey) - if err != nil { - return err - } - defer tx.Close() - - _, err = tx.Exec(func() error { - r.writeMaturedBlock(tx, block) - - // Decrement immature balances - totalImmature := int64(0) - for login, amountString := range immatureCredits.Val() { - amount, _ := strconv.ParseInt(amountString, 10, 64) - totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) - } - tx.Del(creditKey) - tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) - return nil - }) - return err -} - -func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { - tx := r.client.Multi() - defer tx.Close() - - _, err := tx.Exec(func() error { - for _, block := range blocks { - r.writeImmatureBlock(tx, block) - } - return nil - }) - return err -} - -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) { - return r.client.Exists(r.formatKey("miners", login)).Result() -} - -func (r *RedisClient) GetMinerStats(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", "currentShares"), 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) - 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"] = csh - } - - return stats, nil -} - -// Try to convert all numeric strings to int64 -func convertStringMap(m map[string]string) map[string]interface{} { - result := make(map[string]interface{}) - var err error - for k, v := range m { - result[k], err = strconv.ParseInt(v, 10, 64) - if err != nil { - result[k] = v - } - } - return result -} - -// WARNING: Must run it periodically to flush out of window hashrate entries -func (r *RedisClient) FlushStaleStats(window, largeWindow time.Duration) (int64, error) { - now := util.MakeTimestamp() / 1000 - max := fmt.Sprint("(", now-int64(window/time.Second)) - total, err := r.client.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", max).Result() - if err != nil { - return total, err - } - - var c int64 - miners := make(map[string]struct{}) - max = fmt.Sprint("(", now-int64(largeWindow/time.Second)) - - for { - var keys []string - var err error - c, keys, err = r.client.Scan(c, r.formatKey("hashrate", "*"), 100).Result() - if err != nil { - return total, err - } - for _, row := range keys { - login := strings.Split(row, ":")[2] - if _, ok := miners[login]; !ok { - n, err := r.client.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", max).Result() - if err != nil { - return total, err - } - miners[login] = struct{}{} - total += n - } - } - if c == 0 { - break - } - } - return total, 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.HGet(r.formatKey("paymentsTotal"), "all") - tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) - tx.LLen(r.formatKey("lastshares")) - tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) - return nil - }) - - if (err != nil) && (err != redis.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() - - immature := convertBlockResults(cmds[4].(*redis.ZSliceCmd)) - stats["immature"] = immature - stats["immatureTotal"] = cmds[7].(*redis.IntCmd).Val() - - matured := convertBlockResults(cmds[5].(*redis.ZSliceCmd)) - stats["matured"] = matured - stats["maturedTotal"] = cmds[8].(*redis.IntCmd).Val() - - payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) - stats["payments"] = payments - stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() - - finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) - stats["finders"] = finders - - totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) - stats["miners"] = miners - stats["minersTotal"] = len(miners) - stats["hashrate"] = totalHashrate - return stats, nil -} - -func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login string) (map[string]interface{}, error) { - smallWindow := int64(sWindow / time.Second) - largeWindow := int64(lWindow / 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", login), "-inf", fmt.Sprint("(", now-largeWindow)) - tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) - 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) - return nil - }) - - if err != nil { - return nil, err - } - - totalHashrate := int64(0) - currentHashrate := int64(0) - online := int64(0) - offline := int64(0) - workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) - - for id, worker := range workers { - timeOnline := now - worker.startedAt - if timeOnline < 600 { - timeOnline = 600 - } - - boundary := timeOnline - if timeOnline >= smallWindow { - boundary = smallWindow - } - worker.HR = worker.HR / boundary - - boundary = timeOnline - if timeOnline >= largeWindow { - boundary = largeWindow - } - worker.TotalHR = worker.TotalHR / boundary - - if worker.LastBeat < (now - smallWindow/2) { - worker.Offline = true - offline++ - } else { - online++ - } - - blocks := cmds[4].(*redis.ZSliceCmd).Val() - - for _, val := range blocks { - parts := strings.Split(val.Member.(string), ":") - rig := parts[2] - if id == rig { - str := fmt.Sprint(val.Member.(string)) - if worker.LastBeat < (now - largeWindow) { - tx.ZRem(r.formatKey("worker", "blocks", login), str) - } - } - } - - currentHashrate += worker.HR - totalHashrate += worker.TotalHR - valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) - worker.ValidShares = int64(5) - worker.StaleShares = int64(5) - worker.InvalidShares = int64(5) - worker.ValidShares = valid_share - worker.StaleShares = stale_share - worker.InvalidShares = invalid_share - //test percentage - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - tot_share := int64(0) - tot_share += valid_share - tot_share += stale_share - tot_share += invalid_share - if tot_share > 0 { - d := float64(100) - //tot_share += ////error - cost_per := float64(tot_share) / d - v_per := float64(valid_share) / cost_per - worker.ValidPercent = toFixed(v_per, 1) - s_per := float64(stale_share) / cost_per - worker.StalePercent = toFixed(s_per, 1) - i_per := float64(invalid_share) / cost_per - worker.InvalidPercent = toFixed(i_per, 1) - } else { - worker.ValidPercent = toFixed(0, 1) - worker.StalePercent = toFixed(0, 1) - worker.InvalidPercent = toFixed(0, 1) - } - w_stat := int64(0) //test worker large hashrate indicator - if worker.HR >= worker.TotalHR { - w_stat = 1 - worker.WorkerStatus = w_stat - } else if worker.HR < worker.TotalHR { - w_stat = 0 - worker.WorkerStatus = w_stat - } - ///test small hr - tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") - - if tot_w.Err() == redis.Nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, nil - } else if tot_w.Err() != nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, tot_w.Err() - } - - last_hr, _ := tot_w.Int64() - w_stat_s := int64(0) //test worker hashrate indicator - if worker.HR > last_hr { - w_stat_s = 1 - worker.WorkerStatushas = w_stat_s - } else if worker.HR <= last_hr { - w_stat_s = 0 - worker.WorkerStatushas = w_stat_s - } - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) - workers[id] = worker - } - - stats["workers"] = workers - stats["workersTotal"] = len(workers) - stats["workersOnline"] = online - stats["workersOffline"] = offline - stats["hashrate"] = totalHashrate - stats["currentHashrate"] = currentHashrate - - stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 - rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all - - var dorew []*SumRewardData - dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 12 hours", Interval: 3600 * 12, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 24 hours", Interval: 3600 * 24, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 7 days", Interval: 3600 * 24 * 7, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 30 days", Interval: 3600 * 24 * 30, Offset: 0}) - - for _, reward := range rewards { - - for _, dore := range dorew { - dore.Reward += 0 - dore.Blocks += 0 - if reward.Timestamp > now-dore.Interval { - dore.Reward += reward.Reward - dore.Blocks++ - } - } - } - stats["sumrewards"] = dorew - stats["24hreward"] = dorew[2].Reward - return stats, nil -} - -func round(num float64) int { - return int(num + math.Copysign(0.5, num)) -} - -func toFixed(num float64, precision int) float64 { - output := math.Pow(10, float64(precision)) - return float64(round(num*output)) / output -} - -func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { - stats := make(map[string]interface{}) - - tx := r.client.Multi() - defer tx.Close() - - max := int64(windows[len(windows)-1]) - - cmds, err := tx.Exec(func() error { - tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) - tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, max-1) - return nil - }) - if err != nil { - return stats, err - } - blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd), cmds[1].(*redis.ZSliceCmd)) - - calcLuck := func(max int) (int, float64, float64, float64) { - var total int - var sharesDiff, uncles, orphans float64 - for i, block := range blocks { - if i > (max - 1) { - break - } - if block.Uncle { - uncles++ - } - if block.Orphan { - orphans++ - } - sharesDiff += float64(block.TotalShares) / float64(block.Difficulty) - total++ - } - if total > 0 { - sharesDiff /= float64(total) - uncles /= float64(total) - orphans /= float64(total) - } - return total, sharesDiff, uncles, orphans - } - for _, max := range windows { - total, sharesDiff, uncleRate, orphanRate := calcLuck(max) - row := map[string]float64{ - "luck": sharesDiff, "uncleRate": uncleRate, "orphanRate": orphanRate, - } - stats[strconv.Itoa(total)] = row - if total < max { - 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 - } - 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 -} - -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() { - // "nonce:powHash:mixDigest:timestamp:diff:totalShares" - block := BlockData{} - block.Height = int64(v.Score) - block.RoundHeight = block.Height - fields := strings.Split(v.Member.(string), ":") - 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.Login = fields[6] - block.candidateKey = v.Member.(string) - result = append(result, &block) - } - return result -} - -func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { - var result []*RewardData - for _, row := range rows { - for _, v := range row.Val() { - // "amount:percent:immature:block.Hash:block.height" - reward := RewardData{} - reward.Timestamp = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - //block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64) - reward.BlockHash = fields[3] - reward.Reward, _ = strconv.ParseInt(fields[0], 10, 64) - reward.Percent, _ = strconv.ParseFloat(fields[1], 64) - reward.Immature, _ = strconv.ParseBool(fields[2]) - reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) - result = append(result, &reward) - } - } - return result -} - -func convertBlockResults(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), ":") - 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.RewardString = fields[7] - block.ImmatureReward = fields[7] - block.Login = fields[8] - block.immatureKey = v.Member.(string) - result = append(result, &block) - } - } - return result -} - -// Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} -// TS => diff, id, ms -func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { - now := util.MakeTimestamp() / 1000 - workers := make(map[string]Worker) - - for _, v := range blocks.Val() { - parts := strings.Split(v.Member.(string), ":") - id := parts[2] - worker := workers[id] - worker.Blocks++ - workers[id] = worker - } - - for _, v := range raw.Val() { - parts := strings.Split(v.Member.(string), ":") - share, _ := strconv.ParseInt(parts[0], 10, 64) - id := parts[1] - score := int64(v.Score) - worker := workers[id] - - // Add for large window - worker.TotalHR += share - worker.ValidShares = int64(4) - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - worker.WorkerStatus = int64(0) - worker.WorkerStatushas = int64(0) - //worker.StatleShares = int64(4) - //worker.InvalidShares = int64(4) - - // Add for small window if matches - if score >= now-window { - worker.HR += share - } - - if worker.LastBeat < score { - worker.LastBeat = score - } - if worker.startedAt > score || worker.startedAt == 0 { - worker.startedAt = score - } - workers[id] = worker - } - return workers -} - -func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]Miner) { - now := util.MakeTimestamp() / 1000 - miners := make(map[string]Miner) - totalHashrate := int64(0) - - for _, v := range raw.Val() { - parts := strings.Split(v.Member.(string), ":") - share, _ := strconv.ParseInt(parts[0], 10, 64) - id := parts[1] - score := int64(v.Score) - miner := miners[id] - miner.HR += share - - if miner.LastBeat < score { - miner.LastBeat = score - } - if miner.startedAt > score || miner.startedAt == 0 { - miner.startedAt = score - } - miners[id] = miner - } - - for id, miner := range miners { - timeOnline := now - miner.startedAt - if timeOnline < 600 { - timeOnline = 600 - } - - boundary := timeOnline - if timeOnline >= window { - boundary = window - } - miner.HR = miner.HR / boundary - - if miner.LastBeat < (now - window/2) { - miner.Offline = true - } - totalHashrate += miner.HR - miners[id] = miner - } - return totalHashrate, miners -} - -func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { - var result []map[string]interface{} - for _, v := range raw.Val() { - tx := make(map[string]interface{}) - tx["timestamp"] = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - tx["tx"] = fields[0] - // Individual or whole payments row - if len(fields) < 3 { - tx["amount"], _ = strconv.ParseInt(fields[1], 10, 64) - } else { - tx["address"] = fields[1] - tx["amount"], _ = strconv.ParseInt(fields[2], 10, 64) - } - result = append(result, tx) - } - var reverse []map[string]interface{} - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } - return result -} - -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 -} - -/* -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) - - 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) GetCurrentHashrate(login string) (int64, error) { - hashrate := r.client.HGet(r.formatKey("currenthashrate", login), "hashrate") - if hashrate.Err() == redis.Nil { - return 0, nil - } else if hashrate.Err() != nil { - return 0, hashrate.Err() - } - return hashrate.Int64() -} - -// Need a function to delete on round end or whatever, and another function to get. -func (r *RedisClient) ResetWorkerShareStatus() { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.HDel(r.formatKey("minerShare")) - return nil - }) - - // THis should do it ay ? - // fuck it -} - -// Don't know if this will work, returning three values, but let's see - -func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { - valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") - stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") - invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") - - if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { - return 0, 0, 0, nil - } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { - return 0, 0, 0, valid_shares.Err() - } - - v_c, _ := valid_shares.Int64() - s_c, _ := stale_shares.Int64() - i_c, _ := invalid_shares.Int64() - return v_c, s_c, i_c, nil - -} - -//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 - if valid { - valid_int = 1 - } - if stale { - stale_int = 1 - } - if invalid { - 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() - 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() - - 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)) // Would that work? - - return nil - }) - } //end else -} - -func (r *RedisClient) NumberStratumWorker(count int) { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.Del(r.formatKey("WorkersTot")) - tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) - //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) - return nil - }) -} From c8a087d0964bd2653bf1a7f71f10fc422aa4fa43 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:40:47 +0100 Subject: [PATCH 04/11] Update redis.go --- storage/redis.go | 1590 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1590 insertions(+) create mode 100644 storage/redis.go diff --git a/storage/redis.go b/storage/redis.go new file mode 100644 index 0000000..98f67f1 --- /dev/null +++ b/storage/redis.go @@ -0,0 +1,1590 @@ +package storage + +import ( + "fmt" + "math" + "math/big" + "sort" + "strconv" + "strings" + "time" + + "gopkg.in/redis.v3" + + "github.com/yuriy0803/open-etc-pool-friends/util" +) + +type Config struct { + Endpoint string `json:"endpoint"` + Password string `json:"password"` + Database int64 `json:"database"` + PoolSize int `json:"poolSize"` +} + +type RedisClient struct { + client *redis.Client + prefix string + pplns int64 +} + +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 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"` +} + +type RewardData struct { + Height int64 `json:"blockheight"` + Timestamp int64 `json:"timestamp"` + BlockHash string `json:"blockhash"` + Reward int64 `json:"reward"` + Percent float64 `json:"percent"` + Immature bool `json:"immature"` +} + +type BlockData struct { + Login string `json:"login"` + Height int64 `json:"height"` + Timestamp int64 `json:"timestamp"` + Difficulty int64 `json:"difficulty"` + TotalShares int64 `json:"shares"` + Uncle bool `json:"uncle"` + UncleHeight int64 `json:"uncleHeight"` + Orphan bool `json:"orphan"` + Hash string `json:"hash"` + Nonce string `json:"-"` + PowHash string `json:"-"` + MixDigest string `json:"-"` + Reward *big.Int `json:"-"` + ExtraReward *big.Int `json:"-"` + ImmatureReward string `json:"-"` + RewardString string `json:"reward"` + RoundHeight int64 `json:"-"` + candidateKey string + immatureKey string +} + +func (b *BlockData) RewardInShannon() int64 { + reward := new(big.Int).Div(b.Reward, util.Shannon) + return reward.Int64() +} + +func (b *BlockData) serializeHash() string { + if len(b.Hash) > 0 { + return b.Hash + } else { + return "0x0" + } +} + +func (b *BlockData) RoundKey() string { + return join(b.RoundHeight, b.Hash) +} + +func (b *BlockData) key() string { + return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login) +} + +type Miner struct { + LastBeat int64 `json:"lastBeat"` + HR int64 `json:"hr"` + Offline bool `json:"offline"` + startedAt int64 + Blocks int64 `json:"blocks"` +} + +type Worker struct { + Miner + TotalHR int64 `json:"hr2"` + ValidShares int64 `json:"valid"` + StaleShares int64 `json:"stale"` + InvalidShares int64 `json:"invalid"` + ValidPercent float64 `json:"v_per"` + StalePercent float64 `json:"s_per"` + InvalidPercent float64 `json:"i_per"` + WorkerStatus int64 `json:"w_stat"` + WorkerStatushas int64 `json:"w_stat_s"` +} + +func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { + client := redis.NewClient(&redis.Options{ + Addr: cfg.Endpoint, + Password: cfg.Password, + DB: cfg.Database, + PoolSize: cfg.PoolSize, + }) + return &RedisClient{client: client, prefix: prefix, pplns: pplns} +} + +func (r *RedisClient) Client() *redis.Client { + return r.client +} + +func (r *RedisClient) Check() (string, error) { + return r.client.Ping().Result() +} + +func (r *RedisClient) BgSave() (string, error) { + return r.client.BgSave().Result() +} + +// Always returns list of addresses. If Redis fails it will return empty list. +func (r *RedisClient) GetBlacklist() ([]string, error) { + cmd := r.client.SMembers(r.formatKey("blacklist")) + if cmd.Err() != nil { + return []string{}, cmd.Err() + } + return cmd.Val(), nil +} + +// Always returns list of IPs. If Redis fails it will return empty list. +func (r *RedisClient) GetWhitelist() ([]string, error) { + cmd := r.client.SMembers(r.formatKey("whitelist")) + if cmd.Err() != nil { + return []string{}, cmd.Err() + } + 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) 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) 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() + + 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 (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 +} + +func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { + + 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 (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { + tx := r.client.Multi() + defer tx.Close() + + 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)) + 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, height uint64, window time.Duration) (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, window) + tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) + return nil + }) + return false, err +} + +func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (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, window) + tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) + tx.HDel(r.formatKey("stats"), "roundShares") + 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 + } + hashHex := strings.Join(params, ":") + s := join(hashHex, ts, roundDiff, totalShares, login) + 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, expire time.Duration) { + /* # 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 + */ + +// Moved get hostname to stratums + + + tx.LPush(r.formatKey("lastshares"), login) + tx.LTrim(r.formatKey("lastshares"), 0, r.pplns) + tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) + tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) + tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) + 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)) +} + +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 join(args ...interface{}) string { + s := make([]string, len(args)) + for i, v := range args { + switch v.(type) { + case string: + s[i] = v.(string) + case int64: + s[i] = strconv.FormatInt(v.(int64), 10) + case uint64: + s[i] = strconv.FormatUint(v.(uint64), 10) + case float64: + s[i] = strconv.FormatFloat(v.(float64), 'f', 0, 64) + case bool: + if v.(bool) { + s[i] = "1" + } 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 { + s[i] = n.String() + } else { + s[i] = "0" + } + default: + panic("Invalid type specified for conversion") + } + } + return strings.Join(s, ":") +} + +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 +} + +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 +} + +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) + if cmd.Err() != nil { + return nil, cmd.Err() + } + return convertRewardResults(cmd), nil +} + +func (r *RedisClient) GetRoundShares(height int64, nonce string) (map[string]int64, error) { + result := make(map[string]int64) + cmd := r.client.HGetAllMap(r.formatRound(height, nonce)) + if cmd.Err() != nil { + return nil, cmd.Err() + } + sharesMap, _ := cmd.Result() + for login, v := range sharesMap { + n, _ := strconv.ParseInt(v, 10, 64) + result[login] = n + } + return result, nil +} + +func (r *RedisClient) GetPayees() ([]string, error) { + payees := make(map[string]struct{}) + var result []string + var c int64 + + for { + var keys []string + var err error + c, keys, err = r.client.Scan(c, r.formatKey("miners", "*"), 100).Result() + if err != nil { + return nil, err + } + for _, row := range keys { + login := strings.Split(row, ":")[2] + payees[login] = struct{}{} + } + if c == 0 { + break + } + } + for login, _ := range payees { + result = append(result, login) + } + return result, nil +} + +func (r *RedisClient) GetTotalShares() (int64, error) { + cmd := r.client.LLen(r.formatKey("lastshares")) + if cmd.Err() == redis.Nil { + return 0, nil + } else if cmd.Err() != nil { + return 0, cmd.Err() + } + return cmd.Val(), nil +} + +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 + } else if cmd.Err() != nil { + return 0, cmd.Err() + } + return cmd.Int64() +} + +func (r *RedisClient) LockPayouts(login string, amount int64) error { + key := r.formatKey("payments", "lock") + result := r.client.SetNX(key, join(login, amount), 0).Val() + if !result { + return fmt.Errorf("Unable to acquire lock '%s'", key) + } + return nil +} + +func (r *RedisClient) UnlockPayouts() error { + key := r.formatKey("payments", "lock") + _, err := r.client.Del(key).Result() + return err +} + +func (r *RedisClient) IsPayoutsLocked() (bool, error) { + _, err := r.client.Get(r.formatKey("payments", "lock")).Result() + if err == redis.Nil { + return false, nil + } else if err != nil { + return false, err + } else { + return true, nil + } +} + +type PendingPayment struct { + Timestamp int64 `json:"timestamp"` + Amount int64 `json:"amount"` + Address string `json:"login"` +} + +func (r *RedisClient) GetPendingPayments() []*PendingPayment { + raw := r.client.ZRevRangeWithScores(r.formatKey("payments", "pending"), 0, -1) + var result []*PendingPayment + for _, v := range raw.Val() { + // timestamp -> "address:amount" + payment := PendingPayment{} + payment.Timestamp = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + payment.Address = fields[0] + payment.Amount, _ = strconv.ParseInt(fields[1], 10, 64) + result = append(result, &payment) + } + return result +} + +// Deduct miner's balance for payment +func (r *RedisClient) UpdateBalance(login string, amount int64) error { + tx := r.client.Multi() + defer tx.Close() + + ts := util.MakeTimestamp() / 1000 + + _, err := tx.Exec(func() error { + 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"), "pending", amount) + tx.ZAdd(r.formatKey("payments", "pending"), redis.Z{Score: float64(ts), Member: join(login, amount)}) + return nil + }) + return err +} + +func (r *RedisClient) RollbackBalance(login string, amount int64) error { + tx := r.client.Multi() + defer tx.Close() + + _, 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("finances"), "balance", amount) + 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 { + 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), "paid", amount) + 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.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 +} + +func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { + if amount <= 0 { + return nil + } + tx := r.client.Multi() + defer tx.Close() + + addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) + remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) + remscore := block.Timestamp - 3600*24*40 // Store the last 40 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 { + creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) + tx, err := r.client.Watch(creditKey) + // Must decrement immatures using existing log entry + immatureCredits := tx.HGetAllMap(creditKey) + if err != nil { + return err + } + defer tx.Close() + + ts := util.MakeTimestamp() / 1000 + value := join(block.Hash, ts, block.Reward) + + _, err = tx.Exec(func() error { + r.writeMaturedBlock(tx, block) + tx.ZAdd(r.formatKey("credits", "all"), redis.Z{Score: float64(block.Height), Member: value}) + + // Decrement immature balances + totalImmature := int64(0) + for login, amountString := range immatureCredits.Val() { + amount, _ := strconv.ParseInt(amountString, 10, 64) + totalImmature += amount + tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + } + + // Increment balances + total := int64(0) + for login, amount := range roundRewards { + 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)) + } + tx.Del(creditKey) + tx.HIncrBy(r.formatKey("finances"), "balance", total) + 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 +} + +func (r *RedisClient) WriteOrphan(block *BlockData) error { + creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) + tx, err := r.client.Watch(creditKey) + // Must decrement immatures using existing log entry + immatureCredits := tx.HGetAllMap(creditKey) + if err != nil { + return err + } + defer tx.Close() + + _, err = tx.Exec(func() error { + r.writeMaturedBlock(tx, block) + + // Decrement immature balances + totalImmature := int64(0) + for login, amountString := range immatureCredits.Val() { + amount, _ := strconv.ParseInt(amountString, 10, 64) + totalImmature += amount + tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + } + tx.Del(creditKey) + tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) + return nil + }) + return err +} + +func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { + tx := r.client.Multi() + defer tx.Close() + + _, err := tx.Exec(func() error { + for _, block := range blocks { + r.writeImmatureBlock(tx, block) + } + return nil + }) + return err +} + +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) { + return r.client.Exists(r.formatKey("miners", login)).Result() +} + +func (r *RedisClient) GetMinerStats(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", "currentShares"), 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) + 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"] = csh + } + + return stats, nil +} + +// Try to convert all numeric strings to int64 +func convertStringMap(m map[string]string) map[string]interface{} { + result := make(map[string]interface{}) + var err error + for k, v := range m { + result[k], err = strconv.ParseInt(v, 10, 64) + if err != nil { + result[k] = v + } + } + return result +} + +// WARNING: Must run it periodically to flush out of window hashrate entries +func (r *RedisClient) FlushStaleStats(window, largeWindow time.Duration) (int64, error) { + now := util.MakeTimestamp() / 1000 + max := fmt.Sprint("(", now-int64(window/time.Second)) + total, err := r.client.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", max).Result() + if err != nil { + return total, err + } + + var c int64 + miners := make(map[string]struct{}) + max = fmt.Sprint("(", now-int64(largeWindow/time.Second)) + + for { + var keys []string + var err error + c, keys, err = r.client.Scan(c, r.formatKey("hashrate", "*"), 100).Result() + if err != nil { + return total, err + } + for _, row := range keys { + login := strings.Split(row, ":")[2] + if _, ok := miners[login]; !ok { + n, err := r.client.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", max).Result() + if err != nil { + return total, err + } + miners[login] = struct{}{} + total += n + } + } + if c == 0 { + break + } + } + return total, 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.HGet(r.formatKey("paymentsTotal"), "all") + tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) + tx.LLen(r.formatKey("lastshares")) + tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) + return nil + }) + + if (err != nil) && (err != redis.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() + + immature := convertBlockResults(cmds[4].(*redis.ZSliceCmd)) + stats["immature"] = immature + stats["immatureTotal"] = cmds[7].(*redis.IntCmd).Val() + + matured := convertBlockResults(cmds[5].(*redis.ZSliceCmd)) + stats["matured"] = matured + stats["maturedTotal"] = cmds[8].(*redis.IntCmd).Val() + + payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) + stats["payments"] = payments + stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() + + finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) + stats["finders"] = finders + + totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) + stats["miners"] = miners + stats["minersTotal"] = len(miners) + stats["hashrate"] = totalHashrate + return stats, nil +} + +func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login string) (map[string]interface{}, error) { + smallWindow := int64(sWindow / time.Second) + largeWindow := int64(lWindow / 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", login), "-inf", fmt.Sprint("(", now-largeWindow)) + tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) + 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) + return nil + }) + + if err != nil { + return nil, err + } + + totalHashrate := int64(0) + currentHashrate := int64(0) + online := int64(0) + offline := int64(0) + workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) + + for id, worker := range workers { + timeOnline := now - worker.startedAt + if timeOnline < 600 { + timeOnline = 600 + } + + boundary := timeOnline + if timeOnline >= smallWindow { + boundary = smallWindow + } + worker.HR = worker.HR / boundary + + boundary = timeOnline + if timeOnline >= largeWindow { + boundary = largeWindow + } + worker.TotalHR = worker.TotalHR / boundary + + if worker.LastBeat < (now - smallWindow/2) { + worker.Offline = true + offline++ + } else { + online++ + } + + blocks := cmds[4].(*redis.ZSliceCmd).Val() + + for _, val := range blocks { + parts := strings.Split(val.Member.(string), ":") + rig := parts[2] + if id == rig { + str := fmt.Sprint(val.Member.(string)) + if worker.LastBeat < (now - largeWindow) { + tx.ZRem(r.formatKey("worker", "blocks", login), str) + } + } + } + + currentHashrate += worker.HR + totalHashrate += worker.TotalHR + valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) + worker.ValidShares = int64(5) + worker.StaleShares = int64(5) + worker.InvalidShares = int64(5) + worker.ValidShares = valid_share + worker.StaleShares = stale_share + worker.InvalidShares = invalid_share + //test percentage + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + tot_share := int64(0) + tot_share += valid_share + tot_share += stale_share + tot_share += invalid_share + if tot_share > 0 { + d := float64(100) + //tot_share += ////error + cost_per := float64(tot_share) / d + v_per := float64(valid_share) / cost_per + worker.ValidPercent = toFixed(v_per, 1) + s_per := float64(stale_share) / cost_per + worker.StalePercent = toFixed(s_per, 1) + i_per := float64(invalid_share) / cost_per + worker.InvalidPercent = toFixed(i_per, 1) + } else { + worker.ValidPercent = toFixed(0, 1) + worker.StalePercent = toFixed(0, 1) + worker.InvalidPercent = toFixed(0, 1) + } + w_stat := int64(0) //test worker large hashrate indicator + if worker.HR >= worker.TotalHR { + w_stat = 1 + worker.WorkerStatus = w_stat + } else if worker.HR < worker.TotalHR { + w_stat = 0 + worker.WorkerStatus = w_stat + } + ///test small hr + tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") + + if tot_w.Err() == redis.Nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, nil + } else if tot_w.Err() != nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, tot_w.Err() + } + + last_hr, _ := tot_w.Int64() + w_stat_s := int64(0) //test worker hashrate indicator + if worker.HR > last_hr { + w_stat_s = 1 + worker.WorkerStatushas = w_stat_s + } else if worker.HR <= last_hr { + w_stat_s = 0 + worker.WorkerStatushas = w_stat_s + } + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) + workers[id] = worker + } + + stats["workers"] = workers + stats["workersTotal"] = len(workers) + stats["workersOnline"] = online + stats["workersOffline"] = offline + stats["hashrate"] = totalHashrate + stats["currentHashrate"] = currentHashrate + + stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 + rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all + + var dorew []*SumRewardData + dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 12 hours", Interval: 3600 * 12, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 24 hours", Interval: 3600 * 24, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 7 days", Interval: 3600 * 24 * 7, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 30 days", Interval: 3600 * 24 * 30, Offset: 0}) + + for _, reward := range rewards { + + for _, dore := range dorew { + dore.Reward += 0 + dore.Blocks += 0 + if reward.Timestamp > now-dore.Interval { + dore.Reward += reward.Reward + dore.Blocks++ + } + } + } + stats["sumrewards"] = dorew + stats["24hreward"] = dorew[2].Reward + return stats, nil +} + +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +func toFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} + +func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { + stats := make(map[string]interface{}) + + tx := r.client.Multi() + defer tx.Close() + + max := int64(windows[len(windows)-1]) + + cmds, err := tx.Exec(func() error { + tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) + tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, max-1) + return nil + }) + if err != nil { + return stats, err + } + blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd), cmds[1].(*redis.ZSliceCmd)) + + calcLuck := func(max int) (int, float64, float64, float64) { + var total int + var sharesDiff, uncles, orphans float64 + for i, block := range blocks { + if i > (max - 1) { + break + } + if block.Uncle { + uncles++ + } + if block.Orphan { + orphans++ + } + sharesDiff += float64(block.TotalShares) / float64(block.Difficulty) + total++ + } + if total > 0 { + sharesDiff /= float64(total) + uncles /= float64(total) + orphans /= float64(total) + } + return total, sharesDiff, uncles, orphans + } + for _, max := range windows { + total, sharesDiff, uncleRate, orphanRate := calcLuck(max) + row := map[string]float64{ + "luck": sharesDiff, "uncleRate": uncleRate, "orphanRate": orphanRate, + } + stats[strconv.Itoa(total)] = row + if total < max { + 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 + } + 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 +} + +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() { + // "nonce:powHash:mixDigest:timestamp:diff:totalShares" + block := BlockData{} + block.Height = int64(v.Score) + block.RoundHeight = block.Height + fields := strings.Split(v.Member.(string), ":") + 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.Login = fields[6] + block.candidateKey = v.Member.(string) + result = append(result, &block) + } + return result +} + +func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { + var result []*RewardData + for _, row := range rows { + for _, v := range row.Val() { + // "amount:percent:immature:block.Hash:block.height" + reward := RewardData{} + reward.Timestamp = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + //block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64) + reward.BlockHash = fields[3] + reward.Reward, _ = strconv.ParseInt(fields[0], 10, 64) + reward.Percent, _ = strconv.ParseFloat(fields[1], 64) + reward.Immature, _ = strconv.ParseBool(fields[2]) + reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) + result = append(result, &reward) + } + } + return result +} + +func convertBlockResults(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), ":") + 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.RewardString = fields[7] + block.ImmatureReward = fields[7] + block.Login = fields[8] + block.immatureKey = v.Member.(string) + result = append(result, &block) + } + } + return result +} + +// Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} +// TS => diff, id, ms +func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { + now := util.MakeTimestamp() / 1000 + workers := make(map[string]Worker) + + for _, v := range blocks.Val() { + parts := strings.Split(v.Member.(string), ":") + id := parts[2] + worker := workers[id] + worker.Blocks++ + workers[id] = worker + } + + for _, v := range raw.Val() { + parts := strings.Split(v.Member.(string), ":") + share, _ := strconv.ParseInt(parts[0], 10, 64) + id := parts[1] + score := int64(v.Score) + worker := workers[id] + + // Add for large window + worker.TotalHR += share + worker.ValidShares = int64(4) + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + worker.WorkerStatus = int64(0) + worker.WorkerStatushas = int64(0) + //worker.StatleShares = int64(4) + //worker.InvalidShares = int64(4) + + // Add for small window if matches + if score >= now-window { + worker.HR += share + } + + if worker.LastBeat < score { + worker.LastBeat = score + } + if worker.startedAt > score || worker.startedAt == 0 { + worker.startedAt = score + } + workers[id] = worker + } + return workers +} + +func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]Miner) { + now := util.MakeTimestamp() / 1000 + miners := make(map[string]Miner) + totalHashrate := int64(0) + + for _, v := range raw.Val() { + parts := strings.Split(v.Member.(string), ":") + share, _ := strconv.ParseInt(parts[0], 10, 64) + id := parts[1] + score := int64(v.Score) + miner := miners[id] + miner.HR += share + + if miner.LastBeat < score { + miner.LastBeat = score + } + if miner.startedAt > score || miner.startedAt == 0 { + miner.startedAt = score + } + miners[id] = miner + } + + for id, miner := range miners { + timeOnline := now - miner.startedAt + if timeOnline < 600 { + timeOnline = 600 + } + + boundary := timeOnline + if timeOnline >= window { + boundary = window + } + miner.HR = miner.HR / boundary + + if miner.LastBeat < (now - window/2) { + miner.Offline = true + } + totalHashrate += miner.HR + miners[id] = miner + } + return totalHashrate, miners +} + +func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { + var result []map[string]interface{} + for _, v := range raw.Val() { + tx := make(map[string]interface{}) + tx["timestamp"] = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + tx["tx"] = fields[0] + // Individual or whole payments row + if len(fields) < 3 { + tx["amount"], _ = strconv.ParseInt(fields[1], 10, 64) + } else { + tx["address"] = fields[1] + tx["amount"], _ = strconv.ParseInt(fields[2], 10, 64) + } + result = append(result, tx) + } + var reverse []map[string]interface{} + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return result +} + +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 +} + +/* +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) + + 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) GetCurrentHashrate(login string) (int64, error) { + hashrate := r.client.HGet(r.formatKey("currenthashrate", login), "hashrate") + if hashrate.Err() == redis.Nil { + return 0, nil + } else if hashrate.Err() != nil { + return 0, hashrate.Err() + } + return hashrate.Int64() +} + +// Need a function to delete on round end or whatever, and another function to get. +func (r *RedisClient) ResetWorkerShareStatus() { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.HDel(r.formatKey("minerShare")) + return nil + }) + + // THis should do it ay ? + // fuck it +} + +// Don't know if this will work, returning three values, but let's see + +func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { + valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") + stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") + invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") + + if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { + return 0, 0, 0, nil + } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { + return 0, 0, 0, valid_shares.Err() + } + + v_c, _ := valid_shares.Int64() + s_c, _ := stale_shares.Int64() + i_c, _ := invalid_shares.Int64() + return v_c, s_c, i_c, nil + +} + +//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 + if valid { + valid_int = 1 + } + if stale { + stale_int = 1 + } + if invalid { + 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() + 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() + + 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)) // Would that work? + + return nil + }) + } //end else +} + +func (r *RedisClient) NumberStratumWorker(count int) { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.Del(r.formatKey("WorkersTot")) + tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) + //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) + return nil + }) +} From 850a57f878e044c4fc2da822d7ef5e333ede8de9 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:43:17 +0100 Subject: [PATCH 05/11] Update redis.go --- storage/redis.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/storage/redis.go b/storage/redis.go index 98f67f1..86c87db 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -440,15 +440,10 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD } func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, expire time.Duration) { - /* # 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 - */ - -// Moved get hostname to stratums - - - tx.LPush(r.formatKey("lastshares"), login) + 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("shares", "roundCurrent"), login, diff) tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) From 3294324ae6858c8b19ee8c46ff82ba5328454560 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:48:42 +0100 Subject: [PATCH 06/11] Update api.json --- api.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.json b/api.json index c39df18..bc17b13 100644 --- a/api.json +++ b/api.json @@ -2,7 +2,7 @@ "threads": 4, "coin": "etc", "name": "main", - "pplns": 9000, + "pplns": 9000, "proxy": { "enabled": true, From 74d7723f3f24e644e45ccc5cdf8104f5be5167e2 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:55:38 +0100 Subject: [PATCH 07/11] Update redis.go --- storage/redis.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/storage/redis.go b/storage/redis.go index 86c87db..e851f9d 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -440,10 +440,22 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD } func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, expire time.Duration) { +<<<<<<< HEAD times := int(diff / 1000000000) for i := 0; i < times; i++ { tx.LPush(r.formatKey("lastshares"), login) } +======= + /* # 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 + */ + +// Moved get hostname to stratums + + + tx.LPush(r.formatKey("lastshares"), login) +>>>>>>> parent of 159b730 (Update redis.go) tx.LTrim(r.formatKey("lastshares"), 0, r.pplns) tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) @@ -1083,6 +1095,17 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login worker.ValidPercent = toFixed(0, 1) worker.StalePercent = toFixed(0, 1) worker.InvalidPercent = toFixed(0, 1) +<<<<<<< HEAD + } + w_stat := int64(0) //test worker large hashrate indicator + if worker.HR >= worker.TotalHR { + w_stat = 1 + worker.WorkerStatus = w_stat + } else if worker.HR < worker.TotalHR { + w_stat = 0 + worker.WorkerStatus = w_stat + } +======= } w_stat := int64(0) //test worker large hashrate indicator if worker.HR >= worker.TotalHR { @@ -1092,6 +1115,7 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login w_stat = 0 worker.WorkerStatus = w_stat } +>>>>>>> parent of 159b730 (Update redis.go) ///test small hr tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") From 150e92e11fa3aa9938cc0dbef8a8438b49758ad7 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:56:26 +0100 Subject: [PATCH 08/11] Update redis.go --- storage/redis.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/storage/redis.go b/storage/redis.go index e851f9d..86c87db 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -440,22 +440,10 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD } func (r *RedisClient) writeShare(tx *redis.Multi, ms, ts int64, login, id string, diff int64, expire time.Duration) { -<<<<<<< HEAD times := int(diff / 1000000000) for i := 0; i < times; i++ { tx.LPush(r.formatKey("lastshares"), login) } -======= - /* # 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 - */ - -// Moved get hostname to stratums - - - tx.LPush(r.formatKey("lastshares"), login) ->>>>>>> parent of 159b730 (Update redis.go) tx.LTrim(r.formatKey("lastshares"), 0, r.pplns) tx.HIncrBy(r.formatKey("shares", "roundCurrent"), login, diff) tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) @@ -1095,17 +1083,6 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login worker.ValidPercent = toFixed(0, 1) worker.StalePercent = toFixed(0, 1) worker.InvalidPercent = toFixed(0, 1) -<<<<<<< HEAD - } - w_stat := int64(0) //test worker large hashrate indicator - if worker.HR >= worker.TotalHR { - w_stat = 1 - worker.WorkerStatus = w_stat - } else if worker.HR < worker.TotalHR { - w_stat = 0 - worker.WorkerStatus = w_stat - } -======= } w_stat := int64(0) //test worker large hashrate indicator if worker.HR >= worker.TotalHR { @@ -1115,7 +1092,6 @@ func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login w_stat = 0 worker.WorkerStatus = w_stat } ->>>>>>> parent of 159b730 (Update redis.go) ///test small hr tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") From aea010fd27f34c84cf1036d239947e9f92cfbe26 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:57:25 +0100 Subject: [PATCH 09/11] Delete redis.go --- storage/redis.go | 1585 ---------------------------------------------- 1 file changed, 1585 deletions(-) delete mode 100644 storage/redis.go diff --git a/storage/redis.go b/storage/redis.go deleted file mode 100644 index 86c87db..0000000 --- a/storage/redis.go +++ /dev/null @@ -1,1585 +0,0 @@ -package storage - -import ( - "fmt" - "math" - "math/big" - "sort" - "strconv" - "strings" - "time" - - "gopkg.in/redis.v3" - - "github.com/yuriy0803/open-etc-pool-friends/util" -) - -type Config struct { - Endpoint string `json:"endpoint"` - Password string `json:"password"` - Database int64 `json:"database"` - PoolSize int `json:"poolSize"` -} - -type RedisClient struct { - client *redis.Client - prefix string - pplns int64 -} - -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 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"` -} - -type RewardData struct { - Height int64 `json:"blockheight"` - Timestamp int64 `json:"timestamp"` - BlockHash string `json:"blockhash"` - Reward int64 `json:"reward"` - Percent float64 `json:"percent"` - Immature bool `json:"immature"` -} - -type BlockData struct { - Login string `json:"login"` - Height int64 `json:"height"` - Timestamp int64 `json:"timestamp"` - Difficulty int64 `json:"difficulty"` - TotalShares int64 `json:"shares"` - Uncle bool `json:"uncle"` - UncleHeight int64 `json:"uncleHeight"` - Orphan bool `json:"orphan"` - Hash string `json:"hash"` - Nonce string `json:"-"` - PowHash string `json:"-"` - MixDigest string `json:"-"` - Reward *big.Int `json:"-"` - ExtraReward *big.Int `json:"-"` - ImmatureReward string `json:"-"` - RewardString string `json:"reward"` - RoundHeight int64 `json:"-"` - candidateKey string - immatureKey string -} - -func (b *BlockData) RewardInShannon() int64 { - reward := new(big.Int).Div(b.Reward, util.Shannon) - return reward.Int64() -} - -func (b *BlockData) serializeHash() string { - if len(b.Hash) > 0 { - return b.Hash - } else { - return "0x0" - } -} - -func (b *BlockData) RoundKey() string { - return join(b.RoundHeight, b.Hash) -} - -func (b *BlockData) key() string { - return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login) -} - -type Miner struct { - LastBeat int64 `json:"lastBeat"` - HR int64 `json:"hr"` - Offline bool `json:"offline"` - startedAt int64 - Blocks int64 `json:"blocks"` -} - -type Worker struct { - Miner - TotalHR int64 `json:"hr2"` - ValidShares int64 `json:"valid"` - StaleShares int64 `json:"stale"` - InvalidShares int64 `json:"invalid"` - ValidPercent float64 `json:"v_per"` - StalePercent float64 `json:"s_per"` - InvalidPercent float64 `json:"i_per"` - WorkerStatus int64 `json:"w_stat"` - WorkerStatushas int64 `json:"w_stat_s"` -} - -func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { - client := redis.NewClient(&redis.Options{ - Addr: cfg.Endpoint, - Password: cfg.Password, - DB: cfg.Database, - PoolSize: cfg.PoolSize, - }) - return &RedisClient{client: client, prefix: prefix, pplns: pplns} -} - -func (r *RedisClient) Client() *redis.Client { - return r.client -} - -func (r *RedisClient) Check() (string, error) { - return r.client.Ping().Result() -} - -func (r *RedisClient) BgSave() (string, error) { - return r.client.BgSave().Result() -} - -// Always returns list of addresses. If Redis fails it will return empty list. -func (r *RedisClient) GetBlacklist() ([]string, error) { - cmd := r.client.SMembers(r.formatKey("blacklist")) - if cmd.Err() != nil { - return []string{}, cmd.Err() - } - return cmd.Val(), nil -} - -// Always returns list of IPs. If Redis fails it will return empty list. -func (r *RedisClient) GetWhitelist() ([]string, error) { - cmd := r.client.SMembers(r.formatKey("whitelist")) - if cmd.Err() != nil { - return []string{}, cmd.Err() - } - 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) 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) 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() - - 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 (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 -} - -func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { - - 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 (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { - tx := r.client.Multi() - defer tx.Close() - - 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)) - 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, height uint64, window time.Duration) (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, window) - tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) - return nil - }) - return false, err -} - -func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (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, window) - tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) - tx.HDel(r.formatKey("stats"), "roundShares") - 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 - } - hashHex := strings.Join(params, ":") - s := join(hashHex, ts, roundDiff, totalShares, login) - 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, expire time.Duration) { - 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("shares", "roundCurrent"), login, diff) - tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) - tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) - 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)) -} - -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 join(args ...interface{}) string { - s := make([]string, len(args)) - for i, v := range args { - switch v.(type) { - case string: - s[i] = v.(string) - case int64: - s[i] = strconv.FormatInt(v.(int64), 10) - case uint64: - s[i] = strconv.FormatUint(v.(uint64), 10) - case float64: - s[i] = strconv.FormatFloat(v.(float64), 'f', 0, 64) - case bool: - if v.(bool) { - s[i] = "1" - } 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 { - s[i] = n.String() - } else { - s[i] = "0" - } - default: - panic("Invalid type specified for conversion") - } - } - return strings.Join(s, ":") -} - -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 -} - -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 -} - -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) - if cmd.Err() != nil { - return nil, cmd.Err() - } - return convertRewardResults(cmd), nil -} - -func (r *RedisClient) GetRoundShares(height int64, nonce string) (map[string]int64, error) { - result := make(map[string]int64) - cmd := r.client.HGetAllMap(r.formatRound(height, nonce)) - if cmd.Err() != nil { - return nil, cmd.Err() - } - sharesMap, _ := cmd.Result() - for login, v := range sharesMap { - n, _ := strconv.ParseInt(v, 10, 64) - result[login] = n - } - return result, nil -} - -func (r *RedisClient) GetPayees() ([]string, error) { - payees := make(map[string]struct{}) - var result []string - var c int64 - - for { - var keys []string - var err error - c, keys, err = r.client.Scan(c, r.formatKey("miners", "*"), 100).Result() - if err != nil { - return nil, err - } - for _, row := range keys { - login := strings.Split(row, ":")[2] - payees[login] = struct{}{} - } - if c == 0 { - break - } - } - for login, _ := range payees { - result = append(result, login) - } - return result, nil -} - -func (r *RedisClient) GetTotalShares() (int64, error) { - cmd := r.client.LLen(r.formatKey("lastshares")) - if cmd.Err() == redis.Nil { - return 0, nil - } else if cmd.Err() != nil { - return 0, cmd.Err() - } - return cmd.Val(), nil -} - -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 - } else if cmd.Err() != nil { - return 0, cmd.Err() - } - return cmd.Int64() -} - -func (r *RedisClient) LockPayouts(login string, amount int64) error { - key := r.formatKey("payments", "lock") - result := r.client.SetNX(key, join(login, amount), 0).Val() - if !result { - return fmt.Errorf("Unable to acquire lock '%s'", key) - } - return nil -} - -func (r *RedisClient) UnlockPayouts() error { - key := r.formatKey("payments", "lock") - _, err := r.client.Del(key).Result() - return err -} - -func (r *RedisClient) IsPayoutsLocked() (bool, error) { - _, err := r.client.Get(r.formatKey("payments", "lock")).Result() - if err == redis.Nil { - return false, nil - } else if err != nil { - return false, err - } else { - return true, nil - } -} - -type PendingPayment struct { - Timestamp int64 `json:"timestamp"` - Amount int64 `json:"amount"` - Address string `json:"login"` -} - -func (r *RedisClient) GetPendingPayments() []*PendingPayment { - raw := r.client.ZRevRangeWithScores(r.formatKey("payments", "pending"), 0, -1) - var result []*PendingPayment - for _, v := range raw.Val() { - // timestamp -> "address:amount" - payment := PendingPayment{} - payment.Timestamp = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - payment.Address = fields[0] - payment.Amount, _ = strconv.ParseInt(fields[1], 10, 64) - result = append(result, &payment) - } - return result -} - -// Deduct miner's balance for payment -func (r *RedisClient) UpdateBalance(login string, amount int64) error { - tx := r.client.Multi() - defer tx.Close() - - ts := util.MakeTimestamp() / 1000 - - _, err := tx.Exec(func() error { - 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"), "pending", amount) - tx.ZAdd(r.formatKey("payments", "pending"), redis.Z{Score: float64(ts), Member: join(login, amount)}) - return nil - }) - return err -} - -func (r *RedisClient) RollbackBalance(login string, amount int64) error { - tx := r.client.Multi() - defer tx.Close() - - _, 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("finances"), "balance", amount) - 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 { - 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), "paid", amount) - 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.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 -} - -func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { - if amount <= 0 { - return nil - } - tx := r.client.Multi() - defer tx.Close() - - addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) - remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) - remscore := block.Timestamp - 3600*24*40 // Store the last 40 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 { - creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) - tx, err := r.client.Watch(creditKey) - // Must decrement immatures using existing log entry - immatureCredits := tx.HGetAllMap(creditKey) - if err != nil { - return err - } - defer tx.Close() - - ts := util.MakeTimestamp() / 1000 - value := join(block.Hash, ts, block.Reward) - - _, err = tx.Exec(func() error { - r.writeMaturedBlock(tx, block) - tx.ZAdd(r.formatKey("credits", "all"), redis.Z{Score: float64(block.Height), Member: value}) - - // Decrement immature balances - totalImmature := int64(0) - for login, amountString := range immatureCredits.Val() { - amount, _ := strconv.ParseInt(amountString, 10, 64) - totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) - } - - // Increment balances - total := int64(0) - for login, amount := range roundRewards { - 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)) - } - tx.Del(creditKey) - tx.HIncrBy(r.formatKey("finances"), "balance", total) - 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 -} - -func (r *RedisClient) WriteOrphan(block *BlockData) error { - creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) - tx, err := r.client.Watch(creditKey) - // Must decrement immatures using existing log entry - immatureCredits := tx.HGetAllMap(creditKey) - if err != nil { - return err - } - defer tx.Close() - - _, err = tx.Exec(func() error { - r.writeMaturedBlock(tx, block) - - // Decrement immature balances - totalImmature := int64(0) - for login, amountString := range immatureCredits.Val() { - amount, _ := strconv.ParseInt(amountString, 10, 64) - totalImmature += amount - tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) - } - tx.Del(creditKey) - tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) - return nil - }) - return err -} - -func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { - tx := r.client.Multi() - defer tx.Close() - - _, err := tx.Exec(func() error { - for _, block := range blocks { - r.writeImmatureBlock(tx, block) - } - return nil - }) - return err -} - -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) { - return r.client.Exists(r.formatKey("miners", login)).Result() -} - -func (r *RedisClient) GetMinerStats(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", "currentShares"), 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) - 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"] = csh - } - - return stats, nil -} - -// Try to convert all numeric strings to int64 -func convertStringMap(m map[string]string) map[string]interface{} { - result := make(map[string]interface{}) - var err error - for k, v := range m { - result[k], err = strconv.ParseInt(v, 10, 64) - if err != nil { - result[k] = v - } - } - return result -} - -// WARNING: Must run it periodically to flush out of window hashrate entries -func (r *RedisClient) FlushStaleStats(window, largeWindow time.Duration) (int64, error) { - now := util.MakeTimestamp() / 1000 - max := fmt.Sprint("(", now-int64(window/time.Second)) - total, err := r.client.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", max).Result() - if err != nil { - return total, err - } - - var c int64 - miners := make(map[string]struct{}) - max = fmt.Sprint("(", now-int64(largeWindow/time.Second)) - - for { - var keys []string - var err error - c, keys, err = r.client.Scan(c, r.formatKey("hashrate", "*"), 100).Result() - if err != nil { - return total, err - } - for _, row := range keys { - login := strings.Split(row, ":")[2] - if _, ok := miners[login]; !ok { - n, err := r.client.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", max).Result() - if err != nil { - return total, err - } - miners[login] = struct{}{} - total += n - } - } - if c == 0 { - break - } - } - return total, 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.HGet(r.formatKey("paymentsTotal"), "all") - tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) - tx.LLen(r.formatKey("lastshares")) - tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) - return nil - }) - - if (err != nil) && (err != redis.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() - - immature := convertBlockResults(cmds[4].(*redis.ZSliceCmd)) - stats["immature"] = immature - stats["immatureTotal"] = cmds[7].(*redis.IntCmd).Val() - - matured := convertBlockResults(cmds[5].(*redis.ZSliceCmd)) - stats["matured"] = matured - stats["maturedTotal"] = cmds[8].(*redis.IntCmd).Val() - - payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) - stats["payments"] = payments - stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() - - finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) - stats["finders"] = finders - - totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) - stats["miners"] = miners - stats["minersTotal"] = len(miners) - stats["hashrate"] = totalHashrate - return stats, nil -} - -func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login string) (map[string]interface{}, error) { - smallWindow := int64(sWindow / time.Second) - largeWindow := int64(lWindow / 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", login), "-inf", fmt.Sprint("(", now-largeWindow)) - tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) - 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) - return nil - }) - - if err != nil { - return nil, err - } - - totalHashrate := int64(0) - currentHashrate := int64(0) - online := int64(0) - offline := int64(0) - workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) - - for id, worker := range workers { - timeOnline := now - worker.startedAt - if timeOnline < 600 { - timeOnline = 600 - } - - boundary := timeOnline - if timeOnline >= smallWindow { - boundary = smallWindow - } - worker.HR = worker.HR / boundary - - boundary = timeOnline - if timeOnline >= largeWindow { - boundary = largeWindow - } - worker.TotalHR = worker.TotalHR / boundary - - if worker.LastBeat < (now - smallWindow/2) { - worker.Offline = true - offline++ - } else { - online++ - } - - blocks := cmds[4].(*redis.ZSliceCmd).Val() - - for _, val := range blocks { - parts := strings.Split(val.Member.(string), ":") - rig := parts[2] - if id == rig { - str := fmt.Sprint(val.Member.(string)) - if worker.LastBeat < (now - largeWindow) { - tx.ZRem(r.formatKey("worker", "blocks", login), str) - } - } - } - - currentHashrate += worker.HR - totalHashrate += worker.TotalHR - valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) - worker.ValidShares = int64(5) - worker.StaleShares = int64(5) - worker.InvalidShares = int64(5) - worker.ValidShares = valid_share - worker.StaleShares = stale_share - worker.InvalidShares = invalid_share - //test percentage - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - tot_share := int64(0) - tot_share += valid_share - tot_share += stale_share - tot_share += invalid_share - if tot_share > 0 { - d := float64(100) - //tot_share += ////error - cost_per := float64(tot_share) / d - v_per := float64(valid_share) / cost_per - worker.ValidPercent = toFixed(v_per, 1) - s_per := float64(stale_share) / cost_per - worker.StalePercent = toFixed(s_per, 1) - i_per := float64(invalid_share) / cost_per - worker.InvalidPercent = toFixed(i_per, 1) - } else { - worker.ValidPercent = toFixed(0, 1) - worker.StalePercent = toFixed(0, 1) - worker.InvalidPercent = toFixed(0, 1) - } - w_stat := int64(0) //test worker large hashrate indicator - if worker.HR >= worker.TotalHR { - w_stat = 1 - worker.WorkerStatus = w_stat - } else if worker.HR < worker.TotalHR { - w_stat = 0 - worker.WorkerStatus = w_stat - } - ///test small hr - tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") - - if tot_w.Err() == redis.Nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, nil - } else if tot_w.Err() != nil { - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) - //return nil, tot_w.Err() - } - - last_hr, _ := tot_w.Int64() - w_stat_s := int64(0) //test worker hashrate indicator - if worker.HR > last_hr { - w_stat_s = 1 - worker.WorkerStatushas = w_stat_s - } else if worker.HR <= last_hr { - w_stat_s = 0 - worker.WorkerStatushas = w_stat_s - } - tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) - workers[id] = worker - } - - stats["workers"] = workers - stats["workersTotal"] = len(workers) - stats["workersOnline"] = online - stats["workersOffline"] = offline - stats["hashrate"] = totalHashrate - stats["currentHashrate"] = currentHashrate - - stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 - rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all - - var dorew []*SumRewardData - dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 12 hours", Interval: 3600 * 12, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 24 hours", Interval: 3600 * 24, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 7 days", Interval: 3600 * 24 * 7, Offset: 0}) - dorew = append(dorew, &SumRewardData{Name: "Last 30 days", Interval: 3600 * 24 * 30, Offset: 0}) - - for _, reward := range rewards { - - for _, dore := range dorew { - dore.Reward += 0 - dore.Blocks += 0 - if reward.Timestamp > now-dore.Interval { - dore.Reward += reward.Reward - dore.Blocks++ - } - } - } - stats["sumrewards"] = dorew - stats["24hreward"] = dorew[2].Reward - return stats, nil -} - -func round(num float64) int { - return int(num + math.Copysign(0.5, num)) -} - -func toFixed(num float64, precision int) float64 { - output := math.Pow(10, float64(precision)) - return float64(round(num*output)) / output -} - -func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { - stats := make(map[string]interface{}) - - tx := r.client.Multi() - defer tx.Close() - - max := int64(windows[len(windows)-1]) - - cmds, err := tx.Exec(func() error { - tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) - tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, max-1) - return nil - }) - if err != nil { - return stats, err - } - blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd), cmds[1].(*redis.ZSliceCmd)) - - calcLuck := func(max int) (int, float64, float64, float64) { - var total int - var sharesDiff, uncles, orphans float64 - for i, block := range blocks { - if i > (max - 1) { - break - } - if block.Uncle { - uncles++ - } - if block.Orphan { - orphans++ - } - sharesDiff += float64(block.TotalShares) / float64(block.Difficulty) - total++ - } - if total > 0 { - sharesDiff /= float64(total) - uncles /= float64(total) - orphans /= float64(total) - } - return total, sharesDiff, uncles, orphans - } - for _, max := range windows { - total, sharesDiff, uncleRate, orphanRate := calcLuck(max) - row := map[string]float64{ - "luck": sharesDiff, "uncleRate": uncleRate, "orphanRate": orphanRate, - } - stats[strconv.Itoa(total)] = row - if total < max { - 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 - } - 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 -} - -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() { - // "nonce:powHash:mixDigest:timestamp:diff:totalShares" - block := BlockData{} - block.Height = int64(v.Score) - block.RoundHeight = block.Height - fields := strings.Split(v.Member.(string), ":") - 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.Login = fields[6] - block.candidateKey = v.Member.(string) - result = append(result, &block) - } - return result -} - -func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { - var result []*RewardData - for _, row := range rows { - for _, v := range row.Val() { - // "amount:percent:immature:block.Hash:block.height" - reward := RewardData{} - reward.Timestamp = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - //block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64) - reward.BlockHash = fields[3] - reward.Reward, _ = strconv.ParseInt(fields[0], 10, 64) - reward.Percent, _ = strconv.ParseFloat(fields[1], 64) - reward.Immature, _ = strconv.ParseBool(fields[2]) - reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) - result = append(result, &reward) - } - } - return result -} - -func convertBlockResults(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), ":") - 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.RewardString = fields[7] - block.ImmatureReward = fields[7] - block.Login = fields[8] - block.immatureKey = v.Member.(string) - result = append(result, &block) - } - } - return result -} - -// Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} -// TS => diff, id, ms -func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { - now := util.MakeTimestamp() / 1000 - workers := make(map[string]Worker) - - for _, v := range blocks.Val() { - parts := strings.Split(v.Member.(string), ":") - id := parts[2] - worker := workers[id] - worker.Blocks++ - workers[id] = worker - } - - for _, v := range raw.Val() { - parts := strings.Split(v.Member.(string), ":") - share, _ := strconv.ParseInt(parts[0], 10, 64) - id := parts[1] - score := int64(v.Score) - worker := workers[id] - - // Add for large window - worker.TotalHR += share - worker.ValidShares = int64(4) - worker.ValidPercent = float64(0) - worker.StalePercent = float64(0) - worker.InvalidPercent = float64(0) - worker.WorkerStatus = int64(0) - worker.WorkerStatushas = int64(0) - //worker.StatleShares = int64(4) - //worker.InvalidShares = int64(4) - - // Add for small window if matches - if score >= now-window { - worker.HR += share - } - - if worker.LastBeat < score { - worker.LastBeat = score - } - if worker.startedAt > score || worker.startedAt == 0 { - worker.startedAt = score - } - workers[id] = worker - } - return workers -} - -func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]Miner) { - now := util.MakeTimestamp() / 1000 - miners := make(map[string]Miner) - totalHashrate := int64(0) - - for _, v := range raw.Val() { - parts := strings.Split(v.Member.(string), ":") - share, _ := strconv.ParseInt(parts[0], 10, 64) - id := parts[1] - score := int64(v.Score) - miner := miners[id] - miner.HR += share - - if miner.LastBeat < score { - miner.LastBeat = score - } - if miner.startedAt > score || miner.startedAt == 0 { - miner.startedAt = score - } - miners[id] = miner - } - - for id, miner := range miners { - timeOnline := now - miner.startedAt - if timeOnline < 600 { - timeOnline = 600 - } - - boundary := timeOnline - if timeOnline >= window { - boundary = window - } - miner.HR = miner.HR / boundary - - if miner.LastBeat < (now - window/2) { - miner.Offline = true - } - totalHashrate += miner.HR - miners[id] = miner - } - return totalHashrate, miners -} - -func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { - var result []map[string]interface{} - for _, v := range raw.Val() { - tx := make(map[string]interface{}) - tx["timestamp"] = int64(v.Score) - fields := strings.Split(v.Member.(string), ":") - tx["tx"] = fields[0] - // Individual or whole payments row - if len(fields) < 3 { - tx["amount"], _ = strconv.ParseInt(fields[1], 10, 64) - } else { - tx["address"] = fields[1] - tx["amount"], _ = strconv.ParseInt(fields[2], 10, 64) - } - result = append(result, tx) - } - var reverse []map[string]interface{} - for i := len(result) - 1; i >= 0; i-- { - reverse = append(reverse, result[i]) - } - return result -} - -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 -} - -/* -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) - - 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) GetCurrentHashrate(login string) (int64, error) { - hashrate := r.client.HGet(r.formatKey("currenthashrate", login), "hashrate") - if hashrate.Err() == redis.Nil { - return 0, nil - } else if hashrate.Err() != nil { - return 0, hashrate.Err() - } - return hashrate.Int64() -} - -// Need a function to delete on round end or whatever, and another function to get. -func (r *RedisClient) ResetWorkerShareStatus() { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.HDel(r.formatKey("minerShare")) - return nil - }) - - // THis should do it ay ? - // fuck it -} - -// Don't know if this will work, returning three values, but let's see - -func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { - valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") - stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") - invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") - - if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { - return 0, 0, 0, nil - } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { - return 0, 0, 0, valid_shares.Err() - } - - v_c, _ := valid_shares.Int64() - s_c, _ := stale_shares.Int64() - i_c, _ := invalid_shares.Int64() - return v_c, s_c, i_c, nil - -} - -//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 - if valid { - valid_int = 1 - } - if stale { - stale_int = 1 - } - if invalid { - 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() - 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() - - 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)) // Would that work? - - return nil - }) - } //end else -} - -func (r *RedisClient) NumberStratumWorker(count int) { - tx := r.client.Multi() - defer tx.Close() - - tx.Exec(func() error { - tx.Del(r.formatKey("WorkersTot")) - tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) - //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) - return nil - }) -} From 45c1218d6edb733e64afcdcebc32cef02338ee45 Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:57:52 +0100 Subject: [PATCH 10/11] Create redis.go --- storage/redis.go | 1585 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1585 insertions(+) create mode 100644 storage/redis.go diff --git a/storage/redis.go b/storage/redis.go new file mode 100644 index 0000000..86c87db --- /dev/null +++ b/storage/redis.go @@ -0,0 +1,1585 @@ +package storage + +import ( + "fmt" + "math" + "math/big" + "sort" + "strconv" + "strings" + "time" + + "gopkg.in/redis.v3" + + "github.com/yuriy0803/open-etc-pool-friends/util" +) + +type Config struct { + Endpoint string `json:"endpoint"` + Password string `json:"password"` + Database int64 `json:"database"` + PoolSize int `json:"poolSize"` +} + +type RedisClient struct { + client *redis.Client + prefix string + pplns int64 +} + +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 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"` +} + +type RewardData struct { + Height int64 `json:"blockheight"` + Timestamp int64 `json:"timestamp"` + BlockHash string `json:"blockhash"` + Reward int64 `json:"reward"` + Percent float64 `json:"percent"` + Immature bool `json:"immature"` +} + +type BlockData struct { + Login string `json:"login"` + Height int64 `json:"height"` + Timestamp int64 `json:"timestamp"` + Difficulty int64 `json:"difficulty"` + TotalShares int64 `json:"shares"` + Uncle bool `json:"uncle"` + UncleHeight int64 `json:"uncleHeight"` + Orphan bool `json:"orphan"` + Hash string `json:"hash"` + Nonce string `json:"-"` + PowHash string `json:"-"` + MixDigest string `json:"-"` + Reward *big.Int `json:"-"` + ExtraReward *big.Int `json:"-"` + ImmatureReward string `json:"-"` + RewardString string `json:"reward"` + RoundHeight int64 `json:"-"` + candidateKey string + immatureKey string +} + +func (b *BlockData) RewardInShannon() int64 { + reward := new(big.Int).Div(b.Reward, util.Shannon) + return reward.Int64() +} + +func (b *BlockData) serializeHash() string { + if len(b.Hash) > 0 { + return b.Hash + } else { + return "0x0" + } +} + +func (b *BlockData) RoundKey() string { + return join(b.RoundHeight, b.Hash) +} + +func (b *BlockData) key() string { + return join(b.UncleHeight, b.Orphan, b.Nonce, b.serializeHash(), b.Timestamp, b.Difficulty, b.TotalShares, b.Reward, b.Login) +} + +type Miner struct { + LastBeat int64 `json:"lastBeat"` + HR int64 `json:"hr"` + Offline bool `json:"offline"` + startedAt int64 + Blocks int64 `json:"blocks"` +} + +type Worker struct { + Miner + TotalHR int64 `json:"hr2"` + ValidShares int64 `json:"valid"` + StaleShares int64 `json:"stale"` + InvalidShares int64 `json:"invalid"` + ValidPercent float64 `json:"v_per"` + StalePercent float64 `json:"s_per"` + InvalidPercent float64 `json:"i_per"` + WorkerStatus int64 `json:"w_stat"` + WorkerStatushas int64 `json:"w_stat_s"` +} + +func NewRedisClient(cfg *Config, prefix string, pplns int64) *RedisClient { + client := redis.NewClient(&redis.Options{ + Addr: cfg.Endpoint, + Password: cfg.Password, + DB: cfg.Database, + PoolSize: cfg.PoolSize, + }) + return &RedisClient{client: client, prefix: prefix, pplns: pplns} +} + +func (r *RedisClient) Client() *redis.Client { + return r.client +} + +func (r *RedisClient) Check() (string, error) { + return r.client.Ping().Result() +} + +func (r *RedisClient) BgSave() (string, error) { + return r.client.BgSave().Result() +} + +// Always returns list of addresses. If Redis fails it will return empty list. +func (r *RedisClient) GetBlacklist() ([]string, error) { + cmd := r.client.SMembers(r.formatKey("blacklist")) + if cmd.Err() != nil { + return []string{}, cmd.Err() + } + return cmd.Val(), nil +} + +// Always returns list of IPs. If Redis fails it will return empty list. +func (r *RedisClient) GetWhitelist() ([]string, error) { + cmd := r.client.SMembers(r.formatKey("whitelist")) + if cmd.Err() != nil { + return []string{}, cmd.Err() + } + 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) 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) 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() + + 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 (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 +} + +func (r *RedisClient) GetPaymentCharts(login string) (stats []*PaymentCharts, err error) { + + 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 (r *RedisClient) WriteNodeState(id string, height uint64, diff *big.Int) error { + tx := r.client.Multi() + defer tx.Close() + + 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)) + 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, height uint64, window time.Duration) (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, window) + tx.HIncrBy(r.formatKey("stats"), "roundShares", diff) + return nil + }) + return false, err +} + +func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundDiff int64, height uint64, window time.Duration) (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, window) + tx.HSet(r.formatKey("stats"), "lastBlockFound", strconv.FormatInt(ts, 10)) + tx.HDel(r.formatKey("stats"), "roundShares") + 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 + } + hashHex := strings.Join(params, ":") + s := join(hashHex, ts, roundDiff, totalShares, login) + 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, expire time.Duration) { + 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("shares", "roundCurrent"), login, diff) + tx.ZAdd(r.formatKey("hashrate"), redis.Z{Score: float64(ts), Member: join(diff, login, id, ms)}) + tx.ZAdd(r.formatKey("hashrate", login), redis.Z{Score: float64(ts), Member: join(diff, id, ms)}) + 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)) +} + +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 join(args ...interface{}) string { + s := make([]string, len(args)) + for i, v := range args { + switch v.(type) { + case string: + s[i] = v.(string) + case int64: + s[i] = strconv.FormatInt(v.(int64), 10) + case uint64: + s[i] = strconv.FormatUint(v.(uint64), 10) + case float64: + s[i] = strconv.FormatFloat(v.(float64), 'f', 0, 64) + case bool: + if v.(bool) { + s[i] = "1" + } 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 { + s[i] = n.String() + } else { + s[i] = "0" + } + default: + panic("Invalid type specified for conversion") + } + } + return strings.Join(s, ":") +} + +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 +} + +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 +} + +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) + if cmd.Err() != nil { + return nil, cmd.Err() + } + return convertRewardResults(cmd), nil +} + +func (r *RedisClient) GetRoundShares(height int64, nonce string) (map[string]int64, error) { + result := make(map[string]int64) + cmd := r.client.HGetAllMap(r.formatRound(height, nonce)) + if cmd.Err() != nil { + return nil, cmd.Err() + } + sharesMap, _ := cmd.Result() + for login, v := range sharesMap { + n, _ := strconv.ParseInt(v, 10, 64) + result[login] = n + } + return result, nil +} + +func (r *RedisClient) GetPayees() ([]string, error) { + payees := make(map[string]struct{}) + var result []string + var c int64 + + for { + var keys []string + var err error + c, keys, err = r.client.Scan(c, r.formatKey("miners", "*"), 100).Result() + if err != nil { + return nil, err + } + for _, row := range keys { + login := strings.Split(row, ":")[2] + payees[login] = struct{}{} + } + if c == 0 { + break + } + } + for login, _ := range payees { + result = append(result, login) + } + return result, nil +} + +func (r *RedisClient) GetTotalShares() (int64, error) { + cmd := r.client.LLen(r.formatKey("lastshares")) + if cmd.Err() == redis.Nil { + return 0, nil + } else if cmd.Err() != nil { + return 0, cmd.Err() + } + return cmd.Val(), nil +} + +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 + } else if cmd.Err() != nil { + return 0, cmd.Err() + } + return cmd.Int64() +} + +func (r *RedisClient) LockPayouts(login string, amount int64) error { + key := r.formatKey("payments", "lock") + result := r.client.SetNX(key, join(login, amount), 0).Val() + if !result { + return fmt.Errorf("Unable to acquire lock '%s'", key) + } + return nil +} + +func (r *RedisClient) UnlockPayouts() error { + key := r.formatKey("payments", "lock") + _, err := r.client.Del(key).Result() + return err +} + +func (r *RedisClient) IsPayoutsLocked() (bool, error) { + _, err := r.client.Get(r.formatKey("payments", "lock")).Result() + if err == redis.Nil { + return false, nil + } else if err != nil { + return false, err + } else { + return true, nil + } +} + +type PendingPayment struct { + Timestamp int64 `json:"timestamp"` + Amount int64 `json:"amount"` + Address string `json:"login"` +} + +func (r *RedisClient) GetPendingPayments() []*PendingPayment { + raw := r.client.ZRevRangeWithScores(r.formatKey("payments", "pending"), 0, -1) + var result []*PendingPayment + for _, v := range raw.Val() { + // timestamp -> "address:amount" + payment := PendingPayment{} + payment.Timestamp = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + payment.Address = fields[0] + payment.Amount, _ = strconv.ParseInt(fields[1], 10, 64) + result = append(result, &payment) + } + return result +} + +// Deduct miner's balance for payment +func (r *RedisClient) UpdateBalance(login string, amount int64) error { + tx := r.client.Multi() + defer tx.Close() + + ts := util.MakeTimestamp() / 1000 + + _, err := tx.Exec(func() error { + 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"), "pending", amount) + tx.ZAdd(r.formatKey("payments", "pending"), redis.Z{Score: float64(ts), Member: join(login, amount)}) + return nil + }) + return err +} + +func (r *RedisClient) RollbackBalance(login string, amount int64) error { + tx := r.client.Multi() + defer tx.Close() + + _, 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("finances"), "balance", amount) + 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 { + 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), "paid", amount) + 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.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 +} + +func (r *RedisClient) WriteReward(login string, amount int64, percent *big.Rat, immature bool, block *BlockData) error { + if amount <= 0 { + return nil + } + tx := r.client.Multi() + defer tx.Close() + + addStr := join(amount, percent, immature, block.Hash, block.Height, block.Timestamp) + remStr := join(amount, percent, !immature, block.Hash, block.Height, block.Timestamp) + remscore := block.Timestamp - 3600*24*40 // Store the last 40 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 { + creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) + tx, err := r.client.Watch(creditKey) + // Must decrement immatures using existing log entry + immatureCredits := tx.HGetAllMap(creditKey) + if err != nil { + return err + } + defer tx.Close() + + ts := util.MakeTimestamp() / 1000 + value := join(block.Hash, ts, block.Reward) + + _, err = tx.Exec(func() error { + r.writeMaturedBlock(tx, block) + tx.ZAdd(r.formatKey("credits", "all"), redis.Z{Score: float64(block.Height), Member: value}) + + // Decrement immature balances + totalImmature := int64(0) + for login, amountString := range immatureCredits.Val() { + amount, _ := strconv.ParseInt(amountString, 10, 64) + totalImmature += amount + tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + } + + // Increment balances + total := int64(0) + for login, amount := range roundRewards { + 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)) + } + tx.Del(creditKey) + tx.HIncrBy(r.formatKey("finances"), "balance", total) + 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 +} + +func (r *RedisClient) WriteOrphan(block *BlockData) error { + creditKey := r.formatKey("credits", "immature", block.RoundHeight, block.Hash) + tx, err := r.client.Watch(creditKey) + // Must decrement immatures using existing log entry + immatureCredits := tx.HGetAllMap(creditKey) + if err != nil { + return err + } + defer tx.Close() + + _, err = tx.Exec(func() error { + r.writeMaturedBlock(tx, block) + + // Decrement immature balances + totalImmature := int64(0) + for login, amountString := range immatureCredits.Val() { + amount, _ := strconv.ParseInt(amountString, 10, 64) + totalImmature += amount + tx.HIncrBy(r.formatKey("miners", login), "immature", (amount * -1)) + } + tx.Del(creditKey) + tx.HIncrBy(r.formatKey("finances"), "immature", (totalImmature * -1)) + return nil + }) + return err +} + +func (r *RedisClient) WritePendingOrphans(blocks []*BlockData) error { + tx := r.client.Multi() + defer tx.Close() + + _, err := tx.Exec(func() error { + for _, block := range blocks { + r.writeImmatureBlock(tx, block) + } + return nil + }) + return err +} + +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) { + return r.client.Exists(r.formatKey("miners", login)).Result() +} + +func (r *RedisClient) GetMinerStats(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", "currentShares"), 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) + 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"] = csh + } + + return stats, nil +} + +// Try to convert all numeric strings to int64 +func convertStringMap(m map[string]string) map[string]interface{} { + result := make(map[string]interface{}) + var err error + for k, v := range m { + result[k], err = strconv.ParseInt(v, 10, 64) + if err != nil { + result[k] = v + } + } + return result +} + +// WARNING: Must run it periodically to flush out of window hashrate entries +func (r *RedisClient) FlushStaleStats(window, largeWindow time.Duration) (int64, error) { + now := util.MakeTimestamp() / 1000 + max := fmt.Sprint("(", now-int64(window/time.Second)) + total, err := r.client.ZRemRangeByScore(r.formatKey("hashrate"), "-inf", max).Result() + if err != nil { + return total, err + } + + var c int64 + miners := make(map[string]struct{}) + max = fmt.Sprint("(", now-int64(largeWindow/time.Second)) + + for { + var keys []string + var err error + c, keys, err = r.client.Scan(c, r.formatKey("hashrate", "*"), 100).Result() + if err != nil { + return total, err + } + for _, row := range keys { + login := strings.Split(row, ":")[2] + if _, ok := miners[login]; !ok { + n, err := r.client.ZRemRangeByScore(r.formatKey("hashrate", login), "-inf", max).Result() + if err != nil { + return total, err + } + miners[login] = struct{}{} + total += n + } + } + if c == 0 { + break + } + } + return total, 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.HGet(r.formatKey("paymentsTotal"), "all") + tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) + tx.LLen(r.formatKey("lastshares")) + tx.ZRevRangeWithScores(r.formatKey("finders"), 0, -1) + return nil + }) + + if (err != nil) && (err != redis.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() + + immature := convertBlockResults(cmds[4].(*redis.ZSliceCmd)) + stats["immature"] = immature + stats["immatureTotal"] = cmds[7].(*redis.IntCmd).Val() + + matured := convertBlockResults(cmds[5].(*redis.ZSliceCmd)) + stats["matured"] = matured + stats["maturedTotal"] = cmds[8].(*redis.IntCmd).Val() + + payments := convertPaymentsResults(cmds[10].(*redis.ZSliceCmd)) + stats["payments"] = payments + stats["paymentsTotal"], _ = cmds[9].(*redis.StringCmd).Int64() + + finders := convertFindersResults(cmds[12].(*redis.ZSliceCmd)) + stats["finders"] = finders + + totalHashrate, miners := convertMinersStats(window, cmds[1].(*redis.ZSliceCmd)) + stats["miners"] = miners + stats["minersTotal"] = len(miners) + stats["hashrate"] = totalHashrate + return stats, nil +} + +func (r *RedisClient) CollectWorkersStats(sWindow, lWindow time.Duration, login string) (map[string]interface{}, error) { + smallWindow := int64(sWindow / time.Second) + largeWindow := int64(lWindow / 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", login), "-inf", fmt.Sprint("(", now-largeWindow)) + tx.ZRangeWithScores(r.formatKey("hashrate", login), 0, -1) + 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) + return nil + }) + + if err != nil { + return nil, err + } + + totalHashrate := int64(0) + currentHashrate := int64(0) + online := int64(0) + offline := int64(0) + workers := convertWorkersStats(smallWindow, cmds[1].(*redis.ZSliceCmd), cmds[4].(*redis.ZSliceCmd), login, r) + + for id, worker := range workers { + timeOnline := now - worker.startedAt + if timeOnline < 600 { + timeOnline = 600 + } + + boundary := timeOnline + if timeOnline >= smallWindow { + boundary = smallWindow + } + worker.HR = worker.HR / boundary + + boundary = timeOnline + if timeOnline >= largeWindow { + boundary = largeWindow + } + worker.TotalHR = worker.TotalHR / boundary + + if worker.LastBeat < (now - smallWindow/2) { + worker.Offline = true + offline++ + } else { + online++ + } + + blocks := cmds[4].(*redis.ZSliceCmd).Val() + + for _, val := range blocks { + parts := strings.Split(val.Member.(string), ":") + rig := parts[2] + if id == rig { + str := fmt.Sprint(val.Member.(string)) + if worker.LastBeat < (now - largeWindow) { + tx.ZRem(r.formatKey("worker", "blocks", login), str) + } + } + } + + currentHashrate += worker.HR + totalHashrate += worker.TotalHR + valid_share, stale_share, invalid_share, _ := r.getSharesStatus(login, id) + worker.ValidShares = int64(5) + worker.StaleShares = int64(5) + worker.InvalidShares = int64(5) + worker.ValidShares = valid_share + worker.StaleShares = stale_share + worker.InvalidShares = invalid_share + //test percentage + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + tot_share := int64(0) + tot_share += valid_share + tot_share += stale_share + tot_share += invalid_share + if tot_share > 0 { + d := float64(100) + //tot_share += ////error + cost_per := float64(tot_share) / d + v_per := float64(valid_share) / cost_per + worker.ValidPercent = toFixed(v_per, 1) + s_per := float64(stale_share) / cost_per + worker.StalePercent = toFixed(s_per, 1) + i_per := float64(invalid_share) / cost_per + worker.InvalidPercent = toFixed(i_per, 1) + } else { + worker.ValidPercent = toFixed(0, 1) + worker.StalePercent = toFixed(0, 1) + worker.InvalidPercent = toFixed(0, 1) + } + w_stat := int64(0) //test worker large hashrate indicator + if worker.HR >= worker.TotalHR { + w_stat = 1 + worker.WorkerStatus = w_stat + } else if worker.HR < worker.TotalHR { + w_stat = 0 + worker.WorkerStatus = w_stat + } + ///test small hr + tot_w := r.client.HGet(r.formatKey("minerShare", login, id), "hashrate") + + if tot_w.Err() == redis.Nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, nil + } else if tot_w.Err() != nil { + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(0, 10)) + //return nil, tot_w.Err() + } + + last_hr, _ := tot_w.Int64() + w_stat_s := int64(0) //test worker hashrate indicator + if worker.HR > last_hr { + w_stat_s = 1 + worker.WorkerStatushas = w_stat_s + } else if worker.HR <= last_hr { + w_stat_s = 0 + worker.WorkerStatushas = w_stat_s + } + tx.HSet(r.formatKey("minerShare", login, id), "hashrate", strconv.FormatInt(worker.HR, 10)) + workers[id] = worker + } + + stats["workers"] = workers + stats["workersTotal"] = len(workers) + stats["workersOnline"] = online + stats["workersOffline"] = offline + stats["hashrate"] = totalHashrate + stats["currentHashrate"] = currentHashrate + + stats["rewards"] = convertRewardResults(cmds[2].(*redis.ZSliceCmd)) // last 40 + rewards := convertRewardResults(cmds[3].(*redis.ZSliceCmd)) // all + + var dorew []*SumRewardData + dorew = append(dorew, &SumRewardData{Name: "Last 60 minutes", Interval: 3600, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 12 hours", Interval: 3600 * 12, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 24 hours", Interval: 3600 * 24, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 7 days", Interval: 3600 * 24 * 7, Offset: 0}) + dorew = append(dorew, &SumRewardData{Name: "Last 30 days", Interval: 3600 * 24 * 30, Offset: 0}) + + for _, reward := range rewards { + + for _, dore := range dorew { + dore.Reward += 0 + dore.Blocks += 0 + if reward.Timestamp > now-dore.Interval { + dore.Reward += reward.Reward + dore.Blocks++ + } + } + } + stats["sumrewards"] = dorew + stats["24hreward"] = dorew[2].Reward + return stats, nil +} + +func round(num float64) int { + return int(num + math.Copysign(0.5, num)) +} + +func toFixed(num float64, precision int) float64 { + output := math.Pow(10, float64(precision)) + return float64(round(num*output)) / output +} + +func (r *RedisClient) CollectLuckStats(windows []int) (map[string]interface{}, error) { + stats := make(map[string]interface{}) + + tx := r.client.Multi() + defer tx.Close() + + max := int64(windows[len(windows)-1]) + + cmds, err := tx.Exec(func() error { + tx.ZRevRangeWithScores(r.formatKey("blocks", "immature"), 0, -1) + tx.ZRevRangeWithScores(r.formatKey("blocks", "matured"), 0, max-1) + return nil + }) + if err != nil { + return stats, err + } + blocks := convertBlockResults(cmds[0].(*redis.ZSliceCmd), cmds[1].(*redis.ZSliceCmd)) + + calcLuck := func(max int) (int, float64, float64, float64) { + var total int + var sharesDiff, uncles, orphans float64 + for i, block := range blocks { + if i > (max - 1) { + break + } + if block.Uncle { + uncles++ + } + if block.Orphan { + orphans++ + } + sharesDiff += float64(block.TotalShares) / float64(block.Difficulty) + total++ + } + if total > 0 { + sharesDiff /= float64(total) + uncles /= float64(total) + orphans /= float64(total) + } + return total, sharesDiff, uncles, orphans + } + for _, max := range windows { + total, sharesDiff, uncleRate, orphanRate := calcLuck(max) + row := map[string]float64{ + "luck": sharesDiff, "uncleRate": uncleRate, "orphanRate": orphanRate, + } + stats[strconv.Itoa(total)] = row + if total < max { + 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 + } + 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 +} + +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() { + // "nonce:powHash:mixDigest:timestamp:diff:totalShares" + block := BlockData{} + block.Height = int64(v.Score) + block.RoundHeight = block.Height + fields := strings.Split(v.Member.(string), ":") + 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.Login = fields[6] + block.candidateKey = v.Member.(string) + result = append(result, &block) + } + return result +} + +func convertRewardResults(rows ...*redis.ZSliceCmd) []*RewardData { + var result []*RewardData + for _, row := range rows { + for _, v := range row.Val() { + // "amount:percent:immature:block.Hash:block.height" + reward := RewardData{} + reward.Timestamp = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + //block.UncleHeight, _ = strconv.ParseInt(fields[0], 10, 64) + reward.BlockHash = fields[3] + reward.Reward, _ = strconv.ParseInt(fields[0], 10, 64) + reward.Percent, _ = strconv.ParseFloat(fields[1], 64) + reward.Immature, _ = strconv.ParseBool(fields[2]) + reward.Height, _ = strconv.ParseInt(fields[4], 10, 64) + result = append(result, &reward) + } + } + return result +} + +func convertBlockResults(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), ":") + 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.RewardString = fields[7] + block.ImmatureReward = fields[7] + block.Login = fields[8] + block.immatureKey = v.Member.(string) + result = append(result, &block) + } + } + return result +} + +// Build per login workers's total shares map {'rig-1': 12345, 'rig-2': 6789, ...} +// TS => diff, id, ms +func convertWorkersStats(window int64, raw *redis.ZSliceCmd, blocks *redis.ZSliceCmd, login string, r *RedisClient) map[string]Worker { + now := util.MakeTimestamp() / 1000 + workers := make(map[string]Worker) + + for _, v := range blocks.Val() { + parts := strings.Split(v.Member.(string), ":") + id := parts[2] + worker := workers[id] + worker.Blocks++ + workers[id] = worker + } + + for _, v := range raw.Val() { + parts := strings.Split(v.Member.(string), ":") + share, _ := strconv.ParseInt(parts[0], 10, 64) + id := parts[1] + score := int64(v.Score) + worker := workers[id] + + // Add for large window + worker.TotalHR += share + worker.ValidShares = int64(4) + worker.ValidPercent = float64(0) + worker.StalePercent = float64(0) + worker.InvalidPercent = float64(0) + worker.WorkerStatus = int64(0) + worker.WorkerStatushas = int64(0) + //worker.StatleShares = int64(4) + //worker.InvalidShares = int64(4) + + // Add for small window if matches + if score >= now-window { + worker.HR += share + } + + if worker.LastBeat < score { + worker.LastBeat = score + } + if worker.startedAt > score || worker.startedAt == 0 { + worker.startedAt = score + } + workers[id] = worker + } + return workers +} + +func convertMinersStats(window int64, raw *redis.ZSliceCmd) (int64, map[string]Miner) { + now := util.MakeTimestamp() / 1000 + miners := make(map[string]Miner) + totalHashrate := int64(0) + + for _, v := range raw.Val() { + parts := strings.Split(v.Member.(string), ":") + share, _ := strconv.ParseInt(parts[0], 10, 64) + id := parts[1] + score := int64(v.Score) + miner := miners[id] + miner.HR += share + + if miner.LastBeat < score { + miner.LastBeat = score + } + if miner.startedAt > score || miner.startedAt == 0 { + miner.startedAt = score + } + miners[id] = miner + } + + for id, miner := range miners { + timeOnline := now - miner.startedAt + if timeOnline < 600 { + timeOnline = 600 + } + + boundary := timeOnline + if timeOnline >= window { + boundary = window + } + miner.HR = miner.HR / boundary + + if miner.LastBeat < (now - window/2) { + miner.Offline = true + } + totalHashrate += miner.HR + miners[id] = miner + } + return totalHashrate, miners +} + +func convertPaymentsResults(raw *redis.ZSliceCmd) []map[string]interface{} { + var result []map[string]interface{} + for _, v := range raw.Val() { + tx := make(map[string]interface{}) + tx["timestamp"] = int64(v.Score) + fields := strings.Split(v.Member.(string), ":") + tx["tx"] = fields[0] + // Individual or whole payments row + if len(fields) < 3 { + tx["amount"], _ = strconv.ParseInt(fields[1], 10, 64) + } else { + tx["address"] = fields[1] + tx["amount"], _ = strconv.ParseInt(fields[2], 10, 64) + } + result = append(result, tx) + } + var reverse []map[string]interface{} + for i := len(result) - 1; i >= 0; i-- { + reverse = append(reverse, result[i]) + } + return result +} + +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 +} + +/* +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) + + 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) GetCurrentHashrate(login string) (int64, error) { + hashrate := r.client.HGet(r.formatKey("currenthashrate", login), "hashrate") + if hashrate.Err() == redis.Nil { + return 0, nil + } else if hashrate.Err() != nil { + return 0, hashrate.Err() + } + return hashrate.Int64() +} + +// Need a function to delete on round end or whatever, and another function to get. +func (r *RedisClient) ResetWorkerShareStatus() { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.HDel(r.formatKey("minerShare")) + return nil + }) + + // THis should do it ay ? + // fuck it +} + +// Don't know if this will work, returning three values, but let's see + +func (r *RedisClient) getSharesStatus(login string, id string) (int64, int64, int64, error) { + valid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "valid") + stale_shares := r.client.HGet(r.formatKey("minerShare", login, id), "stale") + invalid_shares := r.client.HGet(r.formatKey("minerShare", login, id), "invalid") + + if valid_shares.Err() == redis.Nil || stale_shares.Err() == redis.Nil || invalid_shares.Err() == redis.Nil { + return 0, 0, 0, nil + } else if valid_shares.Err() != nil || stale_shares.Err() != nil || invalid_shares.Err() != nil { + return 0, 0, 0, valid_shares.Err() + } + + v_c, _ := valid_shares.Int64() + s_c, _ := stale_shares.Int64() + i_c, _ := invalid_shares.Int64() + return v_c, s_c, i_c, nil + +} + +//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 + if valid { + valid_int = 1 + } + if stale { + stale_int = 1 + } + if invalid { + 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() + 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() + + 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)) // Would that work? + + return nil + }) + } //end else +} + +func (r *RedisClient) NumberStratumWorker(count int) { + tx := r.client.Multi() + defer tx.Close() + + tx.Exec(func() error { + tx.Del(r.formatKey("WorkersTot")) + tx.HIncrBy(r.formatKey("WorkersTot"), "workers", int64(count)) + //tx.HSet(r.formatKey("WorkersTotal"), "workers", int64(count)) + return nil + }) +} From 29d24c16f99c608e3668e8b19d331ed0bb2aee7f Mon Sep 17 00:00:00 2001 From: yuriy0803 <68668177+yuriy0803@users.noreply.github.com> Date: Fri, 12 Mar 2021 18:02:25 +0100 Subject: [PATCH 11/11] Update rewards.hbs --- www/app/templates/account/rewards.hbs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/www/app/templates/account/rewards.hbs b/www/app/templates/account/rewards.hbs index 44fe03c..5e33de4 100644 --- a/www/app/templates/account/rewards.hbs +++ b/www/app/templates/account/rewards.hbs @@ -2,18 +2,10 @@ {{#if model.rewards}}

Your Latest Rewards

- - - - - - - {{#each model.sumrewards as |sumreward|}} - - + {{/each}}
TimeBlocksReward
{{sumreward.name}}{{sumreward.blocks}}{{format-balance sumreward.reward}}{{format-balance sumreward.reward}}
@@ -39,7 +31,7 @@ {{format-balance tx.reward}} {{/if}} - {{format-number tx.percent style='percent' maximumFractionDigits='6'}} + {{format-number tx.percent style='percent' maximumFractionDigits='2'}} {{/each}}