diff --git a/go.mod b/go.mod index c64adbb..891cc6b 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alp require ( github.com/cosmos/cosmos-sdk v0.42.4 github.com/rs/zerolog v1.21.0 + github.com/slack-go/slack v0.9.1 github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 diff --git a/go.sum b/go.sum index c20c957..67acc9e 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= @@ -506,6 +508,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/slack-go/slack v0.9.1 h1:pekQBs0RmrdAgoqzcMCzUCWSyIkhzUU3F83ExAdZrKo= +github.com/slack-go/slack v0.9.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= diff --git a/main.go b/main.go index 31fe821..2c4bc72 100644 --- a/main.go +++ b/main.go @@ -26,13 +26,16 @@ var ( ConfigPath string NodeAddress string LogLevel string - TelegramToken string - TelegramChat int Interval int Threshold int64 Limit uint64 MintscanPrefix string + TelegramToken string + TelegramChat int + SlackToken string + SlackChat string + Prefix string ValidatorPrefix string ValidatorPubkeyPrefix string @@ -147,6 +150,10 @@ func Execute(cmd *cobra.Command, args []string) { TelegramToken: TelegramToken, TelegramChat: TelegramChat, }, + &SlackReporter{ + SlackToken: SlackToken, + SlackChat: SlackChat, + }, } for _, reporter := range reporters { @@ -467,13 +474,16 @@ func main() { rootCmd.PersistentFlags().StringVar(&ConfigPath, "config", "", "Config file path") rootCmd.PersistentFlags().StringVar(&NodeAddress, "node", "localhost:9090", "RPC node address") rootCmd.PersistentFlags().StringVar(&LogLevel, "log-level", "info", "Logging level") - rootCmd.PersistentFlags().StringVar(&TelegramToken, "telegram-token", "", "Telegram bot token") - rootCmd.PersistentFlags().IntVar(&TelegramChat, "telegram-chat", 0, "Telegram chat or user ID") rootCmd.PersistentFlags().IntVar(&Interval, "interval", 120, "Interval between two checks, in seconds") rootCmd.PersistentFlags().Int64Var(&Threshold, "threshold", 0, "Threshold of missed blocks") rootCmd.PersistentFlags().Uint64Var(&Limit, "limit", 1000, "gRPC query pagination limit") rootCmd.PersistentFlags().StringVar(&MintscanPrefix, "mintscan-prefix", "persistence", "Prefix for mintscan links like https://mintscan.io/{prefix}") + rootCmd.PersistentFlags().StringVar(&TelegramToken, "telegram-token", "", "Telegram bot token") + rootCmd.PersistentFlags().IntVar(&TelegramChat, "telegram-chat", 0, "Telegram chat or user ID") + rootCmd.PersistentFlags().StringVar(&SlackToken, "slack-token", "", "Slack bot token") + rootCmd.PersistentFlags().StringVar(&SlackChat, "slack-chat", "", "Slack chat or user ID") + // some networks, like Iris, have the different prefixes for address, validator and consensus node rootCmd.PersistentFlags().StringVar(&Prefix, "bech-prefix", "persistence", "Bech32 global prefix") rootCmd.PersistentFlags().StringVar(&ValidatorPrefix, "bech-validator-prefix", "", "Bech32 validator prefix") diff --git a/slack.go b/slack.go new file mode 100644 index 0000000..ae33dd5 --- /dev/null +++ b/slack.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/slack-go/slack" +) + +type SlackReporter struct { + SlackToken string + SlackChat string + + SlackClient slack.Client +} + +func (r SlackReporter) Serialize(report Report) string { + var sb strings.Builder + + for _, entry := range report.Entries { + var ( + emoji string + status string + validatorLink string + ) + + switch entry.Direction { + case START_MISSING_BLOCKS: + emoji = "🚨" + status = "is missing blocks" + case MISSING_BLOCKS: + emoji = "🔴" + status = "is missing blocks" + case STOPPED_MISSING_BLOCKS: + emoji = "🟡" + status = "stopped missing blocks" + case WENT_BACK_TO_NORMAL: + emoji = "🟢" + status = "went back to normal" + } + + if entry.ValidatorAddress != "" { + validatorLink = fmt.Sprintf( + "", + MintscanPrefix, + entry.ValidatorAddress, + entry.ValidatorMoniker, + ) + } else { + validatorLink = fmt.Sprintf("validator with key `%s`", entry.Pubkey) + } + + sb.WriteString(fmt.Sprintf( + "%s *%s %s*: %d -> %d\n", + emoji, + validatorLink, + status, + entry.BeforeBlocksMissing, + entry.NowBlocksMissing, + )) + } + + return sb.String() +} + +func (r *SlackReporter) Init() { + if r.SlackToken == "" || r.SlackChat == "" { + log.Debug().Msg("Slack credentials not set, not creating Slack reporter.") + return + } + + client := slack.New(r.SlackToken) + r.SlackClient = *client +} + +func (r SlackReporter) Enabled() bool { + return r.SlackToken != "" && r.SlackChat != "" +} + +func (r SlackReporter) SendReport(report Report) error { + serializedReport := r.Serialize(report) + _, _, err := r.SlackClient.PostMessage( + r.SlackChat, + slack.MsgOptionText(serializedReport, false), + ) + return err +} + +func (r SlackReporter) Name() string { + return "SlackReporter" +}