@@ -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 ):
@@ -83,17 +114,18 @@ def __init__(self, ton, local, *args, **kwargs):
83114 self .validator_module = None
84115 self .inited = False
85116 self .hostname = None
117+ self .ip = None
86118 self .token = None
87119 self .chat_id = None
88120 self .last_db_check = 0
89121
90- def send_message (self , text : str ):
122+ def send_message (self , text : str , silent : bool = False ):
91123 if self .token is None :
92124 raise Exception ("send_message error: token is not initialized" )
93125 if self .chat_id is None :
94126 raise Exception ("send_message error: chat_id is not initialized" )
95127 request_url = f"https://api.telegram.org/bot{ self .token } /sendMessage"
96- 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 }
97129 response = requests .post (request_url , data = data , timeout = 3 )
98130 if response .status_code != 200 :
99131 raise Exception (f"send_message error: { response .text } " )
@@ -113,22 +145,24 @@ def send_alert(self, alert_name: str, *args, **kwargs):
113145❗️ <b>MyTonCtrl Alert { alert_name } </b> ❗️
114146
115147Hostname: <code>{ self .hostname } </code>
148+ Node IP: <code>{ self .ip } </code>
116149Time: <code>{ time_ } </code> (<code>{ int (time .time ())} </code>)
117150Severity: <code>{ alert .severity } </code>
118151
119152Alert text:
120153<blockquote> { alert .text .format (* args , ** kwargs )} </blockquote>
121154'''
122155 if time .time () - last_sent > alert .timeout :
123- self .send_message (text )
156+ self .send_message (text , alert . severity == "info" ) # send info alerts without sound
124157 self .set_alert_sent (alert_name )
125158
126159 def set_global_vars (self ):
127160 # set global vars for correct alerts timeouts for current network
128161 config15 = self .ton .GetConfig15 ()
129- global VALIDATION_PERIOD , FREEZE_PERIOD
162+ global VALIDATION_PERIOD , FREEZE_PERIOD , ELECTIONS_START_BEFORE
130163 VALIDATION_PERIOD = config15 ["validatorsElectedFor" ]
131164 FREEZE_PERIOD = config15 ["stakeHeldFor" ]
165+ ELECTIONS_START_BEFORE = config15 ["electionsStartBefore" ]
132166
133167 def init (self ):
134168 if not self .ton .get_mode_value ('alert-bot' ):
@@ -140,7 +174,9 @@ def init(self):
140174 from modules .validator import ValidatorModule
141175 self .validator_module = ValidatorModule (self .ton , self .local )
142176 self .hostname = get_hostname ()
177+ self .ip = self .ton .get_node_ip ()
143178 self .set_global_vars ()
179+ init_alerts ()
144180 self .inited = True
145181
146182 def get_alert_from_db (self , alert_name : str ):
@@ -206,6 +242,9 @@ def check_db_usage(self):
206242 def check_validator_wallet_balance (self ):
207243 if not self .ton .using_validator ():
208244 return
245+ validator_status = self .ton .GetValidatorStatus ()
246+ if not validator_status .is_working or validator_status .out_of_sync >= 20 :
247+ return
209248 validator_wallet = self .ton .GetValidatorWallet ()
210249 validator_account = self .ton .GetAccount (validator_wallet .addrB64 )
211250 if validator_account .balance < 10 :
@@ -265,6 +304,66 @@ def check_adnl_connection_failed(self):
265304 if not ok :
266305 self .send_alert ("adnl_connection_failed" )
267306
307+ def get_myself_from_election (self , config : dict ):
308+ if not config ["validators" ]:
309+ return
310+ adnl = self .ton .GetAdnlAddr ()
311+ save_elections = self .ton .GetSaveElections ()
312+ elections = save_elections .get (str (config ["startWorkTime" ]))
313+ if elections is None :
314+ return
315+ if adnl not in elections : # didn't participate in elections
316+ return
317+ validator = self .validator_module .find_myself (config ["validators" ])
318+ if validator is None :
319+ return False
320+ validator ['stake' ] = elections [adnl ].get ('stake' )
321+ validator ['walletAddr' ] = elections [adnl ].get ('walletAddr' )
322+ return validator
323+
324+ def check_stake_sent (self ):
325+ if not self .ton .using_validator ():
326+ return
327+ config = self .ton .GetConfig36 ()
328+ res = self .get_myself_from_election (config )
329+ if res is None :
330+ return
331+ if res is False :
332+ self .send_alert ("stake_not_accepted" )
333+ return
334+ self .send_alert ("stake_accepted" , stake = round (res .get ('stake' ), 2 ))
335+
336+ def check_stake_returned (self ):
337+ if not self .ton .using_validator ():
338+ return
339+ config = self .ton .GetConfig32 ()
340+ if not (config ['endWorkTime' ] + FREEZE_PERIOD + 1800 <= time .time () < config ['endWorkTime' ] + FREEZE_PERIOD + 1860 ): # check between 25th and 26th minutes after stakes have been unfrozen
341+ return
342+ res = self .get_myself_from_election (config )
343+ if not res :
344+ return
345+ trs = self .ton .GetAccountHistory (self .ton .GetAccount (res ["walletAddr" ]), limit = 10 )
346+
347+ for tr in trs :
348+ if tr .time >= config ['endWorkTime' ] + FREEZE_PERIOD and tr .srcAddr == '3333333333333333333333333333333333333333333333333333333333333333' and tr .body .startswith ('F96F7324' ): # Elector Recover Stake Response
349+ self .send_alert ("stake_returned" , stake = round (tr .value , 2 ), address = res ["walletAddr" ], reward = round (tr .value - res .get ('stake' , 0 ), 2 ))
350+ return
351+ self .send_alert ("stake_not_returned" , address = res ["walletAddr" ])
352+
353+ def check_voting (self ):
354+ if not self .ton .using_validator ():
355+ return
356+ validator_index = self .ton .GetValidatorIndex ()
357+ if validator_index == - 1 :
358+ return
359+ need_to_vote = []
360+ offers = self .ton .GetOffers ()
361+ for offer in offers :
362+ if not offer ['isPassed' ] and offer ['approvedPercent' ] >= 50 and validator_index not in offer ['votedValidators' ]:
363+ need_to_vote .append (offer ['hash' ])
364+ if need_to_vote :
365+ self .send_alert ("voting" , hashes = ' ' .join (need_to_vote ))
366+
268367 def check_status (self ):
269368 if not self .ton .using_alert_bot ():
270369 return
@@ -279,6 +378,9 @@ def check_status(self):
279378 self .local .try_function (self .check_sync )
280379 self .local .try_function (self .check_slashed )
281380 self .local .try_function (self .check_adnl_connection_failed )
381+ self .local .try_function (self .check_stake_sent )
382+ self .local .try_function (self .check_stake_returned )
383+ self .local .try_function (self .check_voting )
282384
283385 def add_console_commands (self , console ):
284386 console .AddItem ("enable_alert" , self .enable_alert , self .local .translate ("enable_alert_cmd" ))
0 commit comments