Skip to content

Commit f959971

Browse files
authored
Merge pull request #429 from yungwine/bot-ux
Bot ux
2 parents 3630892 + 6fdba7a commit f959971

File tree

2 files changed

+75
-15
lines changed

2 files changed

+75
-15
lines changed

modules/alert_bot.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
@dataclasses.dataclass
1212
class Alert:
1313
severity: str
14+
description: str
1415
text: str
1516
timeout: int
1617

@@ -29,75 +30,89 @@ def init_alerts():
2930
ALERTS = {
3031
"low_wallet_balance": Alert(
3132
"low",
32-
"Validator wallet {wallet} balance is low: {balance} TON.",
33+
"Validator's wallet balance is less than 10 TON",
34+
"Validator's wallet {wallet} balance is less than 10 TON: {balance} TON.",
3335
18 * HOUR
3436
),
3537
"db_usage_80": Alert(
3638
"high",
39+
"Node's db usage is more than 80%",
3740
"""TON DB usage > 80%. Clean the TON database:
3841
https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
3942
or (and) set node\'s archive ttl to lower value.""",
4043
24 * HOUR
4144
),
4245
"db_usage_95": Alert(
4346
"critical",
47+
"Node's db usage is more than 95%",
4448
"""TON DB usage > 95%. Disk is almost full, clean the TON database immediately:
4549
https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming
4650
or (and) set node\'s archive ttl to lower value.""",
4751
6 * HOUR
4852
),
4953
"low_efficiency": Alert(
5054
"high",
51-
"""Validator efficiency is low: {efficiency}%.""",
55+
"Validator had efficiency less than 90% in the validation round",
56+
"""Validator efficiency is less than 90%: <b>{efficiency}%</b>.""",
5257
VALIDATION_PERIOD // 3
5358
),
5459
"out_of_sync": Alert(
5560
"critical",
56-
"Node is out of sync on {sync} sec.",
61+
"Node is out of sync on more than 20 sec",
62+
"Node is out of sync on more than 20 sec: <b>{sync} sec</b>.",
5763
300
5864
),
5965
"service_down": Alert(
6066
"critical",
67+
"Node is not running (service is down)",
6168
"validator.service is down.",
6269
300
6370
),
6471
"adnl_connection_failed": Alert(
6572
"high",
73+
"Node is not answering to ADNL connection",
6674
"ADNL connection to node failed",
6775
3 * HOUR
6876
),
6977
"zero_block_created": Alert(
7078
"critical",
79+
f"Validator has not created any blocks in the {int(VALIDATION_PERIOD // 3 // 3600)} hours",
7180
"Validator has not created any blocks in the last {hours} hours.",
7281
VALIDATION_PERIOD // 3
7382
),
7483
"validator_slashed": Alert(
7584
"high",
85+
"Validator has been slashed in the previous validation round",
7686
"Validator has been slashed in previous round for {amount} TON",
7787
FREEZE_PERIOD
7888
),
7989
"stake_not_accepted": Alert(
8090
"high",
8191
"Validator's stake has not been accepted",
92+
"Validator's stake has not been accepted",
8293
ELECTIONS_START_BEFORE
8394
),
8495
"stake_accepted": Alert(
8596
"info",
97+
"Validator's stake has been accepted (info alert with no sound)",
8698
"Validator's stake {stake} TON has been accepted",
8799
ELECTIONS_START_BEFORE
88100
),
89101
"stake_returned": Alert(
90102
"info",
91-
"Validator's stake {stake} TON has been returned on address {address}. The reward amount is {reward} TON.",
103+
"Validator's stake has been returned (info alert with no sound)",
104+
"Validator's stake {stake} TON has been returned on address <code>{address}</code>. The reward amount is {reward} TON.",
92105
60
93106
),
94107
"stake_not_returned": Alert(
95108
"high",
96-
"Validator's stake has not been returned on address {address}.",
109+
"Validator's stake has not been returned",
110+
"Validator's stake has not been returned on address <code>{address}.</code>",
97111
60
98112
),
99113
"voting": Alert(
100114
"high",
115+
"There is an active network proposal that has many votes (more than 50% of required) but is not voted by the validator",
101116
"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.",
102117
VALIDATION_PERIOD
103118
),
@@ -115,18 +130,19 @@ def __init__(self, ton, local, *args, **kwargs):
115130
self.inited = False
116131
self.hostname = None
117132
self.ip = None
133+
self.adnl = None
118134
self.token = None
119135
self.chat_id = None
120136
self.last_db_check = 0
121137

122-
def send_message(self, text: str, silent: bool = False):
138+
def send_message(self, text: str, silent: bool = False, disable_web_page_preview: bool = False):
123139
if self.token is None:
124140
raise Exception("send_message error: token is not initialized")
125141
if self.chat_id is None:
126142
raise Exception("send_message error: chat_id is not initialized")
127143
request_url = f"https://api.telegram.org/bot{self.token}/sendMessage"
128-
data = {'chat_id': self.chat_id, 'text': text, 'parse_mode': 'HTML', 'disable_notification': silent}
129-
response = requests.post(request_url, data=data, timeout=3)
144+
data = {'chat_id': self.chat_id, 'text': text, 'parse_mode': 'HTML', 'disable_notification': silent, 'link_preview_options': {'is_disabled': disable_web_page_preview}}
145+
response = requests.post(request_url, json=data, timeout=3)
130146
if response.status_code != 200:
131147
raise Exception(f"send_message error: {response.text}")
132148
response = response.json()
@@ -141,16 +157,19 @@ def send_alert(self, alert_name: str, *args, **kwargs):
141157
alert = ALERTS.get(alert_name)
142158
if alert is None:
143159
raise Exception(f"Alert {alert_name} not found")
144-
text = f'''
145-
❗️ <b>MyTonCtrl Alert {alert_name}</b> ❗️
160+
alert_name_readable = alert_name.replace('_', ' ').title()
161+
text = '🆘' if alert.severity != 'info' else ''
162+
text += f''' <b>Node {self.hostname}: {alert_name_readable} </b>
163+
164+
{alert.text.format(*args, **kwargs)}
146165
147166
Hostname: <code>{self.hostname}</code>
148167
Node IP: <code>{self.ip}</code>
168+
ADNL: <code>{self.adnl}</code>
169+
Wallet: <code>{self.wallet}</code>
149170
Time: <code>{time_}</code> (<code>{int(time.time())}</code>)
171+
Alert name: <code>{alert_name}</code>
150172
Severity: <code>{alert.severity}</code>
151-
152-
Alert text:
153-
<blockquote> {alert.text.format(*args, **kwargs)} </blockquote>
154173
'''
155174
if time.time() - last_sent > alert.timeout:
156175
self.send_message(text, alert.severity == "info") # send info alerts without sound
@@ -174,6 +193,9 @@ def init(self):
174193
from modules.validator import ValidatorModule
175194
self.validator_module = ValidatorModule(self.ton, self.local)
176195
self.hostname = get_hostname()
196+
adnl = self.ton.GetAdnlAddr()
197+
self.adnl = adnl
198+
self.wallet = self.ton.GetValidatorWallet().addrB64
177199
self.ip = self.ton.get_node_ip()
178200
self.set_global_vars()
179201
init_alerts()
@@ -230,6 +252,39 @@ def test_alert(self, args):
230252
self.init()
231253
self.send_message('Test alert')
232254

255+
def send_welcome_message(self):
256+
message = f"""
257+
This is alert bot. You have connected validator with ADNL <code>{self.ton.GetAdnlAddr()}</code>.
258+
259+
I don't process any commands, I only send notifications.
260+
261+
Current notifications enabled:
262+
263+
"""
264+
for alert in ALERTS.values():
265+
message += f"- {alert.description}\n"
266+
267+
message += """
268+
If you want, you can disable some notifications in mytonctrl by the <a href="https://docs.ton.org/v3/guidelines/nodes/maintenance-guidelines/mytonctrl-private-alerting#endisbling-alerts"> instruction</a>.
269+
270+
Full bot documentation <a href="https://docs.ton.org/v3/guidelines/nodes/maintenance-guidelines/mytonctrl-private-alerting">here</a>.
271+
"""
272+
self.send_message(text=message, disable_web_page_preview=True)
273+
274+
def on_set_chat_id(self, chat_id):
275+
self.token = self.ton.local.db.get("BotToken")
276+
if self.token is None:
277+
raise Exception("BotToken is not set")
278+
self.chat_id = chat_id
279+
init_alerts()
280+
try:
281+
self.send_welcome_message()
282+
return True
283+
except Exception as e:
284+
self.local.add_log(f"Error while sending welcome message: {e}", "error")
285+
self.local.add_log(f"If you want the bot to write to a multi-person chat group, make sure the bot is added to that chat group. If it is not - do it and run the command `set ChatId <ChatId>` again.", "info")
286+
return False
287+
233288
def check_db_usage(self):
234289
if time.time() - self.last_db_check < 600:
235290
return
@@ -371,7 +426,8 @@ def check_voting(self):
371426
def check_status(self):
372427
if not self.ton.using_alert_bot():
373428
return
374-
if not self.inited:
429+
430+
if not self.inited or self.token != self.ton.local.db.get("BotToken") or self.chat_id != self.ton.local.db.get("ChatId"):
375431
self.init()
376432

377433
self.local.try_function(self.check_db_usage)

mytonctrl/mytonctrl.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ def GetSettings(ton, args):
883883
print(json.dumps(result, indent=2))
884884
#end define
885885

886-
def SetSettings(ton, args):
886+
def SetSettings(local, ton, args):
887887
try:
888888
name = args[0]
889889
value = args[1]
@@ -895,6 +895,10 @@ def SetSettings(ton, args):
895895
color_print(f"{{red}} Error: set {name} ... is deprecated and does not work {{endc}}."
896896
f"\nInstead, use {{bold}}enable_mode {mode_name}{{endc}}")
897897
return
898+
if name == 'ChatId':
899+
from modules.alert_bot import AlertBotModule
900+
if not AlertBotModule(ton, local).on_set_chat_id(value):
901+
return
898902
force = False
899903
if len(args) > 2:
900904
if args[2] == "--force":

0 commit comments

Comments
 (0)