diff --git a/README.md b/README.md index 133cef4cd..a0098e194 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ * Modern beautiful Ember.js frontend * Separate stats for workers: can highlight timed-out workers so miners can perform maintenance of rigs * JSON-API for stats +* PPLNS block reward #### Proxies diff --git a/config.example.json b/config.example.json index 1a264b8cc..f4a0687e1 100644 --- a/config.example.json +++ b/config.example.json @@ -2,6 +2,7 @@ "threads": 2, "coin": "eth", "name": "main", + "pplns": 9000, "proxy": { "enabled": true, diff --git a/main.go b/main.go index faff57e60..42d757885 100644 --- a/main.go +++ b/main.go @@ -82,7 +82,7 @@ func main() { startNewrelic() - backend = storage.NewRedisClient(&cfg.Redis, cfg.Coin) + backend = storage.NewRedisClient(&cfg.Redis, cfg.Coin, cfg.Pplns) pong, err := backend.Check() if err != nil { log.Printf("Can't establish connection to backend: %v", err) diff --git a/payouts/unlocker.go b/payouts/unlocker.go index c073ef0b3..cbf6c1777 100644 --- a/payouts/unlocker.go +++ b/payouts/unlocker.go @@ -453,7 +453,12 @@ func (u *BlockUnlocker) calculateRewards(block *storage.BlockData) (*big.Rat, *b return nil, nil, nil, nil, err } - rewards := calculateRewardsForShares(shares, block.TotalShares, minersProfit) + totalShares := int64(0) + for _, val := range shares { + totalShares += val + } + + rewards := calculateRewardsForShares(shares, totalShares, minersProfit) if block.ExtraReward != nil { extraReward := new(big.Rat).SetInt(block.ExtraReward) diff --git a/proxy/config.go b/proxy/config.go index 6248c538c..457cdd412 100644 --- a/proxy/config.go +++ b/proxy/config.go @@ -17,6 +17,7 @@ type Config struct { Threads int `json:"threads"` Coin string `json:"coin"` + Pplns int64 `json:"pplns"` Redis storage.Config `json:"redis"` BlockUnlocker payouts.UnlockerConfig `json:"unlocker"` diff --git a/storage/redis.go b/storage/redis.go index 449b58fcc..057c85b9e 100644 --- a/storage/redis.go +++ b/storage/redis.go @@ -22,6 +22,7 @@ type Config struct { type RedisClient struct { client *redis.Client prefix string + pplns int64 } type BlockData struct { @@ -78,14 +79,14 @@ type Worker struct { TotalHR int64 `json:"hr2"` } -func NewRedisClient(cfg *Config, prefix string) *RedisClient { +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} + return &RedisClient{client: client, prefix: prefix, pplns: pplns} } func (r *RedisClient) Client() *redis.Client { @@ -210,14 +211,36 @@ func (r *RedisClient) WriteBlock(login, id string, params []string, diff, roundD tx.HDel(r.formatKey("stats"), "roundShares") tx.ZIncrBy(r.formatKey("finders"), 1, login) tx.HIncrBy(r.formatKey("miners", login), "blocksFound", 1) - tx.Rename(r.formatKey("shares", "roundCurrent"), r.formatRound(int64(height), params[0])) - tx.HGetAllMap(r.formatRound(int64(height), params[0])) + tx.HGetAllMap(r.formatKey("shares", "roundCurrent")) + tx.Del(r.formatKey("shares", "roundCurrent")) + tx.LRange(r.formatKey("lastshares"), 0, r.pplns) return nil }) if err != nil { return false, err } else { - sharesMap, _ := cmds[10].(*redis.StringStringMapCmd).Result() + + 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) @@ -231,6 +254,9 @@ 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) { + 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)}) @@ -271,6 +297,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") } @@ -577,7 +610,8 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string tx.HGetAllMap(r.formatKey("miners", login)) tx.ZRevRangeWithScores(r.formatKey("payments", login), 0, maxPayments-1) tx.ZCard(r.formatKey("payments", login)) - tx.HGet(r.formatKey("shares", "roundCurrent"), login) + tx.HGet(r.formatKey("shares", "currentShares"), login) + tx.LRange(r.formatKey("lastshares"), 0, r.pplns) return nil }) @@ -589,8 +623,14 @@ func (r *RedisClient) GetMinerStats(login string, maxPayments int64) (map[string payments := convertPaymentsResults(cmds[1].(*redis.ZSliceCmd)) stats["payments"] = payments stats["paymentsTotal"] = cmds[2].(*redis.IntCmd).Val() - roundShares, _ := cmds[3].(*redis.StringCmd).Int64() - stats["roundShares"] = roundShares + shares := cmds[4].(*redis.StringSliceCmd).Val() + csh := 0 + for _, val := range shares { + if val == login { + csh++ + } + } + stats["roundShares"] = csh } return stats, nil @@ -668,6 +708,7 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym tx.ZCard(r.formatKey("blocks", "matured")) tx.ZCard(r.formatKey("payments", "all")) tx.ZRevRangeWithScores(r.formatKey("payments", "all"), 0, maxPayments-1) + tx.LLen(r.formatKey("lastshares")) return nil }) @@ -676,6 +717,7 @@ func (r *RedisClient) CollectStats(smallWindow time.Duration, maxBlocks, maxPaym } 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 diff --git a/storage/redis_test.go b/storage/redis_test.go index 29c60d9f7..666722e84 100644 --- a/storage/redis_test.go +++ b/storage/redis_test.go @@ -14,7 +14,7 @@ var r *RedisClient const prefix = "test" func TestMain(m *testing.M) { - r = NewRedisClient(&Config{Endpoint: "127.0.0.1:6379"}, prefix) + r = NewRedisClient(&Config{Endpoint: "127.0.0.1:6379"}, prefix, 3000) reset() c := m.Run() reset() diff --git a/www/app/controllers/account.js b/www/app/controllers/account.js index 79782f798..dca71ec74 100644 --- a/www/app/controllers/account.js +++ b/www/app/controllers/account.js @@ -6,7 +6,7 @@ export default Ember.Controller.extend({ roundPercent: Ember.computed('stats', 'model', { get() { - var percent = this.get('model.roundShares') / this.get('stats.roundShares'); + var percent = this.get('model.roundShares') / this.get('stats.nShares'); if (!percent) { return 0; }