@@ -18,59 +18,90 @@ class Alert:
1818HOUR = 3600
1919VALIDATION_PERIOD = 65536
2020FREEZE_PERIOD = 32768
21-
22-
23- ALERTS = {
24- "low_wallet_balance" : Alert (
25- "low" ,
26- "Validator wallet {wallet} balance is low: {balance} TON." ,
27- 18 * HOUR
28- ),
29- "db_usage_80" : Alert (
30- "high" ,
31- """TON DB usage > 80%. Clean the TON database:
32- https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
33- or (and) set node\' s archive ttl to lower value.""" ,
34- 24 * HOUR
35- ),
36- "db_usage_95" : Alert (
37- "critical" ,
38- """TON DB usage > 95%. Disk is almost full, clean the TON database immediately:
39- https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
40- or (and) set node\' s archive ttl to lower value.""" ,
41- 6 * HOUR
42- ),
43- "low_efficiency" : Alert (
44- "high" ,
45- """Validator efficiency is low: {efficiency}%.""" ,
46- VALIDATION_PERIOD // 3
47- ),
48- "out_of_sync" : Alert (
49- "critical" ,
50- "Node is out of sync on {sync} sec." ,
51- 300
52- ),
53- "service_down" : Alert (
54- "critical" ,
55- "validator.service is down." ,
56- 300
57- ),
58- "adnl_connection_failed" : Alert (
59- "high" ,
60- "ADNL connection to node failed" ,
61- 3 * HOUR
62- ),
63- "zero_block_created" : Alert (
64- "critical" ,
65- "Validator has not created any blocks in the last {hours} hours." ,
66- VALIDATION_PERIOD // 3
67- ),
68- "validator_slashed" : Alert (
69- "high" ,
70- "Validator has been slashed in previous round for {amount} TON" ,
71- FREEZE_PERIOD
72- ),
73- }
21+ ELECTIONS_START_BEFORE = 8192
22+
23+
24+ ALERTS = {}
25+
26+
27+ def init_alerts ():
28+ global ALERTS
29+ ALERTS = {
30+ "low_wallet_balance" : Alert (
31+ "low" ,
32+ "Validator wallet {wallet} balance is low: {balance} TON." ,
33+ 18 * HOUR
34+ ),
35+ "db_usage_80" : Alert (
36+ "high" ,
37+ """TON DB usage > 80%. Clean the TON database:
38+ https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
39+ or (and) set node\' s archive ttl to lower value.""" ,
40+ 24 * HOUR
41+ ),
42+ "db_usage_95" : Alert (
43+ "critical" ,
44+ """TON DB usage > 95%. Disk is almost full, clean the TON database immediately:
45+ https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
46+ or (and) set node\' s archive ttl to lower value.""" ,
47+ 6 * HOUR
48+ ),
49+ "low_efficiency" : Alert (
50+ "high" ,
51+ """Validator efficiency is low: {efficiency}%.""" ,
52+ VALIDATION_PERIOD // 3
53+ ),
54+ "out_of_sync" : Alert (
55+ "critical" ,
56+ "Node is out of sync on {sync} sec." ,
57+ 300
58+ ),
59+ "service_down" : Alert (
60+ "critical" ,
61+ "validator.service is down." ,
62+ 300
63+ ),
64+ "adnl_connection_failed" : Alert (
65+ "high" ,
66+ "ADNL connection to node failed" ,
67+ 3 * HOUR
68+ ),
69+ "zero_block_created" : Alert (
70+ "critical" ,
71+ "Validator has not created any blocks in the last {hours} hours." ,
72+ VALIDATION_PERIOD // 3
73+ ),
74+ "validator_slashed" : Alert (
75+ "high" ,
76+ "Validator has been slashed in previous round for {amount} TON" ,
77+ FREEZE_PERIOD
78+ ),
79+ "stake_not_accepted" : Alert (
80+ "high" ,
81+ "Validator's stake has not been accepted" ,
82+ ELECTIONS_START_BEFORE
83+ ),
84+ "stake_accepted" : Alert (
85+ "info" ,
86+ "Validator's stake {stake} TON has been accepted" ,
87+ ELECTIONS_START_BEFORE
88+ ),
89+ "stake_returned" : Alert (
90+ "info" ,
91+ "Validator's stake {stake} TON has been returned on address {address}. The reward amount is {reward} TON." ,
92+ 60
93+ ),
94+ "stake_not_returned" : Alert (
95+ "high" ,
96+ "Validator's stake has not been returned on address {address}." ,
97+ 60
98+ ),
99+ "voting" : Alert (
100+ "high" ,
101+ "Found proposals with hashes `{hashes}` that have significant amount of votes, but current validator didn't vote for them. Please check @tonstatus for more details." ,
102+ VALIDATION_PERIOD
103+ ),
104+ }
74105
75106
76107class AlertBotModule (MtcModule ):
@@ -88,13 +119,13 @@ def __init__(self, ton, local, *args, **kwargs):
88119 self .chat_id = None
89120 self .last_db_check = 0
90121
91- def send_message (self , text : str ):
122+ def send_message (self , text : str , silent : bool = False ):
92123 if self .token is None :
93124 raise Exception ("send_message error: token is not initialized" )
94125 if self .chat_id is None :
95126 raise Exception ("send_message error: chat_id is not initialized" )
96127 request_url = f"https://api.telegram.org/bot{ self .token } /sendMessage"
97- data = {'chat_id' : self .chat_id , 'text' : text , 'parse_mode' : 'HTML' }
128+ data = {'chat_id' : self .chat_id , 'text' : text , 'parse_mode' : 'HTML' , 'disable_notification' : silent }
98129 response = requests .post (request_url , data = data , timeout = 3 )
99130 if response .status_code != 200 :
100131 raise Exception (f"send_message error: { response .text } " )
@@ -122,15 +153,16 @@ def send_alert(self, alert_name: str, *args, **kwargs):
122153<blockquote> { alert .text .format (* args , ** kwargs )} </blockquote>
123154'''
124155 if time .time () - last_sent > alert .timeout :
125- self .send_message (text )
156+ self .send_message (text , alert . severity == "info" ) # send info alerts without sound
126157 self .set_alert_sent (alert_name )
127158
128159 def set_global_vars (self ):
129160 # set global vars for correct alerts timeouts for current network
130161 config15 = self .ton .GetConfig15 ()
131- global VALIDATION_PERIOD , FREEZE_PERIOD
162+ global VALIDATION_PERIOD , FREEZE_PERIOD , ELECTIONS_START_BEFORE
132163 VALIDATION_PERIOD = config15 ["validatorsElectedFor" ]
133164 FREEZE_PERIOD = config15 ["stakeHeldFor" ]
165+ ELECTIONS_START_BEFORE = config15 ["electionsStartBefore" ]
134166
135167 def init (self ):
136168 if not self .ton .get_mode_value ('alert-bot' ):
@@ -142,8 +174,9 @@ def init(self):
142174 from modules .validator import ValidatorModule
143175 self .validator_module = ValidatorModule (self .ton , self .local )
144176 self .hostname = get_hostname ()
145- self .ip = self .ton .get_validator_engine_ip ()
177+ self .ip = self .ton .get_node_ip ()
146178 self .set_global_vars ()
179+ init_alerts ()
147180 self .inited = True
148181
149182 def get_alert_from_db (self , alert_name : str ):
@@ -185,6 +218,7 @@ def disable_alert(self, args):
185218 color_print ("disable_alert - {green}OK{endc}" )
186219
187220 def print_alerts (self , args ):
221+ init_alerts ()
188222 table = [['Name' , 'Enabled' , 'Last sent' ]]
189223 for alert_name in ALERTS :
190224 alert = self .get_alert_from_db (alert_name )
@@ -271,6 +305,69 @@ def check_adnl_connection_failed(self):
271305 if not ok :
272306 self .send_alert ("adnl_connection_failed" )
273307
308+ def get_myself_from_election (self , config : dict ):
309+ if not config ["validators" ]:
310+ return
311+ adnl = self .ton .GetAdnlAddr ()
312+ save_elections = self .ton .GetSaveElections ()
313+ elections = save_elections .get (str (config ["startWorkTime" ]))
314+ if elections is None :
315+ return
316+ if adnl not in elections : # didn't participate in elections
317+ return
318+ validator = self .validator_module .find_myself (config ["validators" ])
319+ if validator is None :
320+ return False
321+ validator ['stake' ] = elections [adnl ].get ('stake' )
322+ validator ['walletAddr' ] = elections [adnl ].get ('walletAddr' )
323+ return validator
324+
325+ def check_stake_sent (self ):
326+ if not self .ton .using_validator ():
327+ return
328+ config = self .ton .GetConfig36 ()
329+ res = self .get_myself_from_election (config )
330+ if res is None :
331+ return
332+ if res is False :
333+ self .send_alert ("stake_not_accepted" )
334+ return
335+ self .send_alert ("stake_accepted" , stake = round (res .get ('stake' ), 2 ))
336+
337+ def check_stake_returned (self ):
338+ if not self .ton .using_validator ():
339+ return
340+ config = self .ton .GetConfig32 ()
341+ if not (config ['endWorkTime' ] + FREEZE_PERIOD + 1800 <= time .time () < config ['endWorkTime' ] + FREEZE_PERIOD + 1860 ): # check between 30th and 31st minutes after stakes have been unfrozen
342+ return
343+ res = self .get_myself_from_election (config )
344+ if not res :
345+ return
346+ trs = self .ton .GetAccountHistory (self .ton .GetAccount (res ["walletAddr" ]), limit = 10 )
347+
348+ for tr in trs :
349+ if tr .time >= config ['endWorkTime' ] + FREEZE_PERIOD and tr .srcAddr == '3333333333333333333333333333333333333333333333333333333333333333' and tr .body .startswith ('F96F7324' ): # Elector Recover Stake Response
350+ self .send_alert ("stake_returned" , stake = round (tr .value , 2 ), address = res ["walletAddr" ], reward = round (tr .value - res .get ('stake' , 0 ), 2 ))
351+ return
352+ self .send_alert ("stake_not_returned" , address = res ["walletAddr" ])
353+
354+ def check_voting (self ):
355+ if not self .ton .using_validator ():
356+ return
357+ validator_index = self .ton .GetValidatorIndex ()
358+ if validator_index == - 1 :
359+ return
360+ config = self .ton .GetConfig34 ()
361+ if time .time () - config ['startWorkTime' ] < 600 : # less than 10 minutes passed since round start
362+ return
363+ need_to_vote = []
364+ offers = self .ton .GetOffers ()
365+ for offer in offers :
366+ if not offer ['isPassed' ] and offer ['approvedPercent' ] >= 50 and validator_index not in offer ['votedValidators' ]:
367+ need_to_vote .append (offer ['hash' ])
368+ if need_to_vote :
369+ self .send_alert ("voting" , hashes = ' ' .join (need_to_vote ))
370+
274371 def check_status (self ):
275372 if not self .ton .using_alert_bot ():
276373 return
@@ -285,6 +382,9 @@ def check_status(self):
285382 self .local .try_function (self .check_sync )
286383 self .local .try_function (self .check_slashed )
287384 self .local .try_function (self .check_adnl_connection_failed )
385+ self .local .try_function (self .check_stake_sent )
386+ self .local .try_function (self .check_stake_returned )
387+ self .local .try_function (self .check_voting )
288388
289389 def add_console_commands (self , console ):
290390 console .AddItem ("enable_alert" , self .enable_alert , self .local .translate ("enable_alert_cmd" ))
0 commit comments