From 0c398a1a2365d641d387df6d96b7265f81f1a3d1 Mon Sep 17 00:00:00 2001 From: yungwine Date: Tue, 11 Feb 2025 11:32:36 +0400 Subject: [PATCH 1/4] update alert message format --- modules/alert_bot.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/alert_bot.py b/modules/alert_bot.py index 45a88a98..318d604a 100644 --- a/modules/alert_bot.py +++ b/modules/alert_bot.py @@ -115,6 +115,7 @@ def __init__(self, ton, local, *args, **kwargs): self.inited = False self.hostname = None self.ip = None + self.adnl = None self.token = None self.chat_id = None self.last_db_check = 0 @@ -141,16 +142,19 @@ def send_alert(self, alert_name: str, *args, **kwargs): alert = ALERTS.get(alert_name) if alert is None: raise Exception(f"Alert {alert_name} not found") - text = f''' -❗️ MyTonCtrl Alert {alert_name} ❗️ + alert_name_readable = alert_name.replace('_', ' ').title() + text = '🆘' if alert.severity != 'info' else '' + text += f''' +️ Node {self.hostname}: {alert_name_readable} + +{alert.text.format(*args, **kwargs)} Hostname: {self.hostname} Node IP: {self.ip} +ADNL: {self.adnl} Time: {time_} ({int(time.time())}) +Alert name: {alert_name} Severity: {alert.severity} - -Alert text: -
{alert.text.format(*args, **kwargs)}
''' if time.time() - last_sent > alert.timeout: self.send_message(text, alert.severity == "info") # send info alerts without sound @@ -174,6 +178,8 @@ def init(self): from modules.validator import ValidatorModule self.validator_module = ValidatorModule(self.ton, self.local) self.hostname = get_hostname() + adnl = self.ton.GetAdnlAddr() + self.adnl = adnl[:4] + '...' + adnl[-4:] self.ip = self.ton.get_node_ip() self.set_global_vars() init_alerts() From e2ed02d41ea45f9aa6be149ef62a240b664578fb Mon Sep 17 00:00:00 2001 From: yungwine Date: Tue, 11 Feb 2025 22:36:24 +0400 Subject: [PATCH 2/4] add welcome message --- modules/alert_bot.py | 63 ++++++++++++++++++++++++++++++++++++------ mytonctrl/mytonctrl.py | 6 +++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/modules/alert_bot.py b/modules/alert_bot.py index 318d604a..5799f185 100644 --- a/modules/alert_bot.py +++ b/modules/alert_bot.py @@ -11,6 +11,7 @@ @dataclasses.dataclass class Alert: severity: str + description: str text: str timeout: int @@ -29,11 +30,13 @@ def init_alerts(): ALERTS = { "low_wallet_balance": Alert( "low", - "Validator wallet {wallet} balance is low: {balance} TON.", + "Validator's wallet balance is low", + "Validator's wallet {wallet} balance is less than 10 TON: {balance} TON.", 18 * HOUR ), "db_usage_80": Alert( "high", + "Node's db usage is more than 80%", """TON DB usage > 80%. Clean the TON database: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming or (and) set node\'s archive ttl to lower value.""", @@ -41,6 +44,7 @@ def init_alerts(): ), "db_usage_95": Alert( "critical", + "Node's db usage is more than 95%", """TON DB usage > 95%. Disk is almost full, clean the TON database immediately: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming or (and) set node\'s archive ttl to lower value.""", @@ -48,56 +52,67 @@ def init_alerts(): ), "low_efficiency": Alert( "high", - """Validator efficiency is low: {efficiency}%.""", + "Validator had low efficiency in the validation round", + """Validator efficiency is less than 90%: {efficiency}%.""", VALIDATION_PERIOD // 3 ), "out_of_sync": Alert( "critical", - "Node is out of sync on {sync} sec.", + "Validator had low efficiency in the round", + "Node is out of sync on more than 20 sec: {sync} sec.", 300 ), "service_down": Alert( "critical", + "Node is not running (service is down)", "validator.service is down.", 300 ), "adnl_connection_failed": Alert( "high", + "Node is not answering to ADNL connection", "ADNL connection to node failed", 3 * HOUR ), "zero_block_created": Alert( "critical", + "Validator has not created any blocks in the last few hours", "Validator has not created any blocks in the last {hours} hours.", VALIDATION_PERIOD // 3 ), "validator_slashed": Alert( "high", + "Validator has been slashed in the previous validation round", "Validator has been slashed in previous round for {amount} TON", FREEZE_PERIOD ), "stake_not_accepted": Alert( "high", "Validator's stake has not been accepted", + "Validator's stake has not been accepted", ELECTIONS_START_BEFORE ), "stake_accepted": Alert( "info", + "Validator's stake has been accepted (info alert with no sound)", "Validator's stake {stake} TON has been accepted", ELECTIONS_START_BEFORE ), "stake_returned": Alert( "info", + "Validator's stake has been returned (info alert with no sound)", "Validator's stake {stake} TON has been returned on address {address}. The reward amount is {reward} TON.", 60 ), "stake_not_returned": Alert( "high", + "Validator's stake has not been returned", "Validator's stake has not been returned on address {address}.", 60 ), "voting": Alert( "high", + "There is an active network proposal that has many votes but is not voted by the validator", "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.", VALIDATION_PERIOD ), @@ -120,14 +135,14 @@ def __init__(self, ton, local, *args, **kwargs): self.chat_id = None self.last_db_check = 0 - def send_message(self, text: str, silent: bool = False): + def send_message(self, text: str, silent: bool = False, disable_web_page_preview: bool = False): if self.token is None: raise Exception("send_message error: token is not initialized") if self.chat_id is None: raise Exception("send_message error: chat_id is not initialized") request_url = f"https://api.telegram.org/bot{self.token}/sendMessage" - data = {'chat_id': self.chat_id, 'text': text, 'parse_mode': 'HTML', 'disable_notification': silent} - response = requests.post(request_url, data=data, timeout=3) + data = {'chat_id': self.chat_id, 'text': text, 'parse_mode': 'HTML', 'disable_notification': silent, 'link_preview_options': {'is_disabled': disable_web_page_preview}} + response = requests.post(request_url, json=data, timeout=3) if response.status_code != 200: raise Exception(f"send_message error: {response.text}") response = response.json() @@ -144,8 +159,7 @@ def send_alert(self, alert_name: str, *args, **kwargs): raise Exception(f"Alert {alert_name} not found") alert_name_readable = alert_name.replace('_', ' ').title() text = '🆘' if alert.severity != 'info' else '' - text += f''' -️ Node {self.hostname}: {alert_name_readable} + text += f''' Node {self.hostname}: {alert_name_readable} {alert.text.format(*args, **kwargs)} @@ -236,6 +250,39 @@ def test_alert(self, args): self.init() self.send_message('Test alert') + def send_welcome_message(self): + message = """ +👋 Hello, this is alert bot. + +I don't process any commands, I only send notifications. + +Current notifications enabled: + +""" + for alert in ALERTS.values(): + message += f"- {alert.description}\n" + + message += """ +If you want, you can disable some notifications in mytonctrl by the instruction . + +Full bot documentation here. +""" + self.send_message(text=message, disable_web_page_preview=True) + + def on_set_chat_id(self, chat_id): + self.token = self.ton.local.db.get("BotToken") + if self.token is None: + raise Exception("BotToken is not set") + self.chat_id = chat_id + init_alerts() + try: + self.send_welcome_message() + return True + except Exception as e: + self.local.add_log(f"Error while sending welcome message: {e}", "error") + 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 and has admin rights. If it is not - do it and run the command `set ChatId ` again.", "info") + return False + def check_db_usage(self): if time.time() - self.last_db_check < 600: return diff --git a/mytonctrl/mytonctrl.py b/mytonctrl/mytonctrl.py index f0e14c3e..ffb7252b 100755 --- a/mytonctrl/mytonctrl.py +++ b/mytonctrl/mytonctrl.py @@ -879,7 +879,7 @@ def GetSettings(ton, args): print(json.dumps(result, indent=2)) #end define -def SetSettings(ton, args): +def SetSettings(local, ton, args): try: name = args[0] value = args[1] @@ -891,6 +891,10 @@ def SetSettings(ton, args): color_print(f"{{red}} Error: set {name} ... is deprecated and does not work {{endc}}." f"\nInstead, use {{bold}}enable_mode {mode_name}{{endc}}") return + if name == 'ChatId': + from modules.alert_bot import AlertBotModule + if not AlertBotModule(ton, local).on_set_chat_id(value): + return force = False if len(args) > 2: if args[2] == "--force": From 1f18ee255c0015f4b963c033441f9f486b2eec57 Mon Sep 17 00:00:00 2001 From: yungwine Date: Wed, 12 Feb 2025 15:58:19 +0400 Subject: [PATCH 3/4] fix changing chat id for bot --- modules/alert_bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/alert_bot.py b/modules/alert_bot.py index 5799f185..ef7049ec 100644 --- a/modules/alert_bot.py +++ b/modules/alert_bot.py @@ -424,7 +424,8 @@ def check_voting(self): def check_status(self): if not self.ton.using_alert_bot(): return - if not self.inited: + + if not self.inited or self.token != self.ton.local.db.get("BotToken") or self.chat_id != self.ton.local.db.get("ChatId"): self.init() self.local.try_function(self.check_db_usage) From 6fdba7ad2c3235d862707076ad03544d1bc1e3d3 Mon Sep 17 00:00:00 2001 From: yungwine Date: Wed, 12 Feb 2025 17:12:33 +0400 Subject: [PATCH 4/4] alert bot ux improves --- modules/alert_bot.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/alert_bot.py b/modules/alert_bot.py index ef7049ec..04fa34bf 100644 --- a/modules/alert_bot.py +++ b/modules/alert_bot.py @@ -30,7 +30,7 @@ def init_alerts(): ALERTS = { "low_wallet_balance": Alert( "low", - "Validator's wallet balance is low", + "Validator's wallet balance is less than 10 TON", "Validator's wallet {wallet} balance is less than 10 TON: {balance} TON.", 18 * HOUR ), @@ -52,13 +52,13 @@ def init_alerts(): ), "low_efficiency": Alert( "high", - "Validator had low efficiency in the validation round", + "Validator had efficiency less than 90% in the validation round", """Validator efficiency is less than 90%: {efficiency}%.""", VALIDATION_PERIOD // 3 ), "out_of_sync": Alert( "critical", - "Validator had low efficiency in the round", + "Node is out of sync on more than 20 sec", "Node is out of sync on more than 20 sec: {sync} sec.", 300 ), @@ -76,7 +76,7 @@ def init_alerts(): ), "zero_block_created": Alert( "critical", - "Validator has not created any blocks in the last few hours", + f"Validator has not created any blocks in the {int(VALIDATION_PERIOD // 3 // 3600)} hours", "Validator has not created any blocks in the last {hours} hours.", VALIDATION_PERIOD // 3 ), @@ -101,18 +101,18 @@ def init_alerts(): "stake_returned": Alert( "info", "Validator's stake has been returned (info alert with no sound)", - "Validator's stake {stake} TON has been returned on address {address}. The reward amount is {reward} TON.", + "Validator's stake {stake} TON has been returned on address {address}. The reward amount is {reward} TON.", 60 ), "stake_not_returned": Alert( "high", "Validator's stake has not been returned", - "Validator's stake has not been returned on address {address}.", + "Validator's stake has not been returned on address {address}.", 60 ), "voting": Alert( "high", - "There is an active network proposal that has many votes but is not voted by the validator", + "There is an active network proposal that has many votes (more than 50% of required) but is not voted by the validator", "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.", VALIDATION_PERIOD ), @@ -166,6 +166,7 @@ def send_alert(self, alert_name: str, *args, **kwargs): Hostname: {self.hostname} Node IP: {self.ip} ADNL: {self.adnl} +Wallet: {self.wallet} Time: {time_} ({int(time.time())}) Alert name: {alert_name} Severity: {alert.severity} @@ -193,7 +194,8 @@ def init(self): self.validator_module = ValidatorModule(self.ton, self.local) self.hostname = get_hostname() adnl = self.ton.GetAdnlAddr() - self.adnl = adnl[:4] + '...' + adnl[-4:] + self.adnl = adnl + self.wallet = self.ton.GetValidatorWallet().addrB64 self.ip = self.ton.get_node_ip() self.set_global_vars() init_alerts() @@ -251,8 +253,8 @@ def test_alert(self, args): self.send_message('Test alert') def send_welcome_message(self): - message = """ -👋 Hello, this is alert bot. + message = f""" +This is alert bot. You have connected validator with ADNL {self.ton.GetAdnlAddr()}. I don't process any commands, I only send notifications. @@ -263,9 +265,9 @@ def send_welcome_message(self): message += f"- {alert.description}\n" message += """ -If you want, you can disable some notifications in mytonctrl by the instruction . +If you want, you can disable some notifications in mytonctrl by the instruction. -Full bot documentation here. +Full bot documentation here. """ self.send_message(text=message, disable_web_page_preview=True) @@ -280,7 +282,7 @@ def on_set_chat_id(self, chat_id): return True except Exception as e: self.local.add_log(f"Error while sending welcome message: {e}", "error") - 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 and has admin rights. If it is not - do it and run the command `set ChatId ` again.", "info") + 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 ` again.", "info") return False def check_db_usage(self):