@@ -2,17 +2,27 @@ package cmd
2
2
3
3
import (
4
4
"context"
5
+ "encoding/json"
5
6
"errors"
6
7
"fmt"
8
+ "math/big"
9
+ "os"
7
10
"runtime"
8
- "strconv "
11
+ "sort "
9
12
"strings"
10
13
"time"
11
14
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
+
12
21
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
13
22
14
23
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
15
24
25
+ "github.com/MinterTeam/mhub2/module/solidity"
16
26
"github.com/MinterTeam/mhub2/module/x/mhub2/types"
17
27
18
28
"github.com/cosmos/cosmos-sdk/client"
@@ -22,52 +32,93 @@ import (
22
32
"github.com/cosmos/cosmos-sdk/client/flags"
23
33
)
24
34
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
+
25
45
// AddMonitorCmd returns monitor cobra Command.
26
46
func AddMonitorCmd () * cobra.Command {
27
47
cmd := & cobra.Command {
28
- Use : "monitor [our_address] [telegram_bot_token] [chat_id ]" ,
48
+ Use : "monitor [config ]" ,
29
49
Short : "" ,
30
50
Long : `` ,
31
- Args : cobra .ExactArgs (3 ),
51
+ Args : cobra .ExactArgs (1 ),
32
52
RunE : func (cmd * cobra.Command , args []string ) error {
33
- bot , err := tgbotapi . NewBotAPI (args [1 ])
53
+ config , err := readConfig (args [0 ])
34
54
if err != nil {
35
- panic ( err )
55
+ return err
36
56
}
37
57
38
- chatId , err := strconv . Atoi ( args [ 2 ] )
58
+ bot , err := tgbotapi . NewBotAPI ( config . TelegramToken )
39
59
if err != nil {
40
60
panic (err )
41
61
}
42
62
43
63
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 )
45
65
}
46
66
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
+ })
48
72
if err != nil {
49
73
panic (err )
50
74
}
51
75
52
- ourAddress := args [0 ]
53
- nonceErrorCounter := 0
54
- hasNonceError := false
76
+ var startMsg tgbotapi.Message
55
77
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 )
56
96
handleErr := func (err error ) {
57
97
pc , filename , line , _ := runtime .Caller (1 )
58
98
59
99
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 {
61
101
println (err .Error ())
62
102
}
63
103
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
+ })
65
112
}
66
113
67
- i := 0
114
+ initialized := false
115
+
68
116
for {
69
- time .Sleep (time .Second * 5 )
117
+ if initialized {
118
+ time .Sleep (time .Minute )
119
+ }
70
120
121
+ initialized = true
71
122
t := ""
72
123
73
124
clientCtx , err := client .GetClientQueryContext (cmd )
@@ -97,10 +148,18 @@ func AddMonitorCmd() *cobra.Command {
97
148
stakingQueryClient := stakingtypes .NewQueryClient (clientCtx )
98
149
vals , _ := stakingQueryClient .Validators (context .Background (), & stakingtypes.QueryValidatorsRequest {})
99
150
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
+
100
161
chains := []types.ChainID {"ethereum" , "minter" , "bsc" }
101
162
for _ , chain := range chains {
102
- t = fmt .Sprintf ("%s\n \n <b>%s</b>" , t , chain .String ())
103
-
104
163
delegatedKeys , err := queryClient .DelegateKeys (context .Background (), & types.DelegateKeysRequest {
105
164
ChainId : chain .String (),
106
165
})
@@ -109,17 +168,32 @@ func AddMonitorCmd() *cobra.Command {
109
168
continue
110
169
}
111
170
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 )
117
172
if err != nil {
118
173
handleErr (err )
119
174
continue
120
175
}
121
176
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
+ }
123
197
124
198
for _ , k := range delegatedKeys .GetDelegateKeys () {
125
199
response , err := queryClient .LastSubmittedExternalEvent (context .Background (), & types.LastSubmittedExternalEventRequest {
@@ -135,39 +209,58 @@ func AddMonitorCmd() *cobra.Command {
135
209
136
210
for _ , v := range vals .GetValidators () {
137
211
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
+ }
139
217
}
140
218
}
219
+ }
141
220
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 ())
145
233
}
234
+ t = fmt .Sprintf ("%s\n %s <b>%s</b> %d HUB" , t , alert , v .GetMoniker (), v .BondedTokens ().QuoRaw (1e18 ).Int64 ())
146
235
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 "
149
245
}
150
246
}
151
247
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
+ }
154
253
}
155
- hasNonceError = false
156
254
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
160
257
}
161
258
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 ())
169
263
}
170
- i ++
171
264
}
172
265
173
266
return nil
@@ -178,3 +271,99 @@ func AddMonitorCmd() *cobra.Command {
178
271
179
272
return cmd
180
273
}
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
+ }
0 commit comments