Skip to content

Commit ee82cb6

Browse files
authored
Merge pull request #425 from ton-blockchain/dev
Dev
2 parents a4144f0 + eacd508 commit ee82cb6

File tree

19 files changed

+511
-145
lines changed

19 files changed

+511
-145
lines changed

modules/__init__.py

Lines changed: 5 additions & 1 deletion
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

@@ -61,6 +63,8 @@ class Setting:
6163
'ChatId': Setting('alert-bot', None, 'Alerting Telegram chat id'),
6264
'auto_backup': Setting('validator', None, 'Make validator backup every election'),
6365
'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.')
6468
}
6569

6670

modules/alert_bot.py

Lines changed: 158 additions & 58 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):
@@ -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"))

modules/backups.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def create_tmp_ton_dir(self):
2727
self.create_keyring(dir_name_db)
2828
return dir_name
2929

30+
@staticmethod
31+
def run_create_backup(args):
32+
backup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/create_backup.sh')
33+
return subprocess.run(["bash", backup_script_path] + args, timeout=5)
34+
3035
def create_backup(self, args):
3136
if len(args) > 1:
3237
color_print("{red}Bad args. Usage:{endc} create_backup [filename]")
@@ -35,8 +40,7 @@ def create_backup(self, args):
3540
command_args = ["-m", self.ton.local.buffer.my_work_dir, "-t", tmp_dir]
3641
if len(args) == 1:
3742
command_args += ["-d", args[0]]
38-
backup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/create_backup.sh')
39-
process = subprocess.run(["bash", backup_script_path] + command_args, timeout=5)
43+
process = self.run_create_backup(command_args)
4044

4145
if process.returncode == 0:
4246
color_print("create_backup - {green}OK{endc}")
@@ -46,6 +50,11 @@ def create_backup(self, args):
4650
return process.returncode
4751
# end define
4852

53+
@staticmethod
54+
def run_restore_backup(args):
55+
restore_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/restore_backup.sh')
56+
return run_as_root(["bash", restore_script_path] + args)
57+
4958
def restore_backup(self, args):
5059
if len(args) == 0 or len(args) > 2:
5160
color_print("{red}Bad args. Usage:{endc} restore_backup <filename> [-y]")
@@ -67,8 +76,7 @@ def restore_backup(self, args):
6776
ip = str(ip2int(get_own_ip()))
6877
command_args = ["-m", self.ton.local.buffer.my_work_dir, "-n", args[0], "-i", ip]
6978

70-
restore_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/restore_backup.sh')
71-
if run_as_root(["bash", restore_script_path] + command_args) == 0:
79+
if self.run_restore_backup(command_args) == 0:
7280
color_print("restore_backup - {green}OK{endc}")
7381
self.local.exit()
7482
else:

0 commit comments

Comments
 (0)