Skip to content

Commit 5d60c77

Browse files
authored
Monitor v2 (#12)
1 parent 254408a commit 5d60c77

File tree

5 files changed

+1651
-74
lines changed

5 files changed

+1651
-74
lines changed

module/cmd/mhub2/cmd/monitor.go

+232-43
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,27 @@ package cmd
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"fmt"
8+
"math/big"
9+
"os"
710
"runtime"
8-
"strconv"
11+
"sort"
912
"strings"
1013
"time"
1114

15+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
16+
17+
"github.com/ethereum/go-ethereum/common"
18+
19+
"github.com/ethereum/go-ethereum/ethclient"
20+
1221
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
1322

1423
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
1524

25+
"github.com/MinterTeam/mhub2/module/solidity"
1626
"github.com/MinterTeam/mhub2/module/x/mhub2/types"
1727

1828
"github.com/cosmos/cosmos-sdk/client"
@@ -22,52 +32,93 @@ import (
2232
"github.com/cosmos/cosmos-sdk/client/flags"
2333
)
2434

35+
type MonitorConfig struct {
36+
OurAddress string `json:"our_address"`
37+
TelegramToken string `json:"telegram_token"`
38+
ChatID int64 `json:"chat_id"`
39+
EthereumRPC []string `json:"ethereum_rpc"`
40+
BNBChainRPC []string `json:"bnb_chain_rpc"`
41+
}
42+
43+
const blockDelay = 6
44+
2545
// AddMonitorCmd returns monitor cobra Command.
2646
func AddMonitorCmd() *cobra.Command {
2747
cmd := &cobra.Command{
28-
Use: "monitor [our_address] [telegram_bot_token] [chat_id]",
48+
Use: "monitor [config]",
2949
Short: "",
3050
Long: ``,
31-
Args: cobra.ExactArgs(3),
51+
Args: cobra.ExactArgs(1),
3252
RunE: func(cmd *cobra.Command, args []string) error {
33-
bot, err := tgbotapi.NewBotAPI(args[1])
53+
config, err := readConfig(args[0])
3454
if err != nil {
35-
panic(err)
55+
return err
3656
}
3757

38-
chatId, err := strconv.Atoi(args[2])
58+
bot, err := tgbotapi.NewBotAPI(config.TelegramToken)
3959
if err != nil {
4060
panic(err)
4161
}
4262

4363
newText := func(t string) string {
44-
return fmt.Sprintf("Watching...\n%s%s", time.Now().Format(time.Stamp), t)
64+
return fmt.Sprintf("%s\n%s", time.Now().Format(time.Stamp), t)
4565
}
4666

47-
startMsg, err := bot.Send(tgbotapi.NewMessage(int64(chatId), newText("")))
67+
chat, err := bot.GetChat(tgbotapi.ChatInfoConfig{
68+
ChatConfig: tgbotapi.ChatConfig{
69+
ChatID: config.ChatID,
70+
},
71+
})
4872
if err != nil {
4973
panic(err)
5074
}
5175

52-
ourAddress := args[0]
53-
nonceErrorCounter := 0
54-
hasNonceError := false
76+
var startMsg tgbotapi.Message
5577

78+
if chat.PinnedMessage != nil {
79+
startMsg = *chat.PinnedMessage
80+
} else {
81+
msg := tgbotapi.NewMessage(config.ChatID, newText(""))
82+
msg.DisableNotification = true
83+
startMsg, err = bot.Send(msg)
84+
if err != nil {
85+
panic(err)
86+
}
87+
88+
bot.Request(tgbotapi.PinChatMessageConfig{
89+
ChatID: startMsg.Chat.ID,
90+
MessageID: startMsg.MessageID,
91+
DisableNotification: true,
92+
})
93+
}
94+
95+
nonceErrorCounter := make(map[types.ChainID]int)
5696
handleErr := func(err error) {
5797
pc, filename, line, _ := runtime.Caller(1)
5898

5999
str := fmt.Sprintf("[error] in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), filename, line, err)
60-
if _, err := bot.Send(tgbotapi.NewMessage(int64(chatId), str)); err != nil {
100+
if _, err := bot.Send(tgbotapi.NewMessage(config.ChatID, str)); err != nil {
61101
println(err.Error())
62102
}
63103

64-
startMsg, _ = bot.Send(tgbotapi.NewMessage(int64(chatId), newText("")))
104+
msg := tgbotapi.NewMessage(config.ChatID, newText(""))
105+
msg.DisableNotification = true
106+
startMsg, _ = bot.Send(msg)
107+
bot.Request(tgbotapi.PinChatMessageConfig{
108+
ChatID: startMsg.Chat.ID,
109+
MessageID: startMsg.MessageID,
110+
DisableNotification: true,
111+
})
65112
}
66113

67-
i := 0
114+
initialized := false
115+
68116
for {
69-
time.Sleep(time.Second * 5)
117+
if initialized {
118+
time.Sleep(time.Minute)
119+
}
70120

121+
initialized = true
71122
t := ""
72123

73124
clientCtx, err := client.GetClientQueryContext(cmd)
@@ -97,10 +148,18 @@ func AddMonitorCmd() *cobra.Command {
97148
stakingQueryClient := stakingtypes.NewQueryClient(clientCtx)
98149
vals, _ := stakingQueryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{})
99150

151+
valHasFailure := map[string]bool{}
152+
valHasNonceFailure := map[string]map[types.ChainID]uint64{}
153+
failuresLog := ""
154+
155+
for _, val := range vals.GetValidators() {
156+
valHasNonceFailure[val.OperatorAddress] = map[types.ChainID]uint64{}
157+
}
158+
159+
actualNonces := map[types.ChainID]uint64{}
160+
100161
chains := []types.ChainID{"ethereum", "minter", "bsc"}
101162
for _, chain := range chains {
102-
t = fmt.Sprintf("%s\n\n<b>%s</b>", t, chain.String())
103-
104163
delegatedKeys, err := queryClient.DelegateKeys(context.Background(), &types.DelegateKeysRequest{
105164
ChainId: chain.String(),
106165
})
@@ -109,17 +168,32 @@ func AddMonitorCmd() *cobra.Command {
109168
continue
110169
}
111170

112-
response, err := queryClient.LastSubmittedExternalEvent(context.Background(), &types.LastSubmittedExternalEventRequest{
113-
Address: ourAddress,
114-
ChainId: chain.String(),
115-
})
116-
171+
actualNonce, err := getActualNonce(chain, config, delegatedKeys.GetDelegateKeys(), queryClient)
117172
if err != nil {
118173
handleErr(err)
119174
continue
120175
}
121176

122-
nonce := response.EventNonce
177+
actualNonces[chain] = actualNonce
178+
179+
if config.OurAddress != "" {
180+
response, err := queryClient.LastSubmittedExternalEvent(context.Background(), &types.LastSubmittedExternalEventRequest{
181+
Address: config.OurAddress,
182+
ChainId: chain.String(),
183+
})
184+
185+
if err != nil {
186+
handleErr(err)
187+
continue
188+
}
189+
190+
ourNonce := response.EventNonce
191+
if ourNonce < actualNonce {
192+
nonceErrorCounter[chain]++
193+
} else {
194+
nonceErrorCounter[chain] = 0
195+
}
196+
}
123197

124198
for _, k := range delegatedKeys.GetDelegateKeys() {
125199
response, err := queryClient.LastSubmittedExternalEvent(context.Background(), &types.LastSubmittedExternalEventRequest{
@@ -135,39 +209,58 @@ func AddMonitorCmd() *cobra.Command {
135209

136210
for _, v := range vals.GetValidators() {
137211
if v.OperatorAddress == k.ValidatorAddress {
138-
t = fmt.Sprintf("%s\n%d %s", t, response.GetEventNonce(), v.GetMoniker())
212+
nonce := response.GetEventNonce()
213+
if nonce < actualNonce {
214+
valHasNonceFailure[v.OperatorAddress][chain] = nonce
215+
valHasFailure[v.OperatorAddress] = true
216+
}
139217
}
140218
}
219+
}
141220

142-
if nonce < response.GetEventNonce() {
143-
hasNonceError = true
144-
}
221+
}
222+
223+
sortedVals := vals.GetValidators()
224+
sort.Slice(sortedVals, func(i, j int) bool {
225+
return sortedVals[i].BondedTokens().GT(sortedVals[j].BondedTokens())
226+
})
227+
228+
for _, v := range sortedVals {
229+
alert := "🟢"
230+
if valHasFailure[v.OperatorAddress] {
231+
alert = fmt.Sprintf("🔴️")
232+
failuresLog = fmt.Sprintf("%s⚠️️ <b>%s</b> ", failuresLog, v.GetMoniker())
145233
}
234+
t = fmt.Sprintf("%s\n%s <b>%s</b> %d HUB", t, alert, v.GetMoniker(), v.BondedTokens().QuoRaw(1e18).Int64())
146235

147-
if !hasNonceError {
148-
nonceErrorCounter = 0
236+
if valHasFailure[v.OperatorAddress] {
237+
var nonceErrs []string
238+
for _, chain := range chains {
239+
if nonce, ok := valHasNonceFailure[v.OperatorAddress][chain]; ok {
240+
nonceErrs = append(nonceErrs, fmt.Sprintf("nonce <b>%d</b> of <b>%d</b> on <b>%s</b>", nonce, actualNonces[chain], chain.String()))
241+
}
242+
}
243+
244+
failuresLog += strings.Join(nonceErrs, ", ") + "\n"
149245
}
150246
}
151247

152-
if hasNonceError {
153-
nonceErrorCounter += 1
248+
for _, chain := range chains {
249+
if nonceErrorCounter[chain] > 5 {
250+
handleErr(errors.New("event nonce on " + chain.String() + " was not updated for too long"))
251+
continue
252+
}
154253
}
155-
hasNonceError = false
156254

157-
if nonceErrorCounter > 5 {
158-
handleErr(errors.New("event nonce on some external network was not updated for too long. Check your orchestrators and minter-connector"))
159-
continue
255+
if failuresLog != "" {
256+
t = t + "\n\n" + failuresLog
160257
}
161258

162-
if i%12 == 0 {
163-
msg := tgbotapi.NewEditMessageText(startMsg.Chat.ID, startMsg.MessageID, newText(t))
164-
msg.ParseMode = "html"
165-
_, err := bot.Send(msg)
166-
if err != nil {
167-
println(err.Error())
168-
}
259+
msg := tgbotapi.NewEditMessageText(startMsg.Chat.ID, startMsg.MessageID, newText(t))
260+
msg.ParseMode = "html"
261+
if _, err := bot.Send(msg); err != nil {
262+
println(err.Error())
169263
}
170-
i++
171264
}
172265

173266
return nil
@@ -178,3 +271,99 @@ func AddMonitorCmd() *cobra.Command {
178271

179272
return cmd
180273
}
274+
275+
func getActualNonce(chain types.ChainID, config MonitorConfig, keys []*types.MsgDelegateKeys, queryClient types.QueryClient) (uint64, error) {
276+
switch chain {
277+
case "ethereum", "bsc":
278+
var address common.Address
279+
var RPCs []string
280+
281+
switch chain {
282+
case "ethereum":
283+
address = common.HexToAddress("0x897c27fa372aa730d4c75b1243e7ea38879194e2")
284+
RPCs = config.EthereumRPC
285+
case "bsc":
286+
address = common.HexToAddress("0xf5b0ed82a0b3e11567081694cc66c3df133f7c8f")
287+
RPCs = config.BNBChainRPC
288+
}
289+
290+
maxNonce, err := getEvmNonce(address, RPCs)
291+
if err != nil {
292+
return 0, err
293+
}
294+
295+
if maxNonce == 0 {
296+
return 0, errors.New("no available nonce source for " + chain.String())
297+
}
298+
299+
return maxNonce, nil
300+
case "minter":
301+
maxNonce := uint64(0)
302+
for _, k := range keys {
303+
response, err := queryClient.LastSubmittedExternalEvent(context.Background(), &types.LastSubmittedExternalEventRequest{
304+
Address: k.OrchestratorAddress,
305+
ChainId: chain.String(),
306+
})
307+
if err != nil {
308+
if !strings.Contains(err.Error(), "validator is not bonded") {
309+
return 0, err
310+
}
311+
}
312+
313+
if maxNonce < response.GetEventNonce() {
314+
maxNonce = response.GetEventNonce()
315+
}
316+
}
317+
318+
return maxNonce, nil
319+
}
320+
321+
return 0, nil
322+
}
323+
324+
func getEvmNonce(address common.Address, RPCs []string) (uint64, error) {
325+
maxNonce := uint64(0)
326+
for _, rpc := range RPCs {
327+
evmClient, err := ethclient.Dial(rpc)
328+
if err != nil {
329+
continue
330+
}
331+
332+
instance, err := solidity.NewHub2(address, evmClient)
333+
if err != nil {
334+
continue
335+
}
336+
337+
latestBlock, err := evmClient.BlockNumber(context.TODO())
338+
if err != nil {
339+
continue
340+
}
341+
342+
lastNonce, err := instance.StateLastEventNonce(&bind.CallOpts{
343+
BlockNumber: big.NewInt(int64(latestBlock - blockDelay)),
344+
})
345+
if err != nil {
346+
continue
347+
}
348+
349+
if maxNonce < lastNonce.Uint64() {
350+
maxNonce = lastNonce.Uint64()
351+
}
352+
}
353+
354+
return maxNonce, nil
355+
}
356+
357+
func readConfig(path string) (MonitorConfig, error) {
358+
config := MonitorConfig{}
359+
configBody, err := os.ReadFile(path)
360+
if err != nil {
361+
return MonitorConfig{}, err
362+
}
363+
364+
if err := json.Unmarshal(configBody, &config); err != nil {
365+
return MonitorConfig{}, err
366+
}
367+
368+
return config, nil
369+
}

module/config-monitor.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"our_address": "hub1...",
3+
"telegram_token": "",
4+
"chat_id": 0,
5+
"ethereum_rpc": ["https://mainnet.infura.io/v3/"],
6+
"bnb_chain_rpc": ["https://bscrpc.com/"]
7+
}

0 commit comments

Comments
 (0)