Skip to content

Commit 319a50d

Browse files
committed
Merge remote-tracking branch 'tonblkch/dev' into dev
2 parents e0d87d1 + 2fb8239 commit 319a50d

File tree

20 files changed

+685
-208
lines changed

20 files changed

+685
-208
lines changed

modules/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from modules.controller import ControllerModule
1010
from modules.liteserver import LiteserverModule
1111
from modules.alert_bot import AlertBotModule
12+
from modules.prometheus import PrometheusModule
1213

1314

1415
MODES = {
@@ -17,7 +18,8 @@
1718
'single-nominator': SingleNominatorModule,
1819
'liquid-staking': ControllerModule,
1920
'liteserver': LiteserverModule,
20-
'alert-bot': AlertBotModule
21+
'alert-bot': AlertBotModule,
22+
'prometheus': PrometheusModule
2123
}
2224

2325

@@ -58,7 +60,11 @@ class Setting:
5860
'debug': Setting(None, False, 'Debug mtc console mode. Prints Traceback on errors'),
5961
'subscribe_tg_channel': Setting('validator', False, 'Disables warning about subscribing to the `TON STATUS` channel'),
6062
'BotToken': Setting('alert-bot', None, 'Alerting Telegram bot token'),
61-
'ChatId': Setting('alert-bot', None, 'Alerting Telegram chat id')
63+
'ChatId': Setting('alert-bot', None, 'Alerting Telegram chat id'),
64+
'auto_backup': Setting('validator', None, 'Make validator backup every election'),
65+
'auto_backup_path': Setting('validator', '/tmp/mytoncore/auto_backups/', 'Path to store auto-backups'),
66+
'prometheus_url': Setting('prometheus', None, 'Prometheus pushgateway url'),
67+
'onlyNode': Setting(None, None, 'MyTonCtrl will work only for collecting validator telemetry (if `sendTelemetry` is True), without participating in Elections and etc.')
6268
}
6369

6470

modules/alert_bot.py

Lines changed: 159 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,90 @@ class Alert:
1818
HOUR = 3600
1919
VALIDATION_PERIOD = 65536
2020
FREEZE_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

76107
class 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
115147
Hostname: <code>{self.hostname}</code>
148+
Node IP: <code>{self.ip}</code>
116149
Time: <code>{time_}</code> (<code>{int(time.time())}</code>)
117150
Severity: <code>{alert.severity}</code>
118151
119152
Alert 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

Comments
 (0)