observer-bot/commands.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
}
},
},
}