181 lines
5.7 KiB
Go
181 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/index"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/monero/address"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
|
|
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
|
|
hbot "github.com/whyrusleeping/hellabot"
|
|
"regexp"
|
|
)
|
|
|
|
type command struct {
|
|
Match *regexp.Regexp
|
|
Handle func(db *DB, entries []*channelEntry, bot *hbot.Bot, message *hbot.Message, replyTo string, matches ...string) bool
|
|
}
|
|
|
|
var guestUserRegex = regexp.MustCompile("^Guest[0-9]+_*$")
|
|
|
|
func isNickAllowed(nick string) error {
|
|
if guestUserRegex.MatchString(nick) {
|
|
return errors.New("guest user is not allowed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var commands = []command{
|
|
{
|
|
Match: regexp.MustCompile("^\\.(status|shares)[ \\t]*"),
|
|
Handle: func(db *DB, entries []*channelEntry, bot *hbot.Bot, message *hbot.Message, replyTo string, matches ...string) bool {
|
|
subs := db.GetByNick(message.Name)
|
|
if len(subs) == 0 {
|
|
bot.Msg(replyTo, "No known subscriptions to your nick.")
|
|
return true
|
|
}
|
|
type result struct {
|
|
Endpoint string
|
|
ShareCount int
|
|
UncleCount int
|
|
YourWeight types.Difficulty
|
|
TotalWeight types.Difficulty
|
|
Tip *index.SideBlock
|
|
Address *address.Address
|
|
MinerId uint64
|
|
SharesPosition *PositionChart
|
|
UnclesPosition *PositionChart
|
|
Consensus *sidechain.Consensus
|
|
}
|
|
|
|
var hasResults bool
|
|
var results []*result
|
|
for i := range entries {
|
|
e := entries[i]
|
|
if e.ApiEndpoint != "" && (message.To == bot.Nick || e.Channel == message.To) {
|
|
func() {
|
|
e.ChainLock.RLock()
|
|
defer e.ChainLock.RUnlock()
|
|
|
|
var tr []*result
|
|
for _, sub := range subs {
|
|
var r result
|
|
r.Tip = e.Tip
|
|
r.Endpoint = e.ApiEndpoint
|
|
r.Address = sub.Address
|
|
r.Consensus = e.Consensus
|
|
|
|
r.SharesPosition = NewPositionChart(30, uint64(e.Tip.WindowDepth))
|
|
r.UnclesPosition = NewPositionChart(30, uint64(e.Tip.WindowDepth))
|
|
tr = append(tr, &r)
|
|
}
|
|
|
|
for range index.IterateSideBlocksInPPLNSWindow(e.Tip, e.Consensus, e.DifficultyFromHeight, e.SideBlockByTemplateId, e.SideBlockUnclesByTemplateId, func(b *index.SideBlock, weight types.Difficulty) {
|
|
for _, r := range tr {
|
|
r.TotalWeight = r.TotalWeight.Add(weight)
|
|
if b.MinerAddress.Compare(r.Address) == 0 {
|
|
r.MinerId = b.Miner
|
|
r.YourWeight = r.YourWeight.Add(weight)
|
|
if b.IsUncle() {
|
|
r.UnclesPosition.Add(int(e.Tip.SideHeight-b.SideHeight), 1)
|
|
r.UncleCount++
|
|
} else {
|
|
r.SharesPosition.Add(int(e.Tip.SideHeight-b.SideHeight), 1)
|
|
r.ShareCount++
|
|
}
|
|
}
|
|
}
|
|
}, func(err error) {
|
|
|
|
}) {
|
|
|
|
}
|
|
|
|
for _, r := range tr {
|
|
if r.ShareCount > 0 || r.UncleCount > 0 {
|
|
results = append(results, r)
|
|
hasResults = true
|
|
}
|
|
}
|
|
|
|
}()
|
|
}
|
|
}
|
|
|
|
if !hasResults {
|
|
bot.Msg(replyTo, "You do not currently have any shares within the PPLNS window across the tracked pools.")
|
|
} else {
|
|
for _, r := range results {
|
|
ratio := float64(r.YourWeight.Lo) / float64(r.TotalWeight.Lo)
|
|
bot.Msg(replyTo, fmt.Sprintf(
|
|
"Your shares %d (+%d uncles) ~%.03f%% %sH/s :: Miner Statistics %s/m/%s :: Shares/Uncles position %s %s",
|
|
r.ShareCount,
|
|
r.UncleCount,
|
|
ratio*100,
|
|
utils.SiUnits(ratio*float64(types.DifficultyFrom64(r.Tip.Difficulty).Div64(r.Consensus.TargetBlockTime).Lo), 3),
|
|
r.Endpoint,
|
|
utils.EncodeBinaryNumber(r.MinerId),
|
|
r.SharesPosition.String(),
|
|
r.UnclesPosition.String(),
|
|
))
|
|
}
|
|
}
|
|
return true
|
|
|
|
},
|
|
},
|
|
|
|
{
|
|
Match: regexp.MustCompile("^\\.(sub|subscribe)[ \\t]+(4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)[ \\t]*"),
|
|
Handle: func(db *DB, entries []*channelEntry, bot *hbot.Bot, message *hbot.Message, replyTo string, matches ...string) bool {
|
|
if err := isNickAllowed(message.Name); err != nil {
|
|
bot.Msg(replyTo, fmt.Sprintf("Cannot subscribe: %s", err))
|
|
return true
|
|
}
|
|
|
|
addr := address.FromBase58(matches[2])
|
|
if addr == nil {
|
|
bot.Msg(replyTo, "Cannot subscribe: Invalid Monero address")
|
|
return true
|
|
}
|
|
|
|
if err := db.Store(&Subscription{
|
|
Address: addr,
|
|
Nick: message.Name,
|
|
}); err == nil {
|
|
bot.Msg(replyTo, fmt.Sprintf("Subscribed your nick to shares found by %s%s%s while you are online. You can private message this bot for any commands instead of using public channels.", FormatItalic, utils.Shorten(addr.ToBase58(), 10), FormatReset))
|
|
for _, e := range entries {
|
|
func() {
|
|
e.ChainLock.RLock()
|
|
defer e.ChainLock.RUnlock()
|
|
|
|
var yourWeight types.Difficulty
|
|
var totalWeight types.Difficulty
|
|
|
|
for range index.IterateSideBlocksInPPLNSWindow(e.Tip, e.Consensus, e.DifficultyFromHeight, e.SideBlockByTemplateId, e.SideBlockUnclesByTemplateId, func(b *index.SideBlock, weight types.Difficulty) {
|
|
totalWeight = totalWeight.Add(weight)
|
|
if b.MinerAddress.Compare(addr) == 0 {
|
|
yourWeight = yourWeight.Add(weight)
|
|
}
|
|
}, func(err error) {
|
|
|
|
}) {
|
|
|
|
}
|
|
|
|
shareRatio := float64(yourWeight.Lo) / float64(totalWeight.Lo)
|
|
if shareRatio > notificationPoolShare { //warn about spammy notifications
|
|
bot.Msg(replyTo, fmt.Sprintf("You have more than %.01f%% of the %s pool total share weight (%s) with %.03f%%. Share notifications will not be sent above this threshold. Consider using the /api/events interface directly.", notificationPoolShare*100, e.Name, e.ApiEndpoint, shareRatio*100))
|
|
}
|
|
}()
|
|
}
|
|
return true
|
|
} else {
|
|
bot.Msg(replyTo, fmt.Sprintf("Cannot subscribe: %s", err))
|
|
return true
|
|
}
|
|
},
|
|
},
|
|
}
|