Initial commit

This commit is contained in:
DataHoarder 2023-04-21 10:02:00 +02:00
commit db05bee6b8
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
4 changed files with 379 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.idea

282
bot.go Normal file
View file

@ -0,0 +1,282 @@
package main
import (
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"git.gammaspectra.live/P2Pool/p2pool-observer/index"
"git.gammaspectra.live/P2Pool/p2pool-observer/types"
"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
hbot "github.com/whyrusleeping/hellabot"
"golang.org/x/exp/slices"
log2 "gopkg.in/inconshreveable/log15.v2"
"gopkg.in/sorcix/irc.v2"
"io"
"log"
"net/http"
"os"
"strings"
"sync"
"time"
)
type foundBlocks []*index.FoundBlock
func (s foundBlocks) Get(mainId types.Hash) *index.FoundBlock {
if i := slices.IndexFunc(s, func(block *index.FoundBlock) bool {
return block.MainBlock.Id == mainId
}); i != -1 {
return s[i]
}
return nil
}
func (s foundBlocks) GetPrevious(b *index.FoundBlock) *index.FoundBlock {
var prev *index.FoundBlock
for _, block := range s {
if prev == nil {
if block.MainBlock.Height < b.MainBlock.Height {
prev = block
}
} else if block.MainBlock.Height < b.MainBlock.Height && block.MainBlock.Height > prev.MainBlock.Height {
prev = block
}
}
return prev
}
const FormatColorGreen = "\x0303"
const FormatColorRed = "\x0304"
const FormatColorOrange = "\x0307"
const FormatColorYellow = "\x0308"
const FormatColorLightGreen = "\x0309"
const FormatBold = "\x02"
const FormatItalic = "\x1D"
const FormatUnderline = "\x1F"
const FormatReset = "\x0F"
func main() {
ircHost := flag.String("irc-host", "irc.libera.chat", "")
ircPort := flag.Uint("irc-port", 6697, "")
ircSsl := flag.Bool("irc-ssl", true, "")
botNickName := flag.String("bot-nick", "", "")
botUserName := flag.String("bot-user", "", "")
botPassword := os.Getenv("BOT_PASSWORD")
botChannels := flag.String("channels", "", "A list of #CHANNEL,NAME,API_ENDPOINT separated by; for example: #p2pool-main,Main,https://p2pool.observer;#p2pool-mini,Mini,https://mini.p2pool.observer")
flag.Parse()
type channelEntry struct {
ApiEndpoint string
Channel string
Name string
LastBlocks foundBlocks
}
const lastBlocksCount = 10
getLastBlocks := func(host string) (result foundBlocks) {
if host == "" {
return nil
}
response, err := http.DefaultClient.Get(fmt.Sprintf("%s/api/found_blocks?limit=%d", host, lastBlocksCount))
if err != nil {
return nil
}
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
if err = json.Unmarshal(buf, &result); err != nil {
return nil
} else {
return result
}
}
}
getBlockByMainId := func(host string, id types.Hash) (result *index.SideBlock) {
if host == "" {
return nil
}
response, err := http.DefaultClient.Get(fmt.Sprintf("%s/api/block_by_id/%s", host, id.String()))
if err != nil {
return nil
}
defer response.Body.Close()
if buf, err := io.ReadAll(response.Body); err != nil {
return nil
} else {
if err = json.Unmarshal(buf, &result); err != nil {
return nil
} else {
return result
}
}
}
var channelEntries []*channelEntry
for _, e := range strings.Split(*botChannels, ";") {
e = strings.TrimSpace(e)
if e == "" {
continue
}
split := strings.Split(e, ",")
if len(split) != 3 {
log.Panicf("invalid entry %s", e)
continue
}
channelEntries = append(channelEntries, &channelEntry{
ApiEndpoint: split[2],
Channel: split[0],
Name: split[1],
LastBlocks: getLastBlocks(split[2]),
})
}
if bot, err := hbot.NewBot(fmt.Sprintf("%s:%d", *ircHost, *ircPort), *botNickName, func(bot *hbot.Bot) {
bot.Nick = *botNickName
bot.Realname = *botUserName
bot.SSL = *ircSsl
bot.Password = botPassword
bot.PingTimeout = time.Hour * 24 * 365 //one year, fix issue with hbot timeouting
if bot.Password != "" {
bot.SASL = true
}
//need to empty old list
bot.Channels = bot.Channels[:0]
for _, e := range channelEntries {
bot.Channels = append(bot.Channels, e.Channel)
}
//unique channels
slices.Sort(bot.Channels)
bot.Channels = slices.Compact(bot.Channels)
}); err != nil {
log.Panic(err)
} else {
logHandler := log2.LvlFilterHandler(log2.LvlDebug, log2.StdoutHandler)
bot.Logger.SetHandler(logHandler)
// see about irc.ERR_NICKNAMEINUSE or irc.ERR_NICKCOLLISION to recover nick
var onlineUsersLock sync.RWMutex
var onlineUsers []string
bot.AddTrigger(hbot.Trigger{
Condition: func(bot *hbot.Bot, message *hbot.Message) bool {
return message.Command == irc.RPL_NAMREPLY || message.Command == irc.RPL_ENDOFNAMES
},
Action: func(bot *hbot.Bot, message *hbot.Message) bool {
if message.Command == irc.RPL_NAMREPLY {
if len(message.Params) < 4 {
return false
}
channelName := message.Params[2]
if slices.ContainsFunc(channelEntries, func(entry *channelEntry) bool {
return entry.Channel == channelName
}) {
onlineUsersLock.Lock()
defer onlineUsersLock.Unlock()
for _, nick := range strings.Split(message.Params[3], " ") {
if !slices.Contains(onlineUsers, nick) {
onlineUsers = append(onlineUsers, strings.TrimLeft(nick, "@~%+"))
}
}
}
}
return false
},
})
//Private message
bot.AddTrigger(hbot.Trigger{
Condition: func(bot *hbot.Bot, message *hbot.Message) bool {
trimMessage := strings.TrimSpace(message.Content)
if len(trimMessage) <= 0 || trimMessage[0] != '.' {
return false
}
return message.To == bot.Nick
},
Action: func(bot *hbot.Bot, message *hbot.Message) bool {
bot.Msg(message.Name, "Bot notifications currently out of service")
return true
},
})
for _, e := range channelEntries {
if e.ApiEndpoint == "" {
continue
}
go func(e *channelEntry) {
for range time.NewTicker(time.Second * 5).C {
lastBlocks := getLastBlocks(e.ApiEndpoint)
if len(lastBlocks) == 0 {
continue
}
for _, b := range lastBlocks {
if e.LastBlocks.Get(b.MainBlock.Id) == nil {
//new block
e.LastBlocks = append(e.LastBlocks, b)
uHeight := (b.SideHeight << 16) | uint64(binary.BigEndian.Uint16(b.MainBlock.Id[0:2]))
effort := float64(0)
if previous := e.LastBlocks.GetPrevious(b); previous != nil {
effort = float64(b.CumulativeDifficulty.SubWrap(previous.CumulativeDifficulty).Mul64(100).Lo) / float64(b.Difficulty)
}
effortColor := FormatColorRed
if effort < 2 {
effortColor = FormatColorYellow
}
if effort < 1 {
effortColor = FormatColorGreen
}
bot.Msg(e.Channel, fmt.Sprintf(
"%sBLOCK FOUND%s on %s: Main height %s%d%s, Side height %d :: %s/s/%s :: Effort %s%.02f%s :: %s%d miners paid%s, total %s%s%s XMR%s :: Id %s%s",
FormatColorLightGreen+FormatBold, FormatReset, e.Name,
FormatColorRed, b.MainBlock.Height, FormatReset,
b.SideHeight,
e.ApiEndpoint, utils.EncodeBinaryNumber(uHeight),
effortColor, effort*100, FormatReset,
FormatColorOrange, b.WindowOutputs, FormatReset,
FormatColorOrange, FormatBold, utils.XMRUnits(b.MainBlock.Reward), FormatReset,
FormatItalic, utils.Shorten(b.MainBlock.Id.String(), 8),
))
}
}
for _, b := range e.LastBlocks {
// no block on new returned blocks, check it did not get orphaned
if lastBlocks.Get(b.MainBlock.Id) == nil {
if sideBlock := getBlockByMainId(e.ApiEndpoint, b.MainBlock.Id); sideBlock != nil {
if !sideBlock.MinedMainAtHeight {
//got orphaned
bot.Msg(e.Channel, fmt.Sprintf(
"%sBLOCK ORPHANED%s on %s: Main height %s%d%s, Side height %d :: Side Template Id %s%s%s :: Id %s%s",
FormatColorLightGreen+FormatBold, FormatReset, e.Name,
FormatColorRed, b.MainBlock.Height, FormatReset,
b.SideHeight,
FormatItalic, b.MainBlock.SideTemplateId, FormatReset,
FormatItalic, utils.Shorten(b.MainBlock.Id.String(), 8),
))
}
}
}
}
e.LastBlocks = lastBlocks
}
}(e)
}
bot.Run()
}
}

33
go.mod Normal file
View file

@ -0,0 +1,33 @@
module git.gammaspectra.live/P2Pool/p2pool-observer-bot
go 1.20
require (
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0-20230421073540-d52e35473d9d
github.com/whyrusleeping/hellabot v0.0.0-20230331073038-70f5dd5c40d9
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb
gopkg.in/inconshreveable/log15.v2 v2.16.0
gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7
)
require (
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550 // indirect
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 // indirect
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823 // indirect
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48 // indirect
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/floatdrop/lru v1.3.0 // indirect
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/holiman/uint256 v1.2.2 // indirect
github.com/inconshreveable/log15 v2.16.0+incompatible // indirect
github.com/jxskiss/base62 v1.1.0 // indirect
github.com/lib/pq v1.10.8 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
lukechampine.com/uint128 v1.3.0 // indirect
)

63
go.sum Normal file
View file

@ -0,0 +1,63 @@
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550 h1:Mqu6Q2e//30TWeP5bM9Th5KEzWdFAFd80Y2ZXN9fmeE=
filippo.io/edwards25519 v1.0.1-0.20220803165937-8c58ed0e3550/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523 h1:oIJzm7kQyASS0xlJ79VSWRvvfXp2Qt7M05+E20o9gwE=
git.gammaspectra.live/P2Pool/go-monero v0.0.0-20230410011208-910450c4a523/go.mod h1:TAOAAV972JNDkCzyV5SkbYkKCRvcfhvvFa8LHH4Dg6g=
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823 h1:HxIuImZsB15Ix59K5VznoHzZ1ut5hW0AAqiDJpOd2+g=
git.gammaspectra.live/P2Pool/go-randomx v0.0.0-20221025112134-5190471ef823/go.mod h1:3kT0v4AMwT/OdorfH2gRWPwoOrUX/LV03HEeBsaXG1c=
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48 h1:ExrYG0RSrx/I4McPWgUF4B8R2OkblMrMki2ia8vG6Bw=
git.gammaspectra.live/P2Pool/moneroutil v0.0.0-20221007140323-a2daa2d5fc48/go.mod h1:XeSC8jK8RXnnzVAmp9e9AQZCDIbML3UoCRkxxGA+lpU=
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0-20230421073540-d52e35473d9d h1:/7aA98m3ZU/5VAfFhIb3Jujog0nY69wtyEVZUF1zBOA=
git.gammaspectra.live/P2Pool/p2pool-observer v0.0.0-20230421073540-d52e35473d9d/go.mod h1:tDkKPjbDZK2Ft/HZr/72/wKJ/9wqu8p1ER24WDujc50=
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752 h1:4r4KXpFLbixah+OGrBT9ZEflSZoFHD7aVJpXL3ukVIo=
git.gammaspectra.live/P2Pool/randomx-go-bindings v0.0.0-20221027134633-11f5607e6752/go.mod h1:KQaYHIxGXNHNMQELC7xGLu8xouwvP/dN7iGk681BXmk=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/floatdrop/lru v1.3.0 h1:83abtaKjXcWrPmtzTAk2Ggq8DUKqI29YzrTrB8+vu0c=
github.com/floatdrop/lru v1.3.0/go.mod h1:83zlXKA06Bm32JImNINCiTr0ldadvdAjUe5jSwIaw0s=
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff h1:zk1wwii7uXmI0znwU+lqg+wFL9G5+vm5I+9rv2let60=
github.com/ftrvxmtrx/fd v0.0.0-20150925145434-c6d800382fff/go.mod h1:yUhRXHewUVJ1k89wHKP68xfzk7kwXUx/DV1nx4EBMbw=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
github.com/holiman/uint256 v1.2.2 h1:TXKcSGc2WaxPD2+bmzAsVthL4+pEN0YwXcL5qED83vk=
github.com/holiman/uint256 v1.2.2/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw=
github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/jxskiss/base62 v1.1.0 h1:A5zbF8v8WXx2xixnAKD2w+abC+sIzYJX+nxmhA6HWFw=
github.com/jxskiss/base62 v1.1.0/go.mod h1:HhWAlUXvxKThfOlZbcuFzsqwtF5TcqS9ru3y5GfjWAc=
github.com/lib/pq v1.10.8 h1:3fdt97i/cwSU83+E0hZTC/Xpc9mTZxc6UWSCRcSbxiE=
github.com/lib/pq v1.10.8/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/whyrusleeping/hellabot v0.0.0-20230331073038-70f5dd5c40d9 h1:y7lb+uda1qXXCfyxbPV707hHWrPL5bfId2bk7n9kvm8=
github.com/whyrusleeping/hellabot v0.0.0-20230331073038-70f5dd5c40d9/go.mod h1:g3f61CcN5csyM0R/e0xF2FX8gKiuGHREi3ostG6FWlQ=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb h1:rhjz/8Mbfa8xROFiH+MQphmAmgqRM0bOMnytznhWEXk=
golang.org/x/exp v0.0.0-20230420155640-133eef4313cb/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20200109203555-b30bc20e4fd1/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inconshreveable/log15.v2 v2.16.0 h1:LWHLVX8KbBMkQFSqfno4901Z4Wg8L3B7Cu0n4K/Q7MA=
gopkg.in/inconshreveable/log15.v2 v2.16.0/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7 h1:XS4tmz0w7EYviIrBpFVww8IyKJQiIX5SU/1ptPVtBWI=
gopkg.in/sorcix/irc.v2 v2.0.0-20200812151606-3f15758ea8c7/go.mod h1:PmJkUcwbuPi1FiZ9Rarr6wzVMvzkO7uWqH1jwrMkgW0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=