diff --git a/README.md b/README.md
index c577d8b4..7fa8fa0a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
     
-
+
# MyTonCtrl
@@ -58,12 +58,6 @@ Mytonctrl's documentation can be found at https://docs.ton.org/participate/run-n
- [x] Show offers
- [x] Vote for the proposal
- [x] Automatic voting for previously voted proposals
-- [x] Domain management
- - [x] Rent a new domain
- - [x] Show rented domains
- - [x] Show domain status
- - [x] Delete domain
- - [ ] Automatic domain renewal
- [x] Controlling the validator
- [x] Participate in the election of a validator
- [x] Return bet + reward
diff --git a/modules/__init__.py b/modules/__init__.py
index 8b92c260..cd1b0076 100644
--- a/modules/__init__.py
+++ b/modules/__init__.py
@@ -8,6 +8,8 @@
from modules.validator import ValidatorModule
from modules.controller import ControllerModule
from modules.liteserver import LiteserverModule
+from modules.alert_bot import AlertBotModule
+from modules.prometheus import PrometheusModule
MODES = {
@@ -15,7 +17,9 @@
'nominator-pool': NominatorPoolModule,
'single-nominator': SingleNominatorModule,
'liquid-staking': ControllerModule,
- 'liteserver': LiteserverModule
+ 'liteserver': LiteserverModule,
+ 'alert-bot': AlertBotModule,
+ 'prometheus': PrometheusModule
}
@@ -54,6 +58,11 @@ class Setting:
'useDefaultCustomOverlays': Setting(None, True, 'Participate in default custom overlays node eligible to'),
'defaultCustomOverlaysUrl': Setting(None, 'https://ton-blockchain.github.io/fallback_custom_overlays.json', 'Default custom overlays config url'),
'debug': Setting(None, False, 'Debug mtc console mode. Prints Traceback on errors'),
+ 'subscribe_tg_channel': Setting('validator', False, 'Disables warning about subscribing to the `TON STATUS` channel'),
+ 'auto_backup': Setting('validator', None, 'Make validator backup every election'),
+ 'auto_backup_path': Setting('validator', '/tmp/mytoncore/auto_backups/', 'Path to store auto-backups'),
+ 'prometheus_url': Setting('prometheus', None, 'Prometheus pushgateway url'),
+ 'onlyNode': Setting(None, None, 'MyTonCtrl will work only for collecting validator telemetry (if `sendTelemetry` is True), without participating in Elections and etc.')
}
diff --git a/modules/alert_bot.py b/modules/alert_bot.py
new file mode 100644
index 00000000..bcb833e3
--- /dev/null
+++ b/modules/alert_bot.py
@@ -0,0 +1,478 @@
+import dataclasses
+import time
+import requests
+
+from modules.module import MtcModule
+from mypylib.mypylib import get_timestamp, print_table, color_print
+from mytoncore import get_hostname
+from mytonctrl.utils import timestamp2utcdatetime
+
+
+@dataclasses.dataclass
+class Alert:
+ severity: str
+ description: str
+ text: str
+ timeout: int
+
+
+HOUR = 3600
+VALIDATION_PERIOD = 65536
+FREEZE_PERIOD = 32768
+ELECTIONS_START_BEFORE = 8192
+
+
+ALERTS = {}
+
+
+def init_alerts():
+ global ALERTS
+ ALERTS = {
+ "low_wallet_balance": Alert(
+ "low",
+ "Validator's wallet balance is less than 10 TON",
+ "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.""",
+ 24 * HOUR
+ ),
+ "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.""",
+ 6 * HOUR
+ ),
+ "low_efficiency": Alert(
+ "high",
+ "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",
+ "Node is out of sync on more than 20 sec",
+ "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",
+ f"Validator has not created any blocks in the {int(VALIDATION_PERIOD // 6 // 3600)} hours",
+ "Validator has not created any blocks in the last {hours} hours.",
+ VALIDATION_PERIOD // 6
+ ),
+ "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 (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
+ ),
+ "initial_sync_completed": Alert(
+ "info",
+ "Initial sync has been completed (info alert with no sound)",
+ "Node initial sync has been completed",
+ 0
+ )
+ }
+
+
+class AlertBotModule(MtcModule):
+
+ description = 'Telegram bot alerts'
+ default_value = False
+
+ def __init__(self, ton, local, *args, **kwargs):
+ super().__init__(ton, local, *args, **kwargs)
+ self.validator_module = None
+ self.inited = False
+ self.hostname = None
+ self.ip = None
+ self.adnl = None
+ self.token = None
+ self.chat_id = None
+ self.last_db_check = 0
+ self.initial_sync = None
+
+ 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, '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()
+ if not response['ok']:
+ raise Exception(f"send_message error: {response}")
+
+ def send_alert(self, alert_name: str, *args, **kwargs):
+ if not self.alert_is_enabled(alert_name):
+ return
+ last_sent = self.get_alert_sent(alert_name)
+ time_ = timestamp2utcdatetime(int(time.time()))
+ alert = ALERTS.get(alert_name)
+ if alert is None:
+ raise Exception(f"Alert {alert_name} not found")
+ alert_name_readable = alert_name.replace('_', ' ').title()
+
+ for key, value in kwargs.items():
+ if isinstance(value, (int, float)):
+ kwargs[key] = f'{value:,}'.replace(',', ' ') # make space separator for thousands
+
+ 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}'''
+
+ if self.ton.using_validator():
+ text += f"\nWallet: {self.wallet}"
+
+ text += f'''
+Time: {time_} ({int(time.time())})
+Alert name: {alert_name}
+Severity: {alert.severity}
+'''
+ if time.time() - last_sent > alert.timeout:
+ self.send_message(text, alert.severity == "info") # send info alerts without sound
+ self.set_alert_sent(alert_name)
+
+ def set_global_vars(self):
+ # set global vars for correct alerts timeouts for current network
+ config15 = self.ton.GetConfig15()
+ global VALIDATION_PERIOD, FREEZE_PERIOD, ELECTIONS_START_BEFORE
+ VALIDATION_PERIOD = config15["validatorsElectedFor"]
+ FREEZE_PERIOD = config15["stakeHeldFor"]
+ ELECTIONS_START_BEFORE = config15["electionsStartBefore"]
+
+ def init(self):
+ if not self.ton.get_mode_value('alert-bot'):
+ return
+ self.token = self.ton.local.db.get("BotToken")
+ self.chat_id = self.ton.local.db.get("ChatId")
+ if self.token is None or self.chat_id is None:
+ raise Exception("BotToken or ChatId is not set")
+ from modules.validator import ValidatorModule
+ self.validator_module = ValidatorModule(self.ton, self.local)
+ self.hostname = get_hostname()
+ adnl = self.ton.GetAdnlAddr()
+ self.adnl = adnl
+ self.wallet = self.ton.GetValidatorWallet().addrB64
+ self.ip = self.ton.get_node_ip()
+ self.set_global_vars()
+ self.initial_sync = self.ton.in_initial_sync()
+ init_alerts()
+ self.inited = True
+
+ def get_alert_from_db(self, alert_name: str):
+ if 'alerts' not in self.ton.local.db:
+ self.ton.local.db['alerts'] = {}
+ if alert_name not in self.ton.local.db['alerts']:
+ self.ton.local.db['alerts'][alert_name] = {'sent': 0, 'enabled': True}
+ return self.ton.local.db['alerts'][alert_name]
+
+ def set_alert_sent(self, alert_name: str):
+ alert = self.get_alert_from_db(alert_name)
+ alert['sent'] = int(time.time())
+
+ def get_alert_sent(self, alert_name: str):
+ alert = self.get_alert_from_db(alert_name)
+ return alert.get('sent', 0)
+
+ def alert_is_enabled(self, alert_name: str):
+ alert = self.get_alert_from_db(alert_name)
+ return alert.get('enabled', True) # default is True
+
+ def set_alert_enabled(self, alert_name: str, enabled: bool):
+ alert = self.get_alert_from_db(alert_name)
+ alert['enabled'] = enabled
+ self.ton.local.save()
+
+ def enable_alert(self, args):
+ if len(args) != 1:
+ raise Exception("Usage: enable_alert ")
+ alert_name = args[0]
+ self.set_alert_enabled(alert_name, True)
+ color_print("enable_alert - {green}OK{endc}")
+
+ def disable_alert(self, args):
+ if len(args) != 1:
+ raise Exception("Usage: disable_alert ")
+ alert_name = args[0]
+ self.set_alert_enabled(alert_name, False)
+ color_print("disable_alert - {green}OK{endc}")
+
+ def print_alerts(self, args):
+ init_alerts()
+ table = [['Name', 'Enabled', 'Last sent']]
+ for alert_name in ALERTS:
+ alert = self.get_alert_from_db(alert_name)
+ table.append([alert_name, alert['enabled'], alert['sent']])
+ print_table(table)
+
+ def test_alert(self, args):
+ if not self.inited:
+ self.init()
+ self.send_message('Test alert')
+
+ def setup_alert_bot(self, args):
+ if len(args) != 2:
+ raise Exception("Usage: setup_alert_bot ")
+ self.token = args[0]
+ self.chat_id = args[1]
+ init_alerts()
+ try:
+ self.send_welcome_message()
+ self.ton.local.db['BotToken'] = args[0]
+ self.ton.local.db['ChatId'] = args[1]
+ color_print("setup_alert_bot - {green}OK{endc}")
+ 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. If it is not - do it and run the command `setup_alert_bot ` again.", "info")
+ color_print("setup_alert_bot - {red}Error{endc}")
+
+ def send_welcome_message(self):
+ 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.
+
+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 check_db_usage(self):
+ if time.time() - self.last_db_check < 600:
+ return
+ self.last_db_check = time.time()
+ usage = self.ton.GetDbUsage()
+ if usage > 95:
+ self.send_alert("db_usage_95")
+ elif usage > 80:
+ self.send_alert("db_usage_80")
+
+ def check_validator_wallet_balance(self):
+ if not self.ton.using_validator():
+ return
+ validator_status = self.ton.GetValidatorStatus()
+ if not validator_status.is_working or validator_status.out_of_sync >= 20:
+ return
+ validator_wallet = self.ton.GetValidatorWallet()
+ validator_account = self.ton.GetAccount(validator_wallet.addrB64)
+ if validator_account.balance < 10:
+ self.send_alert("low_wallet_balance", wallet=validator_wallet.addrB64, balance=validator_account.balance)
+
+ def check_efficiency(self):
+ if not self.ton.using_validator():
+ return
+ validator = self.validator_module.find_myself(self.ton.GetValidatorsList())
+ if validator is None or validator.efficiency is None:
+ return
+ config34 = self.ton.GetConfig34()
+ if (time.time() - config34.startWorkTime) / (config34.endWorkTime - config34.startWorkTime) < 0.8:
+ return # less than 80% of round passed
+ if validator.is_masterchain is False:
+ if validator.efficiency != 0:
+ return
+ if validator.efficiency < 90:
+ self.send_alert("low_efficiency", efficiency=validator.efficiency)
+
+ def check_validator_working(self):
+ validator_status = self.ton.GetValidatorStatus()
+ if not self.initial_sync and not validator_status.is_working:
+ self.send_alert("service_down")
+
+ def check_sync(self):
+ validator_status = self.ton.GetValidatorStatus()
+ if not self.initial_sync and validator_status.is_working and validator_status.out_of_sync >= 20:
+ self.send_alert("out_of_sync", sync=validator_status.out_of_sync)
+
+ def check_zero_blocks_created(self):
+ if not self.ton.using_validator():
+ return
+ ts = get_timestamp()
+ period = VALIDATION_PERIOD // 6 # 3h for mainnet, 40m for testnet
+ start, end = ts - period, ts - 60
+ config34 = self.ton.GetConfig34()
+ if start < config34.startWorkTime: # round started recently
+ return
+ validators = self.ton.GetValidatorsList(start=start, end=end)
+ validator = self.validator_module.find_myself(validators)
+ if validator is None or validator.blocks_created > 0:
+ return
+ self.send_alert("zero_block_created", hours=round(period // 3600, 1))
+
+ def check_slashed(self):
+ if not self.ton.using_validator():
+ return
+ c = self.validator_module.get_my_complaint()
+ if c is not None:
+ self.send_alert("validator_slashed", amount=int(c['suggestedFine']))
+
+ def check_adnl_connection_failed(self):
+ from modules.utilities import UtilitiesModule
+ utils_module = UtilitiesModule(self.ton, self.local)
+ ok, error = utils_module.check_adnl_connection()
+ if not ok:
+ self.local.add_log(error, "warning")
+ self.send_alert("adnl_connection_failed")
+
+ def get_myself_from_election(self, config: dict):
+ if not config["validators"]:
+ return
+ adnl = self.ton.GetAdnlAddr()
+ save_elections = self.ton.GetSaveElections()
+ elections = save_elections.get(str(config["startWorkTime"]))
+ if elections is None:
+ return
+ if adnl not in elections: # didn't participate in elections
+ return
+ validator = self.validator_module.find_myself(config["validators"])
+ if validator is None:
+ return False
+ validator['stake'] = elections[adnl].get('stake')
+ validator['walletAddr'] = elections[adnl].get('walletAddr')
+ return validator
+
+ def check_stake_sent(self):
+ if not self.ton.using_validator():
+ return
+ config = self.ton.GetConfig36()
+ res = self.get_myself_from_election(config)
+ if res is None:
+ return
+ if res is False:
+ self.send_alert("stake_not_accepted")
+ return
+ self.send_alert("stake_accepted", stake=round(res.get('stake')))
+
+ def check_stake_returned(self):
+ if not self.ton.using_validator():
+ return
+ config = self.ton.GetConfig32()
+ 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
+ return
+ res = self.get_myself_from_election(config)
+ if not res:
+ return
+ trs = self.ton.GetAccountHistory(self.ton.GetAccount(res["walletAddr"]), limit=10)
+
+ for tr in trs:
+ if tr.time >= config['endWorkTime'] + FREEZE_PERIOD and tr.srcAddr == '3333333333333333333333333333333333333333333333333333333333333333' and tr.body.startswith('F96F7324'): # Elector Recover Stake Response
+ self.send_alert("stake_returned", stake=round(tr.value), address=res["walletAddr"], reward=round(tr.value - res.get('stake', 0), 2))
+ return
+ self.send_alert("stake_not_returned", address=res["walletAddr"])
+
+ def check_voting(self):
+ if not self.ton.using_validator():
+ return
+ validator_index = self.ton.GetValidatorIndex()
+ if validator_index == -1:
+ return
+ config = self.ton.GetConfig34()
+ if time.time() - config['startWorkTime'] < 600: # less than 10 minutes passed since round start
+ return
+ need_to_vote = []
+ offers = self.ton.GetOffers()
+ for offer in offers:
+ if not offer['isPassed'] and offer['approvedPercent'] >= 50 and validator_index not in offer['votedValidators']:
+ need_to_vote.append(offer['hash'])
+ if need_to_vote:
+ self.send_alert("voting", hashes=' '.join(need_to_vote))
+
+ def check_initial_sync(self):
+ if not self.initial_sync:
+ return
+ if not self.ton.in_initial_sync():
+ self.initial_sync = False
+ self.send_alert("initial_sync_completed")
+
+ def check_status(self):
+ if not self.ton.using_alert_bot():
+ return
+
+ 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)
+ self.local.try_function(self.check_validator_wallet_balance)
+ self.local.try_function(self.check_efficiency) # todo: alert if validator is going to be slashed
+ self.local.try_function(self.check_validator_working)
+ self.local.try_function(self.check_zero_blocks_created)
+ self.local.try_function(self.check_sync)
+ self.local.try_function(self.check_slashed)
+ self.local.try_function(self.check_adnl_connection_failed)
+ self.local.try_function(self.check_stake_sent)
+ self.local.try_function(self.check_stake_returned)
+ self.local.try_function(self.check_voting)
+ self.local.try_function(self.check_initial_sync)
+
+ def add_console_commands(self, console):
+ console.AddItem("enable_alert", self.enable_alert, self.local.translate("enable_alert_cmd"))
+ console.AddItem("disable_alert", self.disable_alert, self.local.translate("disable_alert_cmd"))
+ console.AddItem("list_alerts", self.print_alerts, self.local.translate("list_alerts_cmd"))
+ console.AddItem("test_alert", self.test_alert, self.local.translate("test_alert_cmd"))
+ console.AddItem("setup_alert_bot", self.setup_alert_bot, self.local.translate("setup_alert_bot_cmd"))
diff --git a/modules/backups.py b/modules/backups.py
new file mode 100644
index 00000000..8ea0b2fa
--- /dev/null
+++ b/modules/backups.py
@@ -0,0 +1,91 @@
+import os
+import shutil
+import subprocess
+import time
+
+import pkg_resources
+
+from modules.module import MtcModule
+from mypylib.mypylib import color_print, ip2int, run_as_root, parse
+from mytoninstaller.config import get_own_ip
+
+
+class BackupModule(MtcModule):
+
+ def create_keyring(self, dir_name):
+ keyring_dir = dir_name + '/keyring'
+ self.ton.validatorConsole.Run(f'exportallprivatekeys {keyring_dir}')
+
+ def create_tmp_ton_dir(self):
+ result = self.ton.validatorConsole.Run("getconfig")
+ text = parse(result, "---------", "--------")
+ dir_name = self.ton.tempDir + f'/ton_backup_{int(time.time() * 1000)}'
+ dir_name_db = dir_name + '/db'
+ os.makedirs(dir_name_db)
+ with open(dir_name_db + '/config.json', 'w') as f:
+ f.write(text)
+ self.create_keyring(dir_name_db)
+ return dir_name
+
+ @staticmethod
+ def run_create_backup(args):
+ backup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/create_backup.sh')
+ return subprocess.run(["bash", backup_script_path] + args, timeout=5)
+
+ def create_backup(self, args):
+ if len(args) > 1:
+ color_print("{red}Bad args. Usage:{endc} create_backup [filename]")
+ return
+ tmp_dir = self.create_tmp_ton_dir()
+ command_args = ["-m", self.ton.local.buffer.my_work_dir, "-t", tmp_dir]
+ if len(args) == 1:
+ command_args += ["-d", args[0]]
+ process = self.run_create_backup(command_args)
+
+ if process.returncode == 0:
+ color_print("create_backup - {green}OK{endc}")
+ else:
+ color_print("create_backup - {red}Error{endc}")
+ shutil.rmtree(tmp_dir)
+ return process.returncode
+ # end define
+
+ @staticmethod
+ def run_restore_backup(args):
+ restore_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/restore_backup.sh')
+ return run_as_root(["bash", restore_script_path] + args)
+
+ def restore_backup(self, args):
+ if len(args) == 0 or len(args) > 3:
+ color_print("{red}Bad args. Usage:{endc} restore_backup [-y] [--skip-create-backup]")
+ return
+ if '-y' not in args:
+ res = input(
+ f'This action will overwrite existing configuration with contents of backup archive, please make sure that donor node is not in operation prior to this action. Proceed [y/n]')
+ if res.lower() != 'y':
+ print('aborted.')
+ return
+ else:
+ args.pop(args.index('-y'))
+ if '--skip-create-backup' in args:
+ args.pop(args.index('--skip-create-backup'))
+ else:
+ print('Before proceeding, mtc will create a backup of current configuration.')
+ try:
+ self.create_backup([])
+ except:
+ color_print("{red}Could not create backup{endc}")
+
+ ip = str(ip2int(get_own_ip()))
+ command_args = ["-m", self.ton.local.buffer.my_work_dir, "-n", args[0], "-i", ip]
+
+ if self.run_restore_backup(command_args) == 0:
+ color_print("restore_backup - {green}OK{endc}")
+ self.local.exit()
+ else:
+ color_print("restore_backup - {red}Error{endc}")
+ # end define
+
+ def add_console_commands(self, console):
+ console.AddItem("create_backup", self.create_backup, self.local.translate("create_backup_cmd"))
+ console.AddItem("restore_backup", self.restore_backup, self.local.translate("restore_backup_cmd"))
diff --git a/modules/nominator_pool.py b/modules/nominator_pool.py
index 14f4fbdd..d00708b0 100644
--- a/modules/nominator_pool.py
+++ b/modules/nominator_pool.py
@@ -87,7 +87,45 @@ def update_validator_set(self, args):
self.ton.PoolUpdateValidatorSet(pool_addr, wallet)
color_print("UpdateValidatorSet - {green}OK{endc}")
+ def do_deposit_to_pool(self, pool_addr, amount):
+ wallet = self.ton.GetValidatorWallet()
+ bocPath = self.ton.local.buffer.my_temp_dir + wallet.name + "validator-deposit-query.boc"
+ fiftScript = self.ton.contractsDir + "nominator-pool/func/validator-deposit.fif"
+ args = [fiftScript, bocPath]
+ result = self.ton.fift.Run(args)
+ resultFilePath = self.ton.SignBocWithWallet(wallet, bocPath, pool_addr, amount)
+ self.ton.SendFile(resultFilePath, wallet)
+
+ def deposit_to_pool(self, args):
+ try:
+ poll_addr = args[0]
+ amount = float(args[1])
+ except:
+ color_print("{red}Bad args. Usage:{endc} deposit_to_pool ")
+ return
+ self.do_deposit_to_pool(poll_addr, amount)
+ color_print("DepositToPool - {green}OK{endc}")
+
+ def do_withdraw_from_pool(self, pool_addr, amount):
+ pool_data = self.ton.GetPoolData(pool_addr)
+ if pool_data["state"] == 0:
+ self.ton.WithdrawFromPoolProcess(pool_addr, amount)
+ else:
+ self.ton.PendWithdrawFromPool(pool_addr, amount)
+
+ def withdraw_from_pool(self, args):
+ try:
+ pool_addr = args[0]
+ amount = float(args[1])
+ except:
+ color_print("{red}Bad args. Usage:{endc} withdraw_from_pool ")
+ return
+ self.do_withdraw_from_pool(pool_addr, amount)
+ color_print("WithdrawFromPool - {green}OK{endc}")
+
def add_console_commands(self, console):
console.AddItem("new_pool", self.new_pool, self.local.translate("new_pool_cmd"))
console.AddItem("activate_pool", self.activate_pool, self.local.translate("activate_pool_cmd"))
console.AddItem("update_validator_set", self.update_validator_set, self.local.translate("update_validator_set_cmd"))
+ console.AddItem("withdraw_from_pool", self.withdraw_from_pool, self.local.translate("withdraw_from_pool_cmd"))
+ console.AddItem("deposit_to_pool", self.deposit_to_pool, self.local.translate("deposit_to_pool_cmd"))
diff --git a/modules/pool.py b/modules/pool.py
index ba37b16c..16b481c5 100644
--- a/modules/pool.py
+++ b/modules/pool.py
@@ -58,45 +58,7 @@ def check_download_pool_contract_scripts(self):
if not os.path.isdir(contract_path):
self.ton.DownloadContract("https://github.com/ton-blockchain/nominator-pool")
- def do_deposit_to_pool(self, pool_addr, amount):
- wallet = self.ton.GetValidatorWallet()
- bocPath = self.ton.local.buffer.my_temp_dir + wallet.name + "validator-deposit-query.boc"
- fiftScript = self.ton.contractsDir + "nominator-pool/func/validator-deposit.fif"
- args = [fiftScript, bocPath]
- result = self.ton.fift.Run(args)
- resultFilePath = self.ton.SignBocWithWallet(wallet, bocPath, pool_addr, amount)
- self.ton.SendFile(resultFilePath, wallet)
-
- def deposit_to_pool(self, args):
- try:
- poll_addr = args[0]
- amount = float(args[1])
- except:
- color_print("{red}Bad args. Usage:{endc} deposit_to_pool ")
- return
- self.do_deposit_to_pool(poll_addr, amount)
- color_print("DepositToPool - {green}OK{endc}")
-
- def do_withdraw_from_pool(self, pool_addr, amount):
- pool_data = self.ton.GetPoolData(pool_addr)
- if pool_data["state"] == 0:
- self.ton.WithdrawFromPoolProcess(pool_addr, amount)
- else:
- self.ton.PendWithdrawFromPool(pool_addr, amount)
-
- def withdraw_from_pool(self, args):
- try:
- pool_addr = args[0]
- amount = float(args[1])
- except:
- color_print("{red}Bad args. Usage:{endc} withdraw_from_pool ")
- return
- self.do_withdraw_from_pool(pool_addr, amount)
- color_print("WithdrawFromPool - {green}OK{endc}")
-
def add_console_commands(self, console):
console.AddItem("pools_list", self.print_pools_list, self.local.translate("pools_list_cmd"))
console.AddItem("delete_pool", self.delete_pool, self.local.translate("delete_pool_cmd"))
console.AddItem("import_pool", self.import_pool, self.local.translate("import_pool_cmd"))
- console.AddItem("deposit_to_pool", self.deposit_to_pool, self.local.translate("deposit_to_pool_cmd"))
- console.AddItem("withdraw_from_pool", self.withdraw_from_pool, self.local.translate("withdraw_from_pool_cmd"))
diff --git a/modules/prometheus.py b/modules/prometheus.py
new file mode 100644
index 00000000..e88e1737
--- /dev/null
+++ b/modules/prometheus.py
@@ -0,0 +1,118 @@
+from modules.module import MtcModule
+import dataclasses
+import requests
+
+
+@dataclasses.dataclass
+class Metric:
+ name: str
+ description: str
+ type: str
+
+ def to_format(self, value):
+ return f"""
+# HELP {self.name} {self.description}
+# TYPE {self.name} {self.type}
+{self.name} {value}
+"""
+
+
+METRICS = {
+ 'master_out_of_sync': Metric('validator_masterchain_out_of_sync_seconds', 'Time difference between current time and timestamp of the last known block', 'gauge'),
+ 'shard_out_of_sync': Metric('validator_shardchain_out_of_sync_blocks', 'Number of blocks node\'s shardclient is behind the last known block', 'gauge'),
+ 'out_of_ser': Metric('validator_out_of_serialization', 'Number of blocks last state serialization was ago', 'gauge'),
+ 'vc_up': Metric('validator_console_up', 'Is `validator-console` up', 'gauge'),
+ 'validator_id': Metric('validator_index', 'Validator index', 'gauge'),
+ 'stake': Metric('validator_stake', 'Validator stake', 'gauge'),
+ 'celldb_gc_block': Metric('validator_celldb_gc_block', 'Celldb GC block latency', 'gauge'),
+ 'celldb_gc_state': Metric('validator_celldb_gc_state', 'Celldb GC queue size', 'gauge'),
+ 'collated_master_ok': Metric('validator_blocks_collated_master_ok', 'Number of masterchain blocks successfully collated', 'gauge'),
+ 'collated_master_err': Metric('validator_blocks_collated_master_err', 'Number of masterchain blocks failed to collate', 'gauge'),
+ 'collated_shard_ok': Metric('validator_blocks_collated_shard_ok', 'Number of shardchain blocks successfully collated', 'gauge'),
+ 'collated_shard_err': Metric('validator_blocks_collated_shard_err', 'Number of shardchain blocks failed to collate', 'gauge'),
+ 'validated_master_ok': Metric('validator_blocks_validated_master_ok', 'Number of masterchain blocks successfully validated', 'gauge'),
+ 'validated_master_err': Metric('validator_blocks_validated_master_err', 'Number of masterchain blocks failed to validate', 'gauge'),
+ 'validated_shard_ok': Metric('validator_blocks_validated_shard_ok', 'Number of shardchain blocks successfully validated', 'gauge'),
+ 'validated_shard_err': Metric('validator_blocks_validated_shard_err', 'Number of shardchain blocks failed to validate', 'gauge'),
+ 'validator_groups_master': Metric('validator_active_groups_master', 'Number of masterchain validation groups validator participates in', 'gauge'),
+ 'validator_groups_shard': Metric('validator_active_groups_shard', 'Number of shardchain validation groups validator participates in', 'gauge'),
+ 'ls_queries_ok': Metric('validator_ls_queries_ok', 'Number of Liteserver successful queries', 'gauge'),
+ 'ls_queries_err': Metric('validator_ls_queries_err', 'Number of Liteserver failed queries', 'gauge'),
+}
+
+
+class PrometheusModule(MtcModule):
+
+ description = 'Prometheus format data exporter'
+ default_value = False
+
+ def __init__(self, ton, local, *args, **kwargs):
+ super().__init__(ton, local, *args, **kwargs)
+
+ def get_validator_status_metrics(self, result: list):
+ status = self.ton.GetValidatorStatus()
+ is_working = status.is_working or (status.unixtime is not None)
+ if status.masterchain_out_of_sync is not None:
+ result.append(METRICS['master_out_of_sync'].to_format(status.masterchain_out_of_sync))
+ if status.shardchain_out_of_sync is not None:
+ result.append(METRICS['shard_out_of_sync'].to_format(status.shardchain_out_of_sync))
+ if status.stateserializerenabled and status.masterchain_out_of_ser is not None and status.stateserializermasterchainseqno != 0:
+ result.append(METRICS['out_of_ser'].to_format(status.masterchain_out_of_ser))
+ if status.masterchainblock is not None and status.gcmasterchainblock is not None:
+ result.append(METRICS['celldb_gc_block'].to_format(status.masterchainblock - status.gcmasterchainblock))
+ if status.gcmasterchainblock is not None and status.last_deleted_mc_state is not None:
+ if status.last_deleted_mc_state != 0:
+ result.append(METRICS['celldb_gc_state'].to_format(status.gcmasterchainblock - status.last_deleted_mc_state))
+ else:
+ result.append(METRICS['celldb_gc_state'].to_format(-1))
+ if status.validator_groups_master is not None:
+ result.append(METRICS['validator_groups_master'].to_format(status.validator_groups_master))
+ result.append(METRICS['validator_groups_shard'].to_format(status.validator_groups_shard))
+ result.append(METRICS['vc_up'].to_format(int(is_working)))
+
+ def get_validator_validation_metrics(self, result: list):
+ index = self.ton.GetValidatorIndex()
+ result.append(METRICS['validator_id'].to_format(index))
+ config = self.ton.GetConfig34()
+ save_elections = self.ton.GetSaveElections()
+ elections = save_elections.get(str(config["startWorkTime"]))
+ if elections is not None:
+ adnl = self.ton.GetAdnlAddr()
+ stake = elections.get(adnl, {}).get('stake')
+ if stake:
+ result.append(METRICS['stake'].to_format(round(stake, 2)))
+
+ def get_node_stats_metrics(self, result: list):
+ stats = self.ton.get_node_statistics()
+ if stats and 'ls_queries' in stats:
+ if stats['ls_queries']['time'] < 50:
+ self.local.add_log(f'Liteserver queries time is too low: {stats}')
+ return
+ result.append(METRICS['ls_queries_ok'].to_format(stats['ls_queries']['ok']))
+ result.append(METRICS['ls_queries_err'].to_format(stats['ls_queries']['error']))
+ if stats and 'collated' in stats:
+ result.append(METRICS['collated_master_ok'].to_format(stats['collated']['master']['ok']))
+ result.append(METRICS['collated_master_err'].to_format(stats['collated']['master']['error']))
+ result.append(METRICS['collated_shard_ok'].to_format(stats['collated']['shard']['ok']))
+ result.append(METRICS['collated_shard_err'].to_format(stats['collated']['shard']['error']))
+ if stats and 'validated' in stats:
+ result.append(METRICS['validated_master_ok'].to_format(stats['validated']['master']['ok']))
+ result.append(METRICS['validated_master_err'].to_format(stats['validated']['master']['error']))
+ result.append(METRICS['validated_shard_ok'].to_format(stats['validated']['shard']['ok']))
+ result.append(METRICS['validated_shard_err'].to_format(stats['validated']['shard']['error']))
+
+ def push_metrics(self):
+ if not self.ton.using_prometheus():
+ return
+
+ url = self.ton.local.db.get('prometheus_url')
+ if url is None:
+ raise Exception('Prometheus url is not set')
+ metrics = []
+ self.local.try_function(self.get_validator_status_metrics, args=[metrics])
+ self.local.try_function(self.get_validator_validation_metrics, args=[metrics])
+ self.local.try_function(self.get_node_stats_metrics, args=[metrics])
+ requests.post(url, data='\n'.join(metrics).encode())
+
+ def add_console_commands(self, console):
+ ...
diff --git a/modules/utilities.py b/modules/utilities.py
new file mode 100644
index 00000000..640f160d
--- /dev/null
+++ b/modules/utilities.py
@@ -0,0 +1,399 @@
+import json
+import random
+import subprocess
+import time
+
+import requests
+
+from mypylib.mypylib import color_print, print_table, color_text, timeago, bcolors
+from modules.module import MtcModule
+
+
+class UtilitiesModule(MtcModule):
+
+ description = ''
+ default_value = False
+
+ def view_account_status(self, args):
+ try:
+ addrB64 = args[0]
+ except:
+ color_print("{red}Bad args. Usage:{endc} vas ")
+ return
+ addrB64 = self.ton.get_destination_addr(addrB64)
+ account = self.ton.GetAccount(addrB64)
+ version = self.ton.GetVersionFromCodeHash(account.codeHash)
+ statusTable = list()
+ statusTable += [["Address", "Status", "Balance", "Version"]]
+ statusTable += [[addrB64, account.status, account.balance, version]]
+ codeHashTable = list()
+ codeHashTable += [["Code hash"]]
+ codeHashTable += [[account.codeHash]]
+ historyTable = self.get_history_table(addrB64, 10)
+ print_table(statusTable)
+ print()
+ print_table(codeHashTable)
+ print()
+ print_table(historyTable)
+ # end define
+
+ def get_history_table(self, addr, limit):
+ addr = self.ton.get_destination_addr(addr)
+ account = self.ton.GetAccount(addr)
+ history = self.ton.GetAccountHistory(account, limit)
+ table = list()
+ typeText = color_text("{red}{bold}{endc}")
+ table += [["Time", typeText, "Coins", "From/To"]]
+ for message in history:
+ if message.srcAddr is None:
+ continue
+ srcAddrFull = f"{message.srcWorkchain}:{message.srcAddr}"
+ destAddFull = f"{message.destWorkchain}:{message.destAddr}"
+ if srcAddrFull == account.addrFull:
+ type = color_text("{red}{bold}>>>{endc}")
+ fromto = destAddFull
+ else:
+ type = color_text("{blue}{bold}<<<{endc}")
+ fromto = srcAddrFull
+ fromto = self.ton.AddrFull2AddrB64(fromto)
+ # datetime = timestamp2datetime(message.time, "%Y.%m.%d %H:%M:%S")
+ datetime = timeago(message.time)
+ table += [[datetime, type, message.value, fromto]]
+ return table
+ # end define
+
+ def view_account_history(self, args):
+ try:
+ addr = args[0]
+ limit = int(args[1])
+ except:
+ color_print("{red}Bad args. Usage:{endc} vah ")
+ return
+ table = self.get_history_table(addr, limit)
+ print_table(table)
+ # end define
+
+ def create_new_bookmark(self, args):
+ try:
+ name = args[0]
+ addr = args[1]
+ except:
+ color_print("{red}Bad args. Usage:{endc} nb ")
+ return
+ if not self.ton.IsAddr(addr):
+ raise Exception("Incorrect address")
+ # end if
+
+ bookmark = dict()
+ bookmark["name"] = name
+ bookmark["addr"] = addr
+ self.ton.AddBookmark(bookmark)
+ color_print("CreatNewBookmark - {green}OK{endc}")
+ # end define
+
+ def print_bookmarks_list(self, args):
+ data = self.ton.GetBookmarks()
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ table = list()
+ table += [["Name", "Address", "Balance / Exp. date"]]
+ for item in data:
+ name = item.get("name")
+ addr = item.get("addr")
+ bookmark_data = item.get("data")
+ table += [[name, addr, bookmark_data]]
+ print_table(table)
+ # end define
+
+ def delete_bookmark(self, args):
+ try:
+ name = args[0]
+ except:
+ color_print("{red}Bad args. Usage:{endc} db ")
+ return
+ self.ton.DeleteBookmark(name)
+ color_print("DeleteBookmark - {green}OK{endc}")
+ # end define
+
+ @staticmethod
+ def reduct(item):
+ item = str(item)
+ if item is None:
+ result = None
+ else:
+ end = len(item)
+ result = item[0:6] + "..." + item[end - 6:end]
+ return result
+ # end define
+
+ def print_offers_list(self, args):
+ data = self.ton.GetOffers()
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ if "--json" in args:
+ text = json.dumps(data, indent=2)
+ print(text)
+ else:
+ table = list()
+ table += [["Hash", "Config", "Votes", "W/L", "Approved", "Is passed"]]
+ for item in data:
+ hash = item.get("hash")
+ votedValidators = len(item.get("votedValidators"))
+ wins = item.get("wins")
+ losses = item.get("losses")
+ wl = "{0}/{1}".format(wins, losses)
+ approvedPercent = item.get("approvedPercent")
+ approvedPercent_text = "{0}%".format(approvedPercent)
+ isPassed = item.get("isPassed")
+ if "hash" not in args:
+ hash = self.reduct(hash)
+ if isPassed == True:
+ isPassed = bcolors.green_text("true")
+ if isPassed == False:
+ isPassed = bcolors.red_text("false")
+ table += [[hash, item.config.id, votedValidators, wl, approvedPercent_text, isPassed]]
+ print_table(table)
+ # end define
+
+ def get_offer_diff(self, offer_hash):
+ self.local.add_log("start GetOfferDiff function", "debug")
+ offer = self.ton.GetOffer(offer_hash)
+ config_id = offer["config"]["id"]
+ config_value = offer["config"]["value"]
+
+ if '{' in config_value or '}' in config_value:
+ start = config_value.find('{') + 1
+ end = config_value.find('}')
+ config_value = config_value[start:end]
+ # end if
+
+ args = [self.ton.liteClient.appPath, "--global-config", self.ton.liteClient.configPath, "--verbosity", "0"]
+ process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ time.sleep(1)
+
+ fullConfigAddr = self.ton.GetFullConfigAddr()
+ cmd = "runmethodfull {fullConfigAddr} list_proposals".format(fullConfigAddr=fullConfigAddr)
+ process.stdin.write(cmd.encode() + b'\n')
+ process.stdin.flush()
+ time.sleep(1)
+
+ cmd = "dumpcellas ConfigParam{configId} {configValue}".format(configId=config_id, configValue=config_value)
+ process.stdin.write(cmd.encode() + b'\n')
+ process.stdin.flush()
+ time.sleep(1)
+
+ process.terminate()
+ text = process.stdout.read().decode()
+
+ lines = text.split('\n')
+ b = len(lines)
+ for i in range(b):
+ line = lines[i]
+ if "dumping cells as values of TLB type" in line:
+ a = i + 2
+ break
+ # end for
+
+ for i in range(a, b):
+ line = lines[i]
+ if '(' in line:
+ start = i
+ break
+ # end for
+
+ for i in range(a, b):
+ line = lines[i]
+ if '>' in line:
+ end = i
+ break
+ # end for
+
+ buff = lines[start:end]
+ text = "".join(buff)
+ newData = self.ton.Tlb2Json(text)
+ newFileName = self.ton.tempDir + "data1diff"
+ file = open(newFileName, 'wt')
+ newText = json.dumps(newData, indent=2)
+ file.write(newText)
+ file.close()
+
+ oldData = self.ton.GetConfig(config_id)
+ oldFileName = self.ton.tempDir + "data2diff"
+ file = open(oldFileName, 'wt')
+ oldText = json.dumps(oldData, indent=2)
+ file.write(oldText)
+ file.close()
+
+ print(oldText)
+ args = ["diff", "--color", oldFileName, newFileName]
+ subprocess.run(args)
+ # end define
+
+ def offer_diff(self, args):
+ try:
+ offer_hash = args[0]
+ offer_hash = offer_hash
+ except:
+ color_print("{red}Bad args. Usage:{endc} od ")
+ return
+ self.get_offer_diff(offer_hash)
+ # end define
+
+ def print_complaints_list(self, args):
+ past = "past" in args
+ data = self.ton.GetComplaints(past=past)
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ if "--json" in args:
+ text = json.dumps(data, indent=2)
+ print(text)
+ else:
+ table = list()
+ table += [["Election id", "ADNL", "Fine (part)", "Votes", "Approved", "Is passed"]]
+ for key, item in data.items():
+ electionId = item.get("electionId")
+ adnl = item.get("adnl")
+ suggestedFine = item.get("suggestedFine")
+ suggestedFinePart = item.get("suggestedFinePart")
+ Fine_text = "{0} ({1})".format(suggestedFine, suggestedFinePart)
+ votedValidators = len(item.get("votedValidators"))
+ approvedPercent = item.get("approvedPercent")
+ approvedPercent_text = "{0}%".format(approvedPercent)
+ isPassed = item.get("isPassed")
+ if "adnl" not in args:
+ adnl = self.reduct(adnl)
+ if isPassed:
+ isPassed = bcolors.green_text("true")
+ if not isPassed:
+ isPassed = bcolors.red_text("false")
+ table += [[electionId, adnl, Fine_text, votedValidators, approvedPercent_text, isPassed]]
+ print_table(table)
+ # end define
+
+ def print_election_entries_list(self, args):
+ past = "past" in args
+ data = self.ton.GetElectionEntries(past=past)
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ if "--json" in args:
+ text = json.dumps(data, indent=2)
+ print(text)
+ else:
+ table = list()
+ table += [["ADNL", "Pubkey", "Wallet", "Stake", "Max-factor"]]
+ for key, item in data.items():
+ adnl = item.get("adnlAddr")
+ pubkey = item.get("pubkey")
+ walletAddr = item.get("walletAddr")
+ stake = item.get("stake")
+ maxFactor = item.get("maxFactor")
+ if "adnl" not in args:
+ adnl = self.reduct(adnl)
+ if "pubkey" not in args:
+ pubkey = self.reduct(pubkey)
+ if "wallet" not in args:
+ walletAddr = self.reduct(walletAddr)
+ table += [[adnl, pubkey, walletAddr, stake, maxFactor]]
+ print_table(table)
+ # end define
+
+ def print_validator_list(self, args):
+ past = "past" in args
+ fast = "fast" in args
+ data = self.ton.GetValidatorsList(past=past, fast=fast)
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ if "--json" in args:
+ text = json.dumps(data, indent=2)
+ print(text)
+ else:
+ table = list()
+ table += [["id", "ADNL", "Pubkey", "Wallet", "Stake", "Efficiency", "Online"]]
+ for i, item in enumerate(data):
+ adnl = item.get("adnlAddr")
+ pubkey = item.get("pubkey")
+ walletAddr = item.get("walletAddr")
+ efficiency = item.get("efficiency")
+ online = item.get("online")
+ stake = item.get("stake")
+ if "adnl" not in args:
+ adnl = self.reduct(adnl)
+ if "pubkey" not in args:
+ pubkey = self.reduct(pubkey)
+ if "wallet" not in args:
+ walletAddr = self.reduct(walletAddr)
+ if "offline" in args and online != False:
+ continue
+ if online:
+ online = bcolors.green_text("true")
+ if not online:
+ online = bcolors.red_text("false")
+ table += [[str(i), adnl, pubkey, walletAddr, stake, efficiency, online]]
+ print_table(table)
+ # end define
+
+ def check_adnl_connection(self):
+ telemetry = self.ton.local.db.get("sendTelemetry", False)
+ check_adnl = self.ton.local.db.get("checkAdnl", telemetry)
+ if not check_adnl:
+ return True, ''
+ self.local.add_log('Checking ADNL connection to local node', 'info')
+ hosts = ['45.129.96.53', '5.154.181.153', '2.56.126.137', '91.194.11.68', '45.12.134.214', '138.124.184.27',
+ '103.106.3.171']
+ hosts = random.sample(hosts, k=3)
+ data = self.ton.get_local_adnl_data()
+ error = ''
+ ok = True
+ for host in hosts:
+ url = f'http://{host}/adnl_check'
+ try:
+ response = requests.post(url, json=data, timeout=5).json()
+ except Exception as e:
+ ok = False
+ error = f'Failed to check ADNL connection to local node: {type(e)}: {e}'
+ continue
+ result = response.get("ok")
+ if result:
+ ok = True
+ break
+ if not result:
+ ok = False
+ error = f'Failed to check ADNL connection to local node: {response.get("message")}'
+ return ok, error
+
+ def get_pool_data(self, args):
+ try:
+ pool_name = args[0]
+ except:
+ color_print("{red}Bad args. Usage:{endc} get_pool_data ")
+ return
+ if self.ton.IsAddr(pool_name):
+ pool_addr = pool_name
+ else:
+ pool = self.ton.GetLocalPool(pool_name)
+ pool_addr = pool.addrB64
+ pool_data = self.ton.GetPoolData(pool_addr)
+ print(json.dumps(pool_data, indent=4))
+ # end define
+
+ def add_console_commands(self, console):
+ console.AddItem("vas", self.view_account_status, self.local.translate("vas_cmd"))
+ console.AddItem("vah", self.view_account_history, self.local.translate("vah_cmd"))
+
+ console.AddItem("nb", self.create_new_bookmark, self.local.translate("nb_cmd"))
+ console.AddItem("bl", self.print_bookmarks_list, self.local.translate("bl_cmd"))
+ console.AddItem("db", self.delete_bookmark, self.local.translate("db_cmd"))
+
+ console.AddItem("ol", self.print_offers_list, self.local.translate("ol_cmd"))
+ console.AddItem("od", self.offer_diff, self.local.translate("od_cmd"))
+
+ console.AddItem("el", self.print_election_entries_list, self.local.translate("el_cmd"))
+ console.AddItem("vl", self.print_validator_list, self.local.translate("vl_cmd"))
+ console.AddItem("cl", self.print_complaints_list, self.local.translate("cl_cmd"))
+
+ console.AddItem("get_pool_data", self.get_pool_data, self.local.translate("get_pool_data_cmd"))
diff --git a/modules/validator.py b/modules/validator.py
index 7a6b9ad3..a0fdfa9c 100644
--- a/modules/validator.py
+++ b/modules/validator.py
@@ -54,15 +54,18 @@ def check_efficiency(self, args):
end_time = timestamp2utcdatetime(config32.endWorkTime)
color_print(f"Previous round time: {{yellow}}from {start_time} to {end_time}{{endc}}")
if validator:
- if validator.is_masterchain == False:
- print("Validator index is greater than 100 in the previous round - no efficiency data.")
- elif validator.get('efficiency') is None:
+ if validator.get('efficiency') is None:
print('Failed to get efficiency for the previous round')
+ elif validator.is_masterchain is False and validator.get('efficiency') != 0:
+ print(f"Validator index is greater than {config32['mainValidators']} in the previous round - no efficiency data.")
else:
efficiency = 100 if validator.efficiency > 100 else validator.efficiency
color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%")
- created = validator.blocks_created
- expected = validator.blocks_expected
+ created = validator.master_blocks_created
+ expected = validator.master_blocks_expected
+ if created is None: # there is no updated prev round info in cache
+ created = validator.blocks_created
+ expected = validator.blocks_expected
color_print(f"Previous round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}")
else:
print("Couldn't find this validator in the previous round")
@@ -71,8 +74,8 @@ def check_efficiency(self, args):
end_time = timestamp2utcdatetime(int(get_timestamp()))
color_print(f"Current round time: {{green}}from {start_time} to {end_time}{{endc}}")
if validator:
- if validator.is_masterchain == False:
- print("Validator index is greater than 100 in the current round - no efficiency data.")
+ if validator.is_masterchain is False and validator.efficiency != 0:
+ print(f"Validator index is greater than {config34['mainValidators']} in the current round - no efficiency data.")
elif (time.time() - config34.startWorkTime) / (config34.endWorkTime - config34.startWorkTime) < 0.8:
print("The validation round has started recently, there is not enough data yet. "
"The efficiency evaluation will become more accurate towards the end of the round.")
@@ -81,13 +84,24 @@ def check_efficiency(self, args):
else:
efficiency = 100 if validator.efficiency > 100 else validator.efficiency
color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%")
- created = validator.blocks_created
- expected = validator.blocks_expected
+ created = validator.master_blocks_created
+ expected = validator.master_blocks_expected
color_print(f"Current round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}")
else:
print("Couldn't find this validator in the current round")
# end define
+ def get_my_complaint(self):
+ config32 = self.ton.GetConfig32()
+ save_complaints = self.ton.GetSaveComplaints()
+ complaints = save_complaints.get(str(config32['startWorkTime']))
+ if not complaints:
+ return
+ for c in complaints.values():
+ if c["adnl"] == self.ton.GetAdnlAddr() and c["isPassed"]:
+ return c
+ # end define
+
def add_console_commands(self, console):
console.AddItem("vo", self.vote_offer, self.local.translate("vo_cmd"))
console.AddItem("ve", self.vote_election_entry, self.local.translate("ve_cmd"))
diff --git a/modules/wallet.py b/modules/wallet.py
new file mode 100644
index 00000000..e4bc1c86
--- /dev/null
+++ b/modules/wallet.py
@@ -0,0 +1,205 @@
+import base64
+import os
+
+from modules.module import MtcModule
+from mypylib.mypylib import color_print, print_table
+
+
+class WalletModule(MtcModule):
+
+ description = ''
+ default_value = False
+
+ def create_new_wallet(self, args):
+ version = "v1"
+ try:
+ if len(args) == 0:
+ walletName = self.ton.GenerateWalletName()
+ workchain = 0
+ else:
+ workchain = int(args[0])
+ walletName = args[1]
+ if len(args) > 2:
+ version = args[2]
+ if len(args) == 4:
+ subwallet = int(args[3])
+ else:
+ subwallet = 698983191 + workchain # 0x29A9A317 + workchain
+ except:
+ color_print("{red}Bad args. Usage:{endc} nw [ ]")
+ return
+ wallet = self.ton.CreateWallet(walletName, workchain, version, subwallet=subwallet)
+ table = list()
+ table += [["Name", "Workchain", "Address"]]
+ table += [[wallet.name, wallet.workchain, wallet.addrB64_init]]
+ print_table(table)
+ # end define
+
+ def _wallets_check(self):
+ self.local.add_log("start WalletsCheck function", "debug")
+ wallets = self.get_wallets()
+ for wallet in wallets:
+ if os.path.isfile(wallet.bocFilePath):
+ account = self.ton.GetAccount(wallet.addrB64)
+ if account.balance > 0:
+ self.ton.SendFile(wallet.bocFilePath, wallet)
+ # end define
+
+ def activate_wallet(self, args):
+ try:
+ walletName = args[0]
+ except Exception as err:
+ walletName = "all"
+ if walletName == "all":
+ self._wallets_check()
+ else:
+ wallet = self.ton.GetLocalWallet(walletName)
+ self.ton.ActivateWallet(wallet)
+ color_print("ActivateWallet - {green}OK{endc}")
+ # end define
+
+ def get_wallets(self):
+ self.local.add_log("start GetWallets function", "debug")
+ wallets = list()
+ wallets_name_list = self.ton.GetWalletsNameList()
+ for walletName in wallets_name_list:
+ wallet = self.ton.GetLocalWallet(walletName)
+ wallets.append(wallet)
+ return wallets
+ # end define
+
+ def print_wallets_list(self, args):
+ table = list()
+ table += [["Name", "Status", "Balance", "Ver", "Wch", "Address"]]
+ data = self.get_wallets()
+ if data is None or len(data) == 0:
+ print("No data")
+ return
+ for wallet in data:
+ account = self.ton.GetAccount(wallet.addrB64)
+ if account.status != "active":
+ wallet.addrB64 = wallet.addrB64_init
+ table += [[wallet.name, account.status, account.balance, wallet.version, wallet.workchain, wallet.addrB64]]
+ print_table(table)
+ # end define
+
+ def do_import_wallet(self, addr_b64, key):
+ addr_bytes = self.ton.addr_b64_to_bytes(addr_b64)
+ pk_bytes = base64.b64decode(key)
+ wallet_name = self.ton.GenerateWalletName()
+ wallet_path = self.ton.walletsDir + wallet_name
+ with open(wallet_path + ".addr", 'wb') as file:
+ file.write(addr_bytes)
+ with open(wallet_path + ".pk", 'wb') as file:
+ file.write(pk_bytes)
+ return wallet_name
+ # end define
+
+ def import_wallet(self, args):
+ try:
+ addr = args[0]
+ key = args[1]
+ except:
+ color_print("{red}Bad args. Usage:{endc} iw ")
+ return
+ name = self.do_import_wallet(addr, key)
+ print("Wallet name:", name)
+ # end define
+
+ def set_wallet_version(self, args):
+ try:
+ addr = args[0]
+ version = args[1]
+ except:
+ color_print("{red}Bad args. Usage:{endc} swv ")
+ return
+ self.ton.SetWalletVersion(addr, version)
+ color_print("SetWalletVersion - {green}OK{endc}")
+ # end define
+
+ def do_export_wallet(self, wallet_name):
+ wallet = self.ton.GetLocalWallet(wallet_name)
+ with open(wallet.privFilePath, 'rb') as file:
+ data = file.read()
+ key = base64.b64encode(data).decode("utf-8")
+ return wallet.addrB64, key
+ # end define
+
+ def export_wallet(self, args):
+ try:
+ name = args[0]
+ except:
+ color_print("{red}Bad args. Usage:{endc} ew ")
+ return
+ addr, key = self.do_export_wallet(name)
+ print("Wallet name:", name)
+ print("Address:", addr)
+ print("Secret key:", key)
+ # end define
+
+ def delete_wallet(self, args):
+ try:
+ wallet_name = args[0]
+ except:
+ color_print("{red}Bad args. Usage:{endc} dw ")
+ return
+ if input("Are you sure you want to delete this wallet (yes/no): ") != "yes":
+ print("Cancel wallet deletion")
+ return
+ wallet = self.ton.GetLocalWallet(wallet_name)
+ wallet.Delete()
+ color_print("DeleteWallet - {green}OK{endc}")
+ # end define
+
+ def move_coins(self, args):
+ try:
+ wallet_name = args[0]
+ destination = args[1]
+ amount = args[2]
+ flags = args[3:]
+ except:
+ color_print("{red}Bad args. Usage:{endc} mg ")
+ return
+ wallet = self.ton.GetLocalWallet(wallet_name)
+ destination = self.ton.get_destination_addr(destination)
+ self.ton.MoveCoins(wallet, destination, amount, flags=flags)
+ color_print("MoveCoins - {green}OK{endc}")
+ # end define
+
+ def do_move_coins_through_proxy(self, wallet, dest, coins):
+ self.local.add_log("start MoveCoinsThroughProxy function", "debug")
+ wallet1 = self.ton.CreateWallet("proxy_wallet1", 0)
+ wallet2 = self.ton.CreateWallet("proxy_wallet2", 0)
+ self.ton.MoveCoins(wallet, wallet1.addrB64_init, coins)
+ self.ton.ActivateWallet(wallet1)
+ self.ton.MoveCoins(wallet1, wallet2.addrB64_init, "alld")
+ self.ton.ActivateWallet(wallet2)
+ self.ton.MoveCoins(wallet2, dest, "alld", flags=["-n"])
+ wallet1.Delete()
+ wallet2.Delete()
+ # end define
+
+ def move_coins_through_proxy(self, args):
+ try:
+ wallet_name = args[0]
+ destination = args[1]
+ amount = args[2]
+ except:
+ color_print("{red}Bad args. Usage:{endc} mgtp ")
+ return
+ wallet = self.ton.GetLocalWallet(wallet_name)
+ destination = self.ton.get_destination_addr(destination)
+ self.do_move_coins_through_proxy(wallet, destination, amount)
+ color_print("MoveCoinsThroughProxy - {green}OK{endc}")
+ # end define
+
+ def add_console_commands(self, console):
+ console.AddItem("nw", self.create_new_wallet, self.local.translate("nw_cmd"))
+ console.AddItem("aw", self.activate_wallet, self.local.translate("aw_cmd"))
+ console.AddItem("wl", self.print_wallets_list, self.local.translate("wl_cmd"))
+ console.AddItem("iw", self.import_wallet, self.local.translate("iw_cmd"))
+ console.AddItem("swv", self.set_wallet_version, self.local.translate("swv_cmd"))
+ console.AddItem("ew", self.export_wallet, self.local.translate("ex_cmd"))
+ console.AddItem("dw", self.delete_wallet, self.local.translate("dw_cmd"))
+ console.AddItem("mg", self.move_coins, self.local.translate("mg_cmd"))
+ console.AddItem("mgtp", self.move_coins_through_proxy, self.local.translate("mgtp_cmd"))
diff --git a/mypyconsole b/mypyconsole
index a8f9f569..9b730946 160000
--- a/mypyconsole
+++ b/mypyconsole
@@ -1 +1 @@
-Subproject commit a8f9f56972192247f37ca8f43a5e723e29994c60
+Subproject commit 9b730946fc7998a6b294fe9adb7a77ce1b2b6eef
diff --git a/mypylib b/mypylib
index 049205ee..3508d3aa 160000
--- a/mypylib
+++ b/mypylib
@@ -1 +1 @@
-Subproject commit 049205eee1650d7d847497d78acba68c5a33cc37
+Subproject commit 3508d3aa07ec3ea1a47690a3e7477b2b8e677c4a
diff --git a/mytoncore/functions.py b/mytoncore/functions.py
index 5ae4602a..c39f2c42 100755
--- a/mytoncore/functions.py
+++ b/mytoncore/functions.py
@@ -55,8 +55,8 @@ def Event(local, event_name):
ValidatorDownEvent(local)
elif event_name == "enable_ton_storage_provider":
enable_ton_storage_provider_event(local)
- elif event_name == "enable_liteserver_mode":
- enable_liteserver_mode(local)
+ elif event_name.startswith("enable_mode"):
+ enable_mode(local, event_name)
local.exit()
# end define
@@ -93,10 +93,12 @@ def enable_ton_storage_provider_event(local):
#end define
-def enable_liteserver_mode(local):
+def enable_mode(local, event_name):
ton = MyTonCore(local)
- ton.disable_mode('validator')
- ton.enable_mode('liteserver')
+ mode = event_name.split("_")[-1]
+ if mode == "liteserver":
+ ton.disable_mode('validator')
+ ton.enable_mode(mode)
#end define
@@ -284,6 +286,64 @@ def CalculateNetworkStatistics(zerodata, data):
# end define
+def save_node_statistics(local, ton):
+ status = ton.GetValidatorStatus(no_cache=True)
+ if status.unixtime is None:
+ return
+ data = {'timestamp': status.unixtime}
+
+ def get_ok_error(value: str):
+ ok, error = value.split()
+ return int(ok.split(':')[1]), int(error.split(':')[1])
+
+ if 'total.collated_blocks.master' in status:
+ master_ok, master_error = get_ok_error(status['total.collated_blocks.master'])
+ shard_ok, shard_error = get_ok_error(status['total.collated_blocks.shard'])
+ data['collated_blocks'] = {
+ 'master': {'ok': master_ok, 'error': master_error},
+ 'shard': {'ok': shard_ok, 'error': shard_error},
+ }
+ if 'total.validated_blocks.master' in status:
+ master_ok, master_error = get_ok_error(status['total.validated_blocks.master'])
+ shard_ok, shard_error = get_ok_error(status['total.validated_blocks.shard'])
+ data['validated_blocks'] = {
+ 'master': {'ok': master_ok, 'error': master_error},
+ 'shard': {'ok': shard_ok, 'error': shard_error},
+ }
+ if 'total.ext_msg_check' in status:
+ ok, error = get_ok_error(status['total.ext_msg_check'])
+ data['ext_msg_check'] = {'ok': ok, 'error': error}
+ if 'total.ls_queries_ok' in status and 'total.ls_queries_error' in status:
+ data['ls_queries'] = {}
+ for k in status['total.ls_queries_ok'].split():
+ if k.startswith('TOTAL'):
+ data['ls_queries']['ok'] = int(k.split(':')[1])
+ for k in status['total.ls_queries_error'].split():
+ if k.startswith('TOTAL'):
+ data['ls_queries']['error'] = int(k.split(':')[1])
+ statistics = local.db.get("statistics", dict())
+
+ if time.time() - int(status.start_time) <= 60: # was node restart <60 sec ago, resetting node statistics
+ statistics['node'] = []
+
+ # statistics['node'] = [stats_from_election_id, stats_from_prev_min, stats_now]
+
+ election_id = ton.GetConfig34()['startWorkTime']
+ if 'node' not in statistics or len(statistics['node']) == 0:
+ statistics['node'] = [None, data]
+ elif len(statistics['node']) < 3:
+ statistics['node'].append(data)
+ if len(statistics['node']) == 3:
+ if statistics['node'][0] is None:
+ if 0 < data['timestamp'] - election_id < 90:
+ statistics['node'][0] = data
+ elif statistics['node'][0]['timestamp'] < election_id:
+ statistics['node'][0] = data
+ statistics['node'] = statistics.get('node', []) + [data]
+ statistics['node'].pop(1)
+ local.db["statistics"] = statistics
+
+
def ReadTransData(local, scanner):
transData = local.buffer.transData
SetToTimeData(transData, scanner.transNum)
@@ -384,12 +444,6 @@ def Offers(local, ton):
ton.VoteOffer(offer_hash)
# end define
-
-def Domains(local, ton):
- pass
-# end define
-
-
def Telemetry(local, ton):
sendTelemetry = local.db.get("sendTelemetry")
if sendTelemetry is not True:
@@ -548,6 +602,17 @@ def ScanLiteServers(local, ton):
# end define
+def check_initial_sync(local, ton):
+ if not ton.in_initial_sync():
+ return
+ validator_status = ton.GetValidatorStatus()
+ if validator_status.initial_sync:
+ return
+ if validator_status.out_of_sync < 20:
+ ton.set_initial_sync_off()
+ return
+
+
def General(local):
local.add_log("start General function", "debug")
ton = MyTonCore(local)
@@ -555,8 +620,14 @@ def General(local):
# scanner.Run()
# Start threads
- local.start_cycle(Elections, sec=600, args=(local, ton, ))
local.start_cycle(Statistics, sec=10, args=(local, ))
+ local.start_cycle(Telemetry, sec=60, args=(local, ton, ))
+ local.start_cycle(OverlayTelemetry, sec=7200, args=(local, ton, ))
+ if local.db.get("onlyNode"): # mytoncore service works only for telemetry
+ thr_sleep()
+ return
+
+ local.start_cycle(Elections, sec=600, args=(local, ton, ))
local.start_cycle(Offers, sec=600, args=(local, ton, ))
local.start_cycle(save_past_events, sec=300, args=(local, ton, ))
@@ -566,14 +637,22 @@ def General(local):
local.start_cycle(Complaints, sec=t, args=(local, ton, ))
local.start_cycle(Slashing, sec=t, args=(local, ton, ))
- local.start_cycle(Domains, sec=600, args=(local, ton, ))
- local.start_cycle(Telemetry, sec=60, args=(local, ton, ))
- local.start_cycle(OverlayTelemetry, sec=7200, args=(local, ton, ))
local.start_cycle(ScanLiteServers, sec=60, args=(local, ton,))
+ local.start_cycle(save_node_statistics, sec=60, args=(local, ton, ))
+
from modules.custom_overlays import CustomOverlayModule
local.start_cycle(CustomOverlayModule(ton, local).custom_overlays, sec=60, args=())
+ from modules.alert_bot import AlertBotModule
+ local.start_cycle(AlertBotModule(ton, local).check_status, sec=60, args=())
+
+ from modules.prometheus import PrometheusModule
+ local.start_cycle(PrometheusModule(ton, local).push_metrics, sec=30, args=())
+
+ if ton.in_initial_sync():
+ local.start_cycle(check_initial_sync, sec=120, args=(local, ton))
+
thr_sleep()
# end define
diff --git a/mytoncore/models.py b/mytoncore/models.py
index 474fa276..db803d9b 100644
--- a/mytoncore/models.py
+++ b/mytoncore/models.py
@@ -41,15 +41,6 @@ def __init__(self, workchain, addr):
#end class
-class Domain(dict):
- def __init__(self):
- self["name"] = None
- self["adnlAddr"] = None
- self["walletName"] = None
- #end define
-#end class
-
-
class Block():
def __init__(self, str=None):
self.workchain = None
diff --git a/mytoncore/mytoncore.py b/mytoncore/mytoncore.py
index 5fffd8e8..31190663 100644
--- a/mytoncore/mytoncore.py
+++ b/mytoncore/mytoncore.py
@@ -19,7 +19,6 @@
from mytoncore.models import (
Wallet,
Account,
- Domain,
Block,
Trans,
Message,
@@ -31,7 +30,7 @@
get_timestamp,
timestamp2datetime,
dec2hex,
- Dict
+ Dict, int2ip
)
@@ -116,13 +115,26 @@ def CheckConfigFile(self, fift, liteClient):
self.local.add_log("Restoring the configuration file", "info")
args = ["cp", backup_path, mconfig_path]
subprocess.run(args)
+ self.dbFile = mconfig_path
self.Refresh()
- elif os.path.isfile(backup_path) == False:
- self.local.add_log("Create backup config file", "info")
- args = ["cp", mconfig_path, backup_path]
- subprocess.run(args)
+ elif not os.path.isfile(backup_path) or time.time() - os.path.getmtime(backup_path) > 3600:
+ self.local.try_function(self.create_self_db_backup)
#end define
+ def create_self_db_backup(self):
+ self.local.add_log("Create backup config file", "info")
+ mconfig_path = self.local.buffer.db_path
+ backup_path = mconfig_path + ".backup"
+ backup_tmp_path = backup_path + '.tmp'
+ subprocess.run(["cp", mconfig_path, backup_tmp_path])
+ try:
+ with open(backup_tmp_path, "r") as file:
+ json.load(file)
+ os.rename(backup_tmp_path, backup_path) # atomic opetation
+ except:
+ self.local.add_log("Could not update backup, backup_tmp file is broken", "warning")
+ os.remove(backup_tmp_path)
+
def GetVarFromWorkerOutput(self, text, search):
if ':' not in search:
search += ':'
@@ -390,48 +402,6 @@ def GetComment(self, body):
return result
#end define
- def GetDomainAddr(self, domainName):
- cmd = "dnsresolve {domainName} -1".format(domainName=domainName)
- result = self.liteClient.Run(cmd)
- if "not found" in result:
- raise Exception("GetDomainAddr error: domain \"{domainName}\" not found".format(domainName=domainName))
- resolver = parse(result, "next resolver", '\n')
- buff = resolver.replace(' ', '')
- buffList = buff.split('=')
- fullHexAddr = buffList[0]
- addr = buffList[1]
- return addr
- #end define
-
- def GetDomainEndTime(self, domainName):
- self.local.add_log("start GetDomainEndTime function", "debug")
- buff = domainName.split('.')
- subdomain = buff.pop(0)
- dnsDomain = ".".join(buff)
- dnsAddr = self.GetDomainAddr(dnsDomain)
-
- cmd = "runmethodfull {addr} getexpiration \"{subdomain}\"".format(addr=dnsAddr, subdomain=subdomain)
- result = self.liteClient.Run(cmd)
- result = parse(result, "result:", '\n')
- result = parse(result, "[", "]")
- result = result.replace(' ', '')
- result = int(result)
- return result
- #end define
-
- def GetDomainAdnlAddr(self, domainName):
- self.local.add_log("start GetDomainAdnlAddr function", "debug")
- cmd = "dnsresolve {domainName} 1".format(domainName=domainName)
- result = self.liteClient.Run(cmd)
- lines = result.split('\n')
- for line in lines:
- if "adnl address" in line:
- adnlAddr = parse(line, "=", "\n")
- adnlAddr = adnlAddr.replace(' ', '')
- adnlAddr = adnlAddr
- return adnlAddr
- #end define
-
def GetLocalWallet(self, walletName, version=None, subwallet=None):
self.local.add_log("start GetLocalWallet function", "debug")
if walletName is None:
@@ -806,16 +776,24 @@ def GetShardsNumber(self, block=None):
return shardsNum
#end define
- def GetValidatorStatus(self):
+ def parse_stats_from_vc(self, output: str, result: dict):
+ for line in output.split('\n'):
+ if len(line.split('\t\t\t')) == 2:
+ name, value = line.split('\t\t\t') # https://github.com/ton-blockchain/ton/blob/master/validator-engine-console/validator-engine-console-query.cpp#L648
+ if name not in result:
+ result[name] = value
+
+ def GetValidatorStatus(self, no_cache=False):
# Get buffer
bname = "validator_status"
buff = self.GetFunctionBuffer(bname)
- if buff:
+ if buff and not no_cache:
return buff
#end if
self.local.add_log("start GetValidatorStatus function", "debug")
status = Dict()
+ result = None
try:
# Parse
status.is_working = True
@@ -838,10 +816,20 @@ def GetValidatorStatus(self):
status.masterchain_out_of_ser = status.masterchainblock - status.stateserializermasterchainseqno
status.out_of_sync = status.masterchain_out_of_sync if status.masterchain_out_of_sync > status.shardchain_out_of_sync else status.shardchain_out_of_sync
status.out_of_ser = status.masterchain_out_of_ser
+ status.last_deleted_mc_state = int(parse(result, "last_deleted_mc_state", '\n'))
+ status.stateserializerenabled = parse(result, "stateserializerenabled", '\n') == "true"
+ self.local.try_function(self.parse_stats_from_vc, args=[result, status])
+ if 'active_validator_groups' in status:
+ groups = status.active_validator_groups.split() # master:1 shard:2
+ status.validator_groups_master = int(groups[0].split(':')[1])
+ status.validator_groups_shard = int(groups[1].split(':')[1])
except Exception as ex:
self.local.add_log(f"GetValidatorStatus warning: {ex}", "warning")
status.is_working = False
+ if result is not None:
+ self.local.try_function(self.parse_stats_from_vc, args=[result, status])
#end try
+ status.initial_sync = status.get("process.initial_sync")
# old vars
status.outOfSync = status.out_of_sync
@@ -1269,8 +1257,13 @@ def WaitTransaction(self, wallet, timeout=30):
steps = timeout // timesleep
for i in range(steps):
time.sleep(timesleep)
- seqno = self.GetSeqno(wallet)
+ try:
+ seqno = self.GetSeqno(wallet)
+ except:
+ self.local.add_log("WaitTransaction error: Can't get seqno", "warning")
+ continue
if seqno != wallet.oldseqno:
+ self.local.add_log("WaitTransaction success", "info")
return
raise Exception("WaitTransaction error: time out")
#end define
@@ -1492,21 +1485,47 @@ def ElectionEntry(self, args=None):
self.local.add_log("ElectionEntry completed. Start work time: " + str(startWorkTime))
self.clear_tmp()
+ self.make_backup(startWorkTime)
#end define
- def clear_tmp(self):
+ def clear_dir(self, dir_name):
start = time.time()
count = 0
week_ago = 60 * 60 * 24 * 7
- dir = self.tempDir
- for f in os.listdir(dir):
- ts = os.path.getmtime(os.path.join(dir, f))
+ for f in os.listdir(dir_name):
+ ts = os.path.getmtime(os.path.join(dir_name, f))
if ts < time.time() - week_ago:
count += 1
- os.remove(os.path.join(dir, f))
+ if os.path.isfile(os.path.join(dir_name, f)):
+ os.remove(os.path.join(dir_name, f))
+ self.local.add_log(f"Removed {count} old files from {dir_name} directory for {int(time.time() - start)} seconds", "info")
+
+ def clear_tmp(self):
+ self.clear_dir(self.tempDir)
- self.local.add_log(f"Removed {count} old files from tmp dir for {int(time.time() - start)} seconds", "info")
+ def make_backup(self, election_id: str):
+ if not self.local.db.get("auto_backup"):
+ return
+ from modules.backups import BackupModule
+ module = BackupModule(self, self.local)
+ args = []
+ name = f"/mytonctrl_backup_elid{election_id}.zip"
+ backups_dir = self.tempDir + "/auto_backups"
+ if self.local.db.get("auto_backup_path"):
+ backups_dir = self.local.db.get("auto_backup_path")
+ os.makedirs(backups_dir, exist_ok=True)
+ args.append(backups_dir + name)
+ self.clear_dir(backups_dir)
+ exit_code = module.create_backup(args)
+ if exit_code != 0:
+ self.local.add_log(f"Backup failed with exit code {exit_code}", "error")
+ # try one more time
+ exit_code = module.create_backup(args)
+ if exit_code != 0:
+ self.local.add_log(f"Backup failed with exit code {exit_code}", "error")
+ if exit_code == 0:
+ self.local.add_log(f"Backup created successfully", "info")
def GetValidatorKeyByTime(self, startWorkTime, endWorkTime):
self.local.add_log("start GetValidatorKeyByTime function", "debug")
@@ -1662,7 +1681,7 @@ def CreateWallet(self, name, workchain=0, version="v1", **kwargs):
if os.path.isfile(wallet_path + ".pk") and "v3" not in version:
self.local.add_log("CreateWallet error: Wallet already exists: " + name, "warning")
else:
- fift_args = self.get_new_wallet_fift_args(version, workchain=workchain,
+ fift_args = self.get_new_wallet_fift_args(version, workchain=workchain,
wallet_path=wallet_path, subwallet=subwallet)
result = self.fift.Run(fift_args)
if "Creating new" not in result:
@@ -1705,18 +1724,6 @@ def ActivateWallet(self, wallet):
self.SendFile(wallet.bocFilePath, wallet, remove=False)
#end define
- def ImportWallet(self, addr_b64, key):
- addr_bytes = self.addr_b64_to_bytes(addr_b64)
- pk_bytes = base64.b64decode(key)
- wallet_name = self.GenerateWalletName()
- wallet_path = self.walletsDir + wallet_name
- with open(wallet_path + ".addr", 'wb') as file:
- file.write(addr_bytes)
- with open(wallet_path + ".pk", 'wb') as file:
- file.write(pk_bytes)
- return wallet_name
- #end define
-
def import_wallet_with_version(self, key, version, **kwargs):
wallet_name = kwargs.get("wallet_name")
workchain = kwargs.get("workchain", 0)
@@ -1731,7 +1738,7 @@ def import_wallet_with_version(self, key, version, **kwargs):
wallet_path = self.walletsDir + wallet_name
with open(wallet_path + ".pk", 'wb') as file:
file.write(pk_bytes)
- fift_args = self.get_new_wallet_fift_args(version, workchain=workchain,
+ fift_args = self.get_new_wallet_fift_args(version, workchain=workchain,
wallet_path=wallet_path, subwallet=subwallet)
result = self.fift.Run(fift_args)
if "Creating new" not in result:
@@ -1768,14 +1775,6 @@ def addr_b64_to_bytes(self, addr_b64):
return result
#end define
- def ExportWallet(self, walletName):
- wallet = self.GetLocalWallet(walletName)
- with open(wallet.privFilePath, 'rb') as file:
- data = file.read()
- key = base64.b64encode(data).decode("utf-8")
- return wallet.addrB64, key
- #end define
-
def GetWalletsNameList(self):
self.local.add_log("start GetWalletsNameList function", "debug")
walletsNameList = list()
@@ -1789,16 +1788,6 @@ def GetWalletsNameList(self):
return walletsNameList
#end define
- def GetWallets(self):
- self.local.add_log("start GetWallets function", "debug")
- wallets = list()
- walletsNameList = self.GetWalletsNameList()
- for walletName in walletsNameList:
- wallet = self.GetLocalWallet(walletName)
- wallets.append(wallet)
- return wallets
- #end define
-
def GenerateWalletName(self):
self.local.add_log("start GenerateWalletName function", "debug")
index = 1
@@ -1821,16 +1810,6 @@ def GenerateWalletName(self):
return walletName
#end define
- def WalletsCheck(self):
- self.local.add_log("start WalletsCheck function", "debug")
- wallets = self.GetWallets()
- for wallet in wallets:
- if os.path.isfile(wallet.bocFilePath):
- account = self.GetAccount(wallet.addrB64)
- if account.balance > 0:
- self.SendFile(wallet.bocFilePath, wallet)
- #end define
-
def GetValidatorConfig(self):
#self.local.add_log("start GetValidatorConfig function", "debug")
result = self.validatorConsole.Run("getconfig")
@@ -1937,18 +1916,7 @@ def MoveCoins(self, wallet, dest, coins, **kwargs):
self.SendFile(savedFilePath, wallet, timeout=timeout)
#end define
- def MoveCoinsThroughProxy(self, wallet, dest, coins):
- self.local.add_log("start MoveCoinsThroughProxy function", "debug")
- wallet1 = self.CreateWallet("proxy_wallet1", 0)
- wallet2 = self.CreateWallet("proxy_wallet2", 0)
- self.MoveCoins(wallet, wallet1.addrB64_init, coins)
- self.ActivateWallet(wallet1)
- self.MoveCoins(wallet1, wallet2.addrB64_init, "alld")
- self.ActivateWallet(wallet2)
- self.MoveCoins(wallet2, dest, "alld", flags=["-n"])
- wallet1.Delete()
- wallet2.Delete()
- #end define
+
def MoveCoinsFromHW(self, wallet, destList, **kwargs):
self.local.add_log("start MoveCoinsFromHW function", "debug")
@@ -2140,80 +2108,6 @@ def GetOffers(self):
return offers
#end define
- def GetOfferDiff(self, offerHash):
- self.local.add_log("start GetOfferDiff function", "debug")
- offer = self.GetOffer(offerHash)
- configId = offer["config"]["id"]
- configValue = offer["config"]["value"]
-
- if '{' in configValue or '}' in configValue:
- start = configValue.find('{') + 1
- end = configValue.find('}')
- configValue = configValue[start:end]
- #end if
-
- args = [self.liteClient.appPath, "--global-config", self.liteClient.configPath, "--verbosity", "0"]
- process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- time.sleep(1)
-
- fullConfigAddr = self.GetFullConfigAddr()
- cmd = "runmethodfull {fullConfigAddr} list_proposals".format(fullConfigAddr=fullConfigAddr)
- process.stdin.write(cmd.encode() + b'\n')
- process.stdin.flush()
- time.sleep(1)
-
- cmd = "dumpcellas ConfigParam{configId} {configValue}".format(configId=configId, configValue=configValue)
- process.stdin.write(cmd.encode() + b'\n')
- process.stdin.flush()
- time.sleep(1)
-
- process.terminate()
- text = process.stdout.read().decode()
-
- lines = text.split('\n')
- b = len(lines)
- for i in range(b):
- line = lines[i]
- if "dumping cells as values of TLB type" in line:
- a = i + 2
- break
- #end for
-
- for i in range(a, b):
- line = lines[i]
- if '(' in line:
- start = i
- break
- #end for
-
- for i in range(a, b):
- line = lines[i]
- if '>' in line:
- end = i
- break
- #end for
-
- buff = lines[start:end]
- text = "".join(buff)
- newData = self.Tlb2Json(text)
- newFileName = self.tempDir + "data1diff"
- file = open(newFileName, 'wt')
- newText = json.dumps(newData, indent=2)
- file.write(newText)
- file.close()
-
- oldData = self.GetConfig(configId)
- oldFileName = self.tempDir + "data2diff"
- file = open(oldFileName, 'wt')
- oldText = json.dumps(oldData, indent=2)
- file.write(oldText)
- file.close()
-
- print(oldText)
- args = ["diff", "--color", oldFileName, newFileName]
- subprocess.run(args)
- #end define
-
def GetComplaints(self, electionId=None, past=False):
# Get buffer
bname = "complaints" + str(past)
@@ -2473,6 +2367,7 @@ def get_valid_complaints(self, complaints: dict, election_id: int):
continue
exists = False
+ vload = None
for item in validators_load.values():
if 'fileName' not in item:
continue
@@ -2482,15 +2377,16 @@ def get_valid_complaints(self, complaints: dict, election_id: int):
pseudohash = pubkey + str(election_id)
if pseudohash == complaint['pseudohash']:
exists = True
- vid = item['id']
+ vload = item
break
if not exists:
self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint info was not found, probably it's wrong", "info")
continue
- if vid >= config32['mainValidators']:
- self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint created for non masterchain validator", "info")
+ if (vload["id"] >= config32['mainValidators'] and
+ vload["masterBlocksCreated"] + vload["workBlocksCreated"] > 0):
+ self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint created for non masterchain validator that created more than zero blocks", "info")
continue
# check complaint fine value
@@ -2518,7 +2414,7 @@ def GetOnlineValidators(self):
def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict:
# Get buffer
- bname = f"validatorsLoad{start}{end}"
+ bname = f"validatorsLoad{start}{end}{saveCompFiles}"
buff = self.GetFunctionBuffer(bname, timeout=60)
if buff:
return buff
@@ -2562,7 +2458,10 @@ def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict:
wr = 0
else:
wr = workBlocksCreated / workBlocksExpected
- r = (mr + wr) / 2
+ if masterBlocksExpected > 0: # show only masterchain efficiency for masterchain validator
+ r = mr
+ else:
+ r = (mr + wr) / 2
efficiency = round(r * 100, 2)
if efficiency > 10:
online = True
@@ -2598,21 +2497,23 @@ def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict:
return data
#end define
- def GetValidatorsList(self, past=False, fast=False):
+ def GetValidatorsList(self, past=False, fast=False, start=None, end=None):
# Get buffer
- bname = "validatorsList" + str(past)
+ bname = "validatorsList" + str(past) + str(start) + str(end)
buff = self.GetFunctionBuffer(bname, timeout=60)
if buff:
return buff
#end if
- timestamp = get_timestamp()
- end = timestamp - 60
config = self.GetConfig34()
- if fast:
- start = end - 1000
- else:
- start = config.get("startWorkTime")
+ if end is None:
+ timestamp = get_timestamp()
+ end = timestamp - 60
+ if start is None:
+ if fast:
+ start = end - 1000
+ else:
+ start = config.get("startWorkTime")
if past:
config = self.GetConfig32()
start = config.get("startWorkTime")
@@ -2635,6 +2536,8 @@ def GetValidatorsList(self, past=False, fast=False):
validator["wr"] = validatorsLoad[vid]["wr"]
validator["efficiency"] = validatorsLoad[vid]["efficiency"]
validator["online"] = validatorsLoad[vid]["online"]
+ validator["master_blocks_created"] = validatorsLoad[vid]["masterBlocksCreated"]
+ validator["master_blocks_expected"] = validatorsLoad[vid]["masterBlocksExpected"]
validator["blocks_created"] = validatorsLoad[vid]["masterBlocksCreated"] + validatorsLoad[vid]["workBlocksCreated"]
validator["blocks_expected"] = validatorsLoad[vid]["masterBlocksExpected"] + validatorsLoad[vid]["workBlocksExpected"]
validator["is_masterchain"] = False
@@ -2644,6 +2547,8 @@ def GetValidatorsList(self, past=False, fast=False):
validator["efficiency"] = round(validator["wr"] * 100, 2)
if saveElectionEntries and adnlAddr in saveElectionEntries:
validator["walletAddr"] = saveElectionEntries[adnlAddr]["walletAddr"]
+ validator["stake"] = saveElectionEntries[adnlAddr].get("stake")
+ validator["stake"] = int(validator["stake"]) if validator["stake"] else None
#end for
# Set buffer
@@ -2659,6 +2564,8 @@ def CheckValidators(self, start, end):
electionId = start
complaints = self.GetComplaints(electionId)
valid_complaints = self.get_valid_complaints(complaints, electionId)
+ voted_complaints = self.GetVotedComplaints(complaints)
+ voted_complaints_pseudohashes = [complaint['pseudohash'] for complaint in voted_complaints.values()]
data = self.GetValidatorsLoad(start, end, saveCompFiles=True)
fullElectorAddr = self.GetFullElectorAddr()
wallet = self.GetValidatorWallet(mode="vote")
@@ -2678,9 +2585,9 @@ def CheckValidators(self, start, end):
var2 = item.get("var2")
pubkey = item.get("pubkey")
pseudohash = pubkey + str(electionId)
- if pseudohash in valid_complaints:
+ if pseudohash in valid_complaints or pseudohash in voted_complaints_pseudohashes: # do not create complaints that already created or voted by ourself
continue
- if item['id'] >= config['mainValidators']: # do not create complaints for non-masterchain validators
+ if item['id'] >= config['mainValidators'] and item["masterBlocksCreated"] + item["workBlocksCreated"] > 0: # create complaints for non-masterchain validators only if they created 0 blocks
continue
# Create complaint
fileName = self.remove_proofs_from_complaint(fileName)
@@ -2766,24 +2673,6 @@ def GetDbSize(self, exceptions="log"):
return result
#end define
- def check_adnl(self):
- telemetry = self.local.db.get("sendTelemetry", False)
- check_adnl = self.local.db.get("checkAdnl", telemetry)
- if not check_adnl:
- return
- url = 'http://45.129.96.53/adnl_check'
- try:
- data = self.get_local_adnl_data()
- response = requests.post(url, json=data, timeout=5).json()
- except Exception as e:
- self.local.add_log(f'Failed to check adnl connection: {type(e)}: {e}', 'error')
- return False
- result = response.get("ok")
- if not result:
- self.local.add_log(f'Failed to check adnl connection to local node: {response.get("message")}', 'error')
- return result
- #end define
-
def get_local_adnl_data(self):
def int2ip(dec):
@@ -2938,90 +2827,6 @@ def GetItemFromDict(self, data, search):
return None
#end define
- def GetDomainFromAuction(self, walletName, addr):
- wallet = self.GetLocalWallet(walletName)
- bocPath = self.local.buffer.my_temp_dir + "get_dns_data.boc"
- bocData = bytes.fromhex("b5ee9c7241010101000e0000182fcb26a20000000000000000f36cae4d")
- with open(bocPath, 'wb') as file:
- file.write(bocData)
- resultFilePath = self.SignBocWithWallet(wallet, bocPath, addr, 0.1)
- self.SendFile(resultFilePath, wallet)
- #end define
-
- def NewDomain(self, domain):
- self.local.add_log("start NewDomain function", "debug")
- domainName = domain["name"]
- buff = domainName.split('.')
- subdomain = buff.pop(0)
- dnsDomain = ".".join(buff)
- dnsAddr = self.GetDomainAddr(dnsDomain)
- wallet = self.GetLocalWallet(domain["walletName"])
- expireInSec = 700000 # fix me
- catId = 1 # fix me
-
- # Check if domain is busy
- domainEndTime = self.GetDomainEndTime(domainName)
- if domainEndTime > 0:
- raise Exception("NewDomain error: domain is busy")
- #end if
-
- fileName = self.tempDir + "dns-msg-body.boc"
- args = ["auto-dns.fif", dnsAddr, "add", subdomain, expireInSec, "owner", wallet.addrB64, "cat", catId, "adnl", domain["adnlAddr"], "-o", fileName]
- result = self.fift.Run(args)
- resultFilePath = parse(result, "Saved to file ", ')')
- resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, dnsAddr, 1.7)
- self.SendFile(resultFilePath, wallet)
- self.AddDomain(domain)
- #end define
-
- def AddDomain(self, domain):
- if "domains" not in self.local.db:
- self.local.db["domains"] = list()
- #end if
- self.local.db["domains"].append(domain)
- self.local.save()
- #end define
-
- def GetDomains(self):
- domains = self.local.db.get("domains", list())
- for domain in domains:
- domainName = domain.get("name")
- domain["endTime"] = self.GetDomainEndTime(domainName)
- return domains
- #end define
-
- def GetDomain(self, domainName):
- domain = dict()
- domain["name"] = domainName
- domain["adnlAddr"] = self.GetDomainAdnlAddr(domainName)
- domain["endTime"] = self.GetDomainEndTime(domainName)
- return domain
- #end define
-
- def DeleteDomain(self, domainName):
- domains = self.local.db.get("domains")
- for domain in domains:
- if (domainName == domain.get("name")):
- domains.remove(domain)
- self.local.save()
- return
- raise Exception("DeleteDomain error: Domain not found")
- #end define
-
- def GetAutoTransferRules(self):
- autoTransferRules = self.local.db.get("autoTransferRules")
- if autoTransferRules is None:
- autoTransferRules = list()
- self.local.db["autoTransferRules"] = autoTransferRules
- return autoTransferRules
- #end define
-
- def AddAutoTransferRule(self, rule):
- autoTransferRules = self.GetAutoTransferRules()
- autoTransferRules.append(rule)
- self.local.save()
- #end define
-
def AddBookmark(self, bookmark):
if "bookmarks" not in self.local.db:
self.local.db["bookmarks"] = list()
@@ -3038,23 +2843,11 @@ def GetBookmarks(self):
return bookmarks
#end define
- def GetBookmarkAddr(self, type, name):
- bookmarks = self.local.db.get("bookmarks", list())
- for bookmark in bookmarks:
- bookmarkType = bookmark.get("type")
- bookmarkName = bookmark.get("name")
- bookmarkAddr = bookmark.get("addr")
- if (bookmarkType == type and bookmarkName == name):
- return bookmarkAddr
- raise Exception("GetBookmarkAddr error: Bookmark not found")
- #end define
-
- def DeleteBookmark(self, name, type):
+ def DeleteBookmark(self, name):
bookmarks = self.local.db.get("bookmarks")
for bookmark in bookmarks:
- bookmarkType = bookmark.get("type")
- bookmarkName = bookmark.get("name")
- if (type == bookmarkType and name == bookmarkName):
+ bookmark_name = bookmark.get("name")
+ if name == bookmark_name:
bookmarks.remove(bookmark)
self.local.save()
return
@@ -3062,23 +2855,12 @@ def DeleteBookmark(self, name, type):
#end define
def WriteBookmarkData(self, bookmark):
- type = bookmark.get("type")
- if type == "account":
- addr = bookmark.get("addr")
- account = self.GetAccount(addr)
- if account.status == "empty":
- data = "empty"
- else:
- data = account.balance
- elif type == "domain":
- domainName = bookmark.get("addr")
- endTime = self.GetDomainEndTime(domainName)
- if endTime == 0:
- data = "free"
- else:
- data = timestamp2datetime(endTime, "%d.%m.%Y")
+ addr = bookmark.get("addr")
+ account = self.GetAccount(addr)
+ if account.status == "empty":
+ data = "empty"
else:
- data = "null"
+ data = account.balance
bookmark["data"] = data
#end define
@@ -3125,18 +2907,18 @@ def GetVotedComplaints(self, complaints: dict):
return result
#end define
- def GetDestinationAddr(self, destination):
+ def get_destination_addr(self, destination):
if self.IsAddrB64(destination):
pass
elif self.IsAddrFull(destination):
destination = self.AddrFull2AddrB64(destination)
else:
- walletsNameList = self.GetWalletsNameList()
- if destination in walletsNameList:
+ wallets_name_list = self.GetWalletsNameList()
+ if destination in wallets_name_list:
wallet = self.GetLocalWallet(destination)
destination = wallet.addrB64
return destination
- #end define
+ # end define
def AddrFull2AddrB64(self, addrFull, bounceable=True):
if addrFull is None or "None" in addrFull:
@@ -3272,6 +3054,48 @@ def GetStatistics(self, name, statistics=None):
return data
#end define
+ def get_node_statistics(self):
+ """
+ :return: stats for collated/validated blocks since round beggining and stats for ls queries for the last minute
+ """
+ stats = self.local.db.get('statistics', {}).get('node')
+ result = {}
+ if stats is not None and len(stats) == 3 and stats[0] is not None:
+ for k in ['master', 'shard']:
+ result = {
+ 'collated': {
+ 'ok': 0,
+ 'error': 0,
+ },
+ 'validated': {
+ 'ok': 0,
+ 'error': 0,
+ }
+ }
+ collated_ok = stats[2]['collated_blocks'][k]['ok'] - stats[0]['collated_blocks'][k]['ok']
+ collated_error = stats[2]['collated_blocks'][k]['error'] - stats[0]['collated_blocks'][k]['error']
+ validated_ok = stats[2]['validated_blocks'][k]['ok'] - stats[0]['validated_blocks'][k]['ok']
+ validated_error = stats[2]['validated_blocks'][k]['error'] - stats[0]['validated_blocks'][k]['error']
+ result['collated'][k] = {
+ 'ok': collated_ok,
+ 'error': collated_error,
+ }
+ result['validated'][k] = {
+ 'ok': validated_ok,
+ 'error': validated_error,
+ }
+ result['collated']['ok'] += collated_ok
+ result['collated']['error'] += collated_error
+ result['validated']['ok'] += validated_ok
+ result['validated']['error'] += validated_error
+ if stats is not None and len(stats) >= 2 and stats[0] is not None:
+ result['ls_queries'] = {
+ 'ok': stats[-1]['ls_queries']['ok'] - stats[-2]['ls_queries']['ok'],
+ 'error': stats[-1]['ls_queries']['error'] - stats[-2]['ls_queries']['error'],
+ 'time': stats[-1].get('timestamp', 0) - stats[-2].get('timestamp', 0),
+ }
+ return result
+
def GetSettings(self, name):
# self.local.load_db()
result = self.local.db.get(name)
@@ -3284,6 +3108,7 @@ def SetSettings(self, name, data):
except: pass
self.local.db[name] = data
self.local.save()
+ self.create_self_db_backup()
#end define
def migrate_to_modes(self):
@@ -3325,6 +3150,9 @@ def check_enable_mode(self, name):
if self.using_liteserver():
raise Exception(f'Cannot enable validator mode while liteserver mode is enabled. '
f'Use `disable_mode liteserver` first.')
+ if name == 'liquid-staking':
+ from mytoninstaller.settings import enable_ton_http_api
+ enable_ton_http_api(self.local)
def enable_mode(self, name):
if name not in MODES:
@@ -3365,6 +3193,19 @@ def using_validator(self):
def using_liteserver(self):
return self.get_mode_value('liteserver')
+ def using_alert_bot(self):
+ return self.get_mode_value('alert-bot')
+
+ def using_prometheus(self):
+ return self.get_mode_value('prometheus')
+
+ def in_initial_sync(self):
+ return self.local.db.get('initialSync', False)
+
+ def set_initial_sync_off(self):
+ self.local.db.pop('initialSync', None)
+ self.local.save()
+
def Tlb2Json(self, text):
# Заменить скобки
start = 0
@@ -3561,9 +3402,8 @@ def PendWithdrawFromPool(self, poolAddr, amount):
#end define
def HandlePendingWithdraw(self, pendingWithdraws, poolAddr):
- amount = pendingWithdraws.get(poolAddr)
+ amount = pendingWithdraws.pop(poolAddr)
self.WithdrawFromPoolProcess(poolAddr, amount)
- pendingWithdraws.pop(poolAddr)
#end define
def GetPendingWithdraws(self):
@@ -3785,7 +3625,8 @@ def GetController(self, mode):
#end define
def GetControllerRequiredBalanceForLoan(self, controllerAddr, credit, interest):
- cmd = f"runmethodfull {controllerAddr} required_balance_for_loan {credit} {interest}"
+ credit_nano_tons = credit * 10**9
+ cmd = f"runmethodfull {controllerAddr} required_balance_for_loan {credit_nano_tons} {interest}"
result = self.liteClient.Run(cmd)
data = self.Result2List(result)
if data is None:
@@ -3877,7 +3718,7 @@ def CalculateLoanAmount(self, min_loan, max_loan, max_interest):
print(f"CalculateLoanAmount data: {data}")
url = "http://127.0.0.1:8801/runGetMethod"
- res = requests.post(url, json=data)
+ res = requests.post(url, json=data, timeout=3)
res_data = res.json()
if res_data.get("ok") is False:
error = res_data.get("error")
@@ -4063,6 +3904,16 @@ def GetNetworkName(self):
return "unknown"
#end define
+ def get_node_ip(self):
+ try:
+ config = self.GetValidatorConfig()
+ return int2ip(config['addrs'][0]['ip'])
+ except:
+ return None
+
+ def get_validator_engine_ip(self):
+ return self.validatorConsole.addr.split(':')[0]
+
def GetFunctionBuffer(self, name, timeout=10):
timestamp = get_timestamp()
buff = self.local.buffer.get(name)
diff --git a/mytoncore/utils.py b/mytoncore/utils.py
index 0a8bdc91..a31e299c 100644
--- a/mytoncore/utils.py
+++ b/mytoncore/utils.py
@@ -1,6 +1,7 @@
import base64
import json
import re
+import subprocess
def str2b64(s):
@@ -97,3 +98,6 @@ def parse_db_stats(path: str):
result[s[0]] = {k: float(v) for k, v in items}
return result
# end define
+
+def get_hostname():
+ return subprocess.run(["hostname", "-f"], stdout=subprocess.PIPE).stdout.decode().strip()
diff --git a/mytonctrl/mytonctrl.py b/mytonctrl/mytonctrl.py
index 3811c7a4..9bad6550 100755
--- a/mytonctrl/mytonctrl.py
+++ b/mytonctrl/mytonctrl.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
import base64
+import random
import subprocess
import json
import psutil
@@ -10,6 +11,8 @@
from functools import partial
+import requests
+
from mypylib.mypylib import (
int2ip,
get_git_author_and_repo,
@@ -29,7 +32,7 @@
color_text,
bcolors,
Dict,
- MyPyClass
+ MyPyClass, ip2int
)
from mypyconsole.mypyconsole import MyPyConsole
@@ -42,10 +45,12 @@
)
from mytoncore.telemetry import is_host_virtual
from mytonctrl.migrate import run_migrations
-from mytonctrl.utils import GetItemFromList, timestamp2utcdatetime, fix_git_config, GetColorInt
+from mytonctrl.utils import GetItemFromList, timestamp2utcdatetime, fix_git_config, is_hex, GetColorInt
import sys, getopt, os
+from mytoninstaller.config import get_own_ip
+
def Init(local, ton, console, argv):
# Load translate table
@@ -66,6 +71,7 @@ def inject_globals(func):
console.name = "MyTonCtrl"
console.startFunction = inject_globals(PreUp)
console.debug = ton.GetSettings("debug")
+ console.local = local
console.AddItem("update", inject_globals(Update), local.translate("update_cmd"))
console.AddItem("upgrade", inject_globals(Upgrade), local.translate("upgrade_cmd"))
@@ -80,56 +86,18 @@ def inject_globals(func):
console.AddItem("set", inject_globals(SetSettings), local.translate("set_cmd"))
console.AddItem("rollback", inject_globals(rollback_to_mtc1), local.translate("rollback_cmd"))
- console.AddItem("seqno", inject_globals(Seqno), local.translate("seqno_cmd"))
- console.AddItem("getconfig", inject_globals(GetConfig), local.translate("getconfig_cmd"))
- console.AddItem("get_pool_data", inject_globals(GetPoolData), local.translate("get_pool_data_cmd"))
-
- console.AddItem("nw", inject_globals(CreatNewWallet), local.translate("nw_cmd"))
- console.AddItem("aw", inject_globals(ActivateWallet), local.translate("aw_cmd"))
- console.AddItem("wl", inject_globals(PrintWalletsList), local.translate("wl_cmd"))
- console.AddItem("iw", inject_globals(ImportWallet), local.translate("iw_cmd"))
- console.AddItem("swv", inject_globals(SetWalletVersion), local.translate("swv_cmd"))
- console.AddItem("ew", inject_globals(ExportWallet), local.translate("ex_cmd"))
- console.AddItem("dw", inject_globals(DeleteWallet), local.translate("dw_cmd"))
-
- console.AddItem("vas", inject_globals(ViewAccountStatus), local.translate("vas_cmd"))
- console.AddItem("vah", inject_globals(ViewAccountHistory), local.translate("vah_cmd"))
- console.AddItem("mg", inject_globals(MoveCoins), local.translate("mg_cmd"))
- console.AddItem("mgtp", inject_globals(MoveCoinsThroughProxy), local.translate("mgtp_cmd"))
-
- console.AddItem("nb", inject_globals(CreatNewBookmark), local.translate("nb_cmd"))
- console.AddItem("bl", inject_globals(PrintBookmarksList), local.translate("bl_cmd"))
- console.AddItem("db", inject_globals(DeleteBookmark), local.translate("db_cmd"))
-
- # console.AddItem("nr", inject_globals(CreatNewAutoTransferRule), local.translate("nr_cmd")) # "Добавить правило автопереводов в расписание / Create new auto transfer rule"
- # console.AddItem("rl", inject_globals(PrintAutoTransferRulesList), local.translate("rl_cmd")) # "Показать правила автопереводов / Show auto transfer rule list"
- # console.AddItem("dr", inject_globals(DeleteAutoTransferRule), local.translate("dr_cmd")) # "Удалить правило автопереводов из расписания / Delete auto transfer rule"
-
- # console.AddItem("nd", inject_globals(NewDomain), local.translate("nd_cmd"))
- # console.AddItem("dl", inject_globals(PrintDomainsList), local.translate("dl_cmd"))
- # console.AddItem("vds", inject_globals(ViewDomainStatus), local.translate("vds_cmd"))
- # console.AddItem("dd", inject_globals(DeleteDomain), local.translate("dd_cmd"))
- # console.AddItem("gdfa", inject_globals(GetDomainFromAuction), local.translate("gdfa_cmd"))
-
- console.AddItem("ol", inject_globals(PrintOffersList), local.translate("ol_cmd"))
- console.AddItem("od", inject_globals(OfferDiff), local.translate("od_cmd"))
-
- console.AddItem("el", inject_globals(PrintElectionEntriesList), local.translate("el_cmd"))
- console.AddItem("vl", inject_globals(PrintValidatorList), local.translate("vl_cmd"))
- console.AddItem("cl", inject_globals(PrintComplaintsList), local.translate("cl_cmd"))
-
#console.AddItem("xrestart", inject_globals(Xrestart), local.translate("xrestart_cmd"))
#console.AddItem("xlist", inject_globals(Xlist), local.translate("xlist_cmd"))
#console.AddItem("gpk", inject_globals(GetPubKey), local.translate("gpk_cmd"))
#console.AddItem("ssoc", inject_globals(SignShardOverlayCert), local.translate("ssoc_cmd"))
#console.AddItem("isoc", inject_globals(ImportShardOverlayCert), local.translate("isoc_cmd"))
- from modules.custom_overlays import CustomOverlayModule
- module = CustomOverlayModule(ton, local)
+ from modules.backups import BackupModule
+ module = BackupModule(ton, local)
module.add_console_commands(console)
- from modules.collator_config import CollatorConfigModule
- module = CollatorConfigModule(ton, local)
+ from modules.custom_overlays import CustomOverlayModule
+ module = CustomOverlayModule(ton, local)
module.add_console_commands(console)
if ton.using_validator():
@@ -137,6 +105,18 @@ def inject_globals(func):
module = ValidatorModule(ton, local)
module.add_console_commands(console)
+ from modules.collator_config import CollatorConfigModule
+ module = CollatorConfigModule(ton, local)
+ module.add_console_commands(console)
+
+ from modules.wallet import WalletModule
+ module = WalletModule(ton, local)
+ module.add_console_commands(console)
+
+ from modules.utilities import UtilitiesModule
+ module = UtilitiesModule(ton, local)
+ module.add_console_commands(console)
+
if ton.using_pool(): # add basic pool functions (pools_list, delete_pool, import_pool)
from modules.pool import PoolModule
module = PoolModule(ton, local)
@@ -157,9 +137,13 @@ def inject_globals(func):
module = ControllerModule(ton, local)
module.add_console_commands(console)
- console.AddItem("cleanup", inject_globals(cleanup_validator_db), local.translate("cleanup_cmd"))
+ if ton.using_alert_bot():
+ from modules.alert_bot import AlertBotModule
+ module = AlertBotModule(ton, local)
+ module.add_console_commands(console)
+
console.AddItem("benchmark", inject_globals(run_benchmark), local.translate("benchmark_cmd"))
- console.AddItem("activate_ton_storage_provider", inject_globals(activate_ton_storage_provider), local.translate("activate_ton_storage_provider_cmd"))
+ # console.AddItem("activate_ton_storage_provider", inject_globals(activate_ton_storage_provider), local.translate("activate_ton_storage_provider_cmd"))
# Process input parameters
opts, args = getopt.getopt(argv,"hc:w:",["config=","wallets="])
@@ -247,7 +231,6 @@ def PreUp(local: MyPyClass, ton: MyTonCore):
CheckMytonctrlUpdate(local)
check_installer_user(local)
check_vport(local, ton)
- ton.check_adnl()
warnings(local, ton)
# CheckTonUpdate()
#end define
@@ -257,7 +240,7 @@ def Installer(args):
# args = ["python3", "/usr/src/mytonctrl/mytoninstaller.py"]
cmd = ["python3", "-m", "mytoninstaller"]
if args:
- cmd += ["-c", *args]
+ cmd += ["-c", " ".join(args)]
subprocess.run(cmd)
#end define
@@ -315,7 +298,7 @@ def check_git(input_args, default_repo, text, default_branch='master'):
need_branch = data.get("branch")
# Check if remote repo is different from default
- if ((need_author is None and local_author != default_author) or
+ if ((need_author is None and local_author != default_author) or
(need_repo is None and local_repo != default_repo)):
remote_url = f"https://github.com/{local_author}/{local_repo}/tree/{need_branch if need_branch else local_branch}"
raise Exception(f"{text} error: You are on {remote_url} remote url, to update to the tip use `{text} {remote_url}` command")
@@ -334,8 +317,11 @@ def check_git(input_args, default_repo, text, default_branch='master'):
#end define
def check_branch_exists(author, repo, branch):
+ if len(branch) >= 6 and is_hex(branch):
+ print('Hex name detected, skip branch existence check.')
+ return
url = f"https://github.com/{author}/{repo}.git"
- args = ["git", "ls-remote", "--heads", url, branch]
+ args = ["git", "ls-remote", "--heads", "--tags", url, branch]
process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3)
output = process.stdout.decode("utf-8")
if branch not in output:
@@ -384,14 +370,6 @@ def Upgrade(ton, args):
upgrade_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/upgrade.sh')
runArgs = ["bash", upgrade_script_path, "-a", author, "-r", repo, "-b", branch]
exitCode = run_as_root(runArgs)
- if ton.using_validator():
- try:
- from mytoninstaller.mytoninstaller import set_node_argument, get_node_args
- node_args = get_node_args()
- if node_args.get('--state-ttl') == '604800':
- set_node_argument(ton.local, ["--state-ttl", "-d"])
- except Exception as e:
- color_print(f"{{red}}Failed to set node argument: {e} {{endc}}")
if exitCode == 0:
text = "Upgrade - {green}OK{endc}"
else:
@@ -418,12 +396,6 @@ def rollback_to_mtc1(local, ton, args):
local.exit()
#end define
-def cleanup_validator_db(ton, args):
- cleanup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/cleanup.sh')
- run_args = ["bash", cleanup_script_path]
- exit_code = run_as_root(run_args)
-#end define
-
def run_benchmark(ton, args):
timeout = 200
benchmark_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/benchmark.sh')
@@ -468,6 +440,9 @@ def check_disk_usage(local, ton):
def check_sync(local, ton):
validator_status = ton.GetValidatorStatus()
+ if validator_status.initial_sync or ton.in_initial_sync():
+ print_warning(local, "initial_sync_warning")
+ return
if not validator_status.is_working or validator_status.out_of_sync >= 20:
print_warning(local, "sync_warning")
#end define
@@ -494,11 +469,40 @@ def check_vps(local, ton):
color_print(f"Virtualization detected: {data['product_name']}")
#end define
+def check_tg_channel(local, ton):
+ if ton.using_validator() and ton.local.db.get("subscribe_tg_channel") is None:
+ print_warning(local, "subscribe_tg_channel_warning")
+#end difine
+
+def check_slashed(local, ton):
+ validator_status = ton.GetValidatorStatus()
+ if not ton.using_validator() or not validator_status.is_working or validator_status.out_of_sync >= 20:
+ return
+ from modules import ValidatorModule
+ validator_module = ValidatorModule(ton, local)
+ c = validator_module.get_my_complaint()
+ if c:
+ warning = local.translate("slashed_warning").format(int(c['suggestedFine']))
+ print_warning(local, warning)
+#end define
+
+def check_adnl(local, ton):
+ from modules.utilities import UtilitiesModule
+ utils_module = UtilitiesModule(ton, local)
+ ok, error = utils_module.check_adnl_connection()
+ if not ok:
+ error = "{red}" + error + "{endc}"
+ print_warning(local, error)
+#end define
+
def warnings(local, ton):
- check_disk_usage(local, ton)
- check_sync(local, ton)
- check_validator_balance(local, ton)
- check_vps(local, ton)
+ local.try_function(check_disk_usage, args=[local, ton])
+ local.try_function(check_sync, args=[local, ton])
+ local.try_function(check_adnl, args=[local, ton])
+ local.try_function(check_validator_balance, args=[local, ton])
+ local.try_function(check_vps, args=[local, ton])
+ local.try_function(check_tg_channel, args=[local, ton])
+ local.try_function(check_slashed, args=[local, ton])
#end define
def CheckTonUpdate(local):
@@ -508,21 +512,15 @@ def CheckTonUpdate(local):
color_print(local.translate("ton_update_available"))
#end define
-def PrintTest(local, args):
- print(json.dumps(local.buffer, indent=2))
-#end define
-
-def sl(ton, args):
- Slashing(ton.local, ton)
-#end define
-
-
def mode_status(ton, args):
from modules import get_mode
modes = ton.get_modes()
table = [["Name", "Status", "Description"]]
for mode_name in modes:
mode = get_mode(mode_name)
+ if mode is None:
+ color_print(f"{{red}}Mode {mode_name} not found{{endc}}")
+ continue
status = color_text('{green}enabled{endc}' if modes[mode_name] else '{red}disabled{endc}')
table.append([mode_name, status, mode.description])
print_table(table)
@@ -575,7 +573,7 @@ def PrintStatus(local, ton, args):
config34 = ton.GetConfig34()
config36 = ton.GetConfig36()
totalValidators = config34["totalValidators"]
-
+
if opt != "fast":
onlineValidators = ton.GetOnlineValidators()
# validator_efficiency = ton.GetValidatorEfficiency()
@@ -586,28 +584,28 @@ def PrintStatus(local, ton, args):
if oldStartWorkTime is None:
oldStartWorkTime = config34.get("startWorkTime")
shardsNumber = ton.GetShardsNumber()
-
+
config15 = ton.GetConfig15()
config17 = ton.GetConfig17()
fullConfigAddr = ton.GetFullConfigAddr()
fullElectorAddr = ton.GetFullElectorAddr()
startWorkTime = ton.GetActiveElectionId(fullElectorAddr)
validator_index = ton.GetValidatorIndex()
-
+
offersNumber = ton.GetOffersNumber()
complaintsNumber = ton.GetComplaintsNumber()
-
+
tpsAvg = ton.GetStatistics("tpsAvg", statistics)
-
+
if validator_wallet is not None:
validator_account = ton.GetAccount(validator_wallet.addrB64)
#end if
if all_status:
PrintTonStatus(local, network_name, startWorkTime, totalValidators, onlineValidators, shardsNumber, offersNumber, complaintsNumber, tpsAvg)
- PrintLocalStatus(local, adnl_addr, validator_index, validator_efficiency, validator_wallet, validator_account, validator_status,
+ PrintLocalStatus(local, ton, adnl_addr, validator_index, validator_efficiency, validator_wallet, validator_account, validator_status,
db_size, db_usage, memory_info, swap_info, net_load_avg, disks_load_avg, disks_load_percent_avg, fullnode_adnl)
- if all_status:
+ if all_status and ton.using_validator():
PrintTonConfig(local, fullConfigAddr, fullElectorAddr, config15, config17)
PrintTimes(local, rootWorkchainEnabledTime_int, startWorkTime, oldStartWorkTime, config15)
#end define
@@ -656,7 +654,7 @@ def PrintTonStatus(local, network_name, startWorkTime, totalValidators, onlineVa
print()
#end define
-def PrintLocalStatus(local, adnlAddr, validatorIndex, validatorEfficiency, validatorWallet, validatorAccount, validator_status, dbSize, dbUsage, memoryInfo, swapInfo, netLoadAvg, disksLoadAvg, disksLoadPercentAvg, fullnode_adnl):
+def PrintLocalStatus(local, ton, adnlAddr, validatorIndex, validatorEfficiency, validatorWallet, validatorAccount, validator_status, dbSize, dbUsage, memoryInfo, swapInfo, netLoadAvg, disksLoadAvg, disksLoadPercentAvg, fullnode_adnl):
if validatorWallet is None:
return
walletAddr = validatorWallet.addrB64
@@ -734,14 +732,50 @@ def PrintLocalStatus(local, adnlAddr, validatorIndex, validatorEfficiency, valid
validatorStatus_color = GetColorStatus(validatorStatus_bool)
mytoncoreStatus_text = local.translate("local_status_mytoncore_status").format(mytoncoreStatus_color, mytoncoreUptime_text)
validatorStatus_text = local.translate("local_status_validator_status").format(validatorStatus_color, validatorUptime_text)
- validator_out_of_sync_text = local.translate("local_status_validator_out_of_sync").format(GetColorInt(validator_status.out_of_sync, 20, logic="less", ending=" s"))
- validator_out_of_ser_text = local.translate("local_status_validator_out_of_ser").format(f'{validator_status.out_of_ser} blocks ago')
+ validator_initial_sync_text = ''
+ validator_out_of_sync_text = ''
+
+ if validator_status.initial_sync:
+ validator_initial_sync_text = local.translate("local_status_validator_initial_sync").format(validator_status['process.initial_sync'])
+ elif ton.in_initial_sync(): # states have been downloaded, now downloading blocks
+ validator_initial_sync_text = local.translate("local_status_validator_initial_sync").format(
+ f'Syncing blocks, last known block was {validator_status.out_of_sync} s ago'
+ )
+ else:
+ validator_out_of_sync_text = local.translate("local_status_validator_out_of_sync").format(GetColorInt(validator_status.out_of_sync, 20, logic="less"))
+ master_out_of_sync_text = local.translate("local_status_master_out_of_sync").format(GetColorInt(validator_status.masterchain_out_of_sync, 20, logic="less", ending=" sec"))
+ shard_out_of_sync_text = local.translate("local_status_shard_out_of_sync").format(GetColorInt(validator_status.shardchain_out_of_sync, 5, logic="less", ending=" blocks"))
+
+ validator_out_of_ser_text = None
+
+ if validator_status.stateserializerenabled:
+ validator_out_of_ser_text = local.translate("local_status_validator_out_of_ser").format(f'{validator_status.out_of_ser} blocks ago')
+
+ active_validator_groups = None
+
+ if ton.using_validator() and validator_status.validator_groups_master and validator_status.validator_groups_shard:
+ active_validator_groups = local.translate("active_validator_groups").format(validator_status.validator_groups_master, validator_status.validator_groups_shard)
+
+ collated, validated = None, None
+ ls_queries = None
+ if ton.using_validator():
+ node_stats = ton.get_node_statistics()
+ if node_stats and 'collated' in node_stats and 'validated' in node_stats:
+ collated = local.translate('collated_blocks').format(node_stats['collated']['ok'], node_stats['collated']['error'])
+ validated = local.translate('validated_blocks').format(node_stats['validated']['ok'], node_stats['validated']['error'])
+ else:
+ collated = local.translate('collated_blocks').format('collecting data...', 'wait for the next validation round')
+ validated = local.translate('validated_blocks').format('collecting data...', 'wait for the next validation round')
+ if ton.using_liteserver():
+ node_stats = ton.get_node_statistics()
+ if node_stats and 'ls_queries' in node_stats:
+ ls_queries = local.translate('ls_queries').format(node_stats['ls_queries']['time'], node_stats['ls_queries']['ok'], node_stats['ls_queries']['error'])
dbSize_text = GetColorInt(dbSize, 1000, logic="less", ending=" Gb")
dbUsage_text = GetColorInt(dbUsage, 80, logic="less", ending="%")
dbStatus_text = local.translate("local_status_db").format(dbSize_text, dbUsage_text)
-
+
# Mytonctrl and validator git hash
mtcGitPath = "/usr/src/mytonctrl"
validatorGitPath = "/usr/src/ton"
@@ -760,21 +794,42 @@ def PrintLocalStatus(local, adnlAddr, validatorIndex, validatorEfficiency, valid
validatorVersion_text = local.translate("local_status_version_validator").format(validatorGitHash_text, validatorGitBranch_text)
color_print(local.translate("local_status_head"))
- print(validatorIndex_text)
- # print(validatorEfficiency_text)
+ node_ip = ton.get_validator_engine_ip()
+ is_node_remote = node_ip != '127.0.0.1'
+ if is_node_remote:
+ nodeIpAddr_text = local.translate("node_ip_address").format(node_ip)
+ color_print(nodeIpAddr_text)
+ if ton.using_validator():
+ print(validatorIndex_text)
+ # print(validatorEfficiency_text)
print(adnlAddr_text)
print(fullnode_adnl_text)
- print(walletAddr_text)
- print(walletBalance_text)
+ if ton.using_validator():
+ print(walletAddr_text)
+ print(walletBalance_text)
print(cpuLoad_text)
print(netLoad_text)
print(memoryLoad_text)
-
+
print(disksLoad_text)
print(mytoncoreStatus_text)
- print(validatorStatus_text)
- print(validator_out_of_sync_text)
- print(validator_out_of_ser_text)
+ if not is_node_remote:
+ print(validatorStatus_text)
+ if validator_initial_sync_text:
+ print(validator_initial_sync_text)
+ if validator_out_of_sync_text:
+ print(validator_out_of_sync_text)
+ print(master_out_of_sync_text)
+ print(shard_out_of_sync_text)
+ if validator_out_of_ser_text:
+ print(validator_out_of_ser_text)
+ if active_validator_groups:
+ print(active_validator_groups)
+ if collated and validated:
+ print(collated)
+ print(validated)
+ if ls_queries:
+ print(ls_queries)
print(dbStatus_text)
print(mtcVersion_text)
print(validatorVersion_text)
@@ -867,511 +922,6 @@ def GetColorTime(datetime, timestamp):
return result
#end define
-def Seqno(ton, args):
- try:
- walletName = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} seqno ")
- return
- wallet = ton.GetLocalWallet(walletName)
- seqno = ton.GetSeqno(wallet)
- print(walletName, "seqno:", seqno)
-#end define
-
-def CreatNewWallet(ton, args):
- version = "v1"
- try:
- if len(args) == 0:
- walletName = ton.GenerateWalletName()
- workchain = 0
- else:
- workchain = int(args[0])
- walletName = args[1]
- if len(args) > 2:
- version = args[2]
- if len(args) == 4:
- subwallet = int(args[3])
- else:
- subwallet = 698983191 + workchain # 0x29A9A317 + workchain
- except:
- color_print("{red}Bad args. Usage:{endc} nw [ ]")
- return
- wallet = ton.CreateWallet(walletName, workchain, version, subwallet=subwallet)
- table = list()
- table += [["Name", "Workchain", "Address"]]
- table += [[wallet.name, wallet.workchain, wallet.addrB64_init]]
- print_table(table)
-#end define
-
-def ActivateWallet(local, ton, args):
- try:
- walletName = args[0]
- except Exception as err:
- walletName = "all"
- if walletName == "all":
- ton.WalletsCheck()
- else:
- wallet = ton.GetLocalWallet(walletName)
- ton.ActivateWallet(wallet)
- color_print("ActivateWallet - {green}OK{endc}")
-#end define
-
-def PrintWalletsList(ton, args):
- table = list()
- table += [["Name", "Status", "Balance", "Ver", "Wch", "Address"]]
- data = ton.GetWallets()
- if (data is None or len(data) == 0):
- print("No data")
- return
- for wallet in data:
- account = ton.GetAccount(wallet.addrB64)
- if account.status != "active":
- wallet.addrB64 = wallet.addrB64_init
- table += [[wallet.name, account.status, account.balance, wallet.version, wallet.workchain, wallet.addrB64]]
- print_table(table)
-#end define
-
-def ImportWallet(ton, args):
- try:
- addr = args[0]
- key = args[1]
- except:
- color_print("{red}Bad args. Usage:{endc} iw ")
- return
- name = ton.ImportWallet(addr, key)
- print("Wallet name:", name)
-#end define
-
-def SetWalletVersion(ton, args):
- try:
- addr = args[0]
- version = args[1]
- except:
- color_print("{red}Bad args. Usage:{endc} swv ")
- return
- ton.SetWalletVersion(addr, version)
- color_print("SetWalletVersion - {green}OK{endc}")
-#end define
-
-def ExportWallet(ton, args):
- try:
- name = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} ew ")
- return
- addr, key = ton.ExportWallet(name)
- print("Wallet name:", name)
- print("Address:", addr)
- print("Secret key:", key)
-#end define
-
-def DeleteWallet(ton, args):
- try:
- walletName = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} dw ")
- return
- if input("Are you sure you want to delete this wallet (yes/no): ") != "yes":
- print("Cancel wallet deletion")
- return
- wallet = ton.GetLocalWallet(walletName)
- wallet.Delete()
- color_print("DeleteWallet - {green}OK{endc}")
-#end define
-
-def ViewAccountStatus(ton, args):
- try:
- addrB64 = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} vas ")
- return
- addrB64 = ton.GetDestinationAddr(addrB64)
- account = ton.GetAccount(addrB64)
- version = ton.GetVersionFromCodeHash(account.codeHash)
- statusTable = list()
- statusTable += [["Address", "Status", "Balance", "Version"]]
- statusTable += [[addrB64, account.status, account.balance, version]]
- codeHashTable = list()
- codeHashTable += [["Code hash"]]
- codeHashTable += [[account.codeHash]]
- historyTable = GetHistoryTable(ton, addrB64, 10)
- print_table(statusTable)
- print()
- print_table(codeHashTable)
- print()
- print_table(historyTable)
-#end define
-
-def ViewAccountHistory(ton, args):
- try:
- addr = args[0]
- limit = int(args[1])
- except:
- color_print("{red}Bad args. Usage:{endc} vah ")
- return
- table = GetHistoryTable(ton, addr, limit)
- print_table(table)
-#end define
-
-def GetHistoryTable(ton, addr, limit):
- addr = ton.GetDestinationAddr(addr)
- account = ton.GetAccount(addr)
- history = ton.GetAccountHistory(account, limit)
- table = list()
- typeText = color_text("{red}{bold}{endc}")
- table += [["Time", typeText, "Coins", "From/To"]]
- for message in history:
- if message.srcAddr is None:
- continue
- srcAddrFull = f"{message.srcWorkchain}:{message.srcAddr}"
- destAddFull = f"{message.destWorkchain}:{message.destAddr}"
- if srcAddrFull == account.addrFull:
- type = color_text("{red}{bold}>>>{endc}")
- fromto = destAddFull
- else:
- type = color_text("{blue}{bold}<<<{endc}")
- fromto = srcAddrFull
- fromto = ton.AddrFull2AddrB64(fromto)
- #datetime = timestamp2datetime(message.time, "%Y.%m.%d %H:%M:%S")
- datetime = timeago(message.time)
- table += [[datetime, type, message.value, fromto]]
- return table
-#end define
-
-def MoveCoins(ton, args):
- try:
- walletName = args[0]
- destination = args[1]
- amount = args[2]
- flags = args[3:]
- except:
- color_print("{red}Bad args. Usage:{endc} mg ")
- return
- wallet = ton.GetLocalWallet(walletName)
- destination = ton.GetDestinationAddr(destination)
- ton.MoveCoins(wallet, destination, amount, flags=flags)
- color_print("MoveCoins - {green}OK{endc}")
-#end define
-
-def MoveCoinsThroughProxy(ton, args):
- try:
- walletName = args[0]
- destination = args[1]
- amount = args[2]
- except:
- color_print("{red}Bad args. Usage:{endc} mgtp ")
- return
- wallet = ton.GetLocalWallet(walletName)
- destination = ton.GetDestinationAddr(destination)
- ton.MoveCoinsThroughProxy(wallet, destination, amount)
- color_print("MoveCoinsThroughProxy - {green}OK{endc}")
-#end define
-
-def CreatNewBookmark(ton, args):
- try:
- name = args[0]
- addr = args[1]
- except:
- color_print("{red}Bad args. Usage:{endc} nb ")
- return
- if ton.IsAddr(addr):
- type = "account"
- else:
- type = "domain"
- #end if
-
- bookmark = dict()
- bookmark["name"] = name
- bookmark["type"] = type
- bookmark["addr"] = addr
- ton.AddBookmark(bookmark)
- color_print("CreatNewBookmark - {green}OK{endc}")
-#end define
-
-def PrintBookmarksList(ton, args):
- data = ton.GetBookmarks()
- if (data is None or len(data) == 0):
- print("No data")
- return
- table = list()
- table += [["Name", "Type", "Address / Domain", "Balance / Exp. date"]]
- for item in data:
- name = item.get("name")
- type = item.get("type")
- addr = item.get("addr")
- bookmark_data = item.get("data")
- table += [[name, type, addr, bookmark_data]]
- print_table(table)
-#end define
-
-def DeleteBookmark(ton, args):
- try:
- name = args[0]
- type = args[1]
- except:
- color_print("{red}Bad args. Usage:{endc} db ")
- return
- ton.DeleteBookmark(name, type)
- color_print("DeleteBookmark - {green}OK{endc}")
-#end define
-
-# def CreatNewAutoTransferRule(args):
-# try:
-# name = args[0]
-# addr = args[1]
-# except:
-# color_print("{red}Bad args. Usage:{endc} nr ")
-# return
-# rule = dict()
-# rule["name"] = name
-# rule["addr"] = addr
-# ton.AddAutoTransferRule(rule)
-# color_print("CreatNewAutoTransferRule - {green}OK{endc}")
-# #end define
-
-# def PrintAutoTransferRulesList(args):
-# data = ton.GetRules()
-# if (data is None or len(data) == 0):
-# print("No data")
-# return
-# table = list()
-# table += [["Name", "fix me"]]
-# for item in data:
-# table += [[item.get("name"), item.get("fix me")]]
-# print_table(table)
-# #end define
-
-# def DeleteAutoTransferRule(args):
-# print("fix me")
-# #end define
-
-def PrintOffersList(ton, args):
- data = ton.GetOffers()
- if (data is None or len(data) == 0):
- print("No data")
- return
- if "--json" in args:
- text = json.dumps(data, indent=2)
- print(text)
- else:
- table = list()
- table += [["Hash", "Config", "Votes", "W/L", "Approved", "Is passed"]]
- for item in data:
- hash = item.get("hash")
- votedValidators = len(item.get("votedValidators"))
- wins = item.get("wins")
- losses = item.get("losses")
- wl = "{0}/{1}".format(wins, losses)
- approvedPercent = item.get("approvedPercent")
- approvedPercent_text = "{0}%".format(approvedPercent)
- isPassed = item.get("isPassed")
- if "hash" not in args:
- hash = Reduct(hash)
- if isPassed == True:
- isPassed = bcolors.green_text("true")
- if isPassed == False:
- isPassed = bcolors.red_text("false")
- table += [[hash, item.config.id, votedValidators, wl, approvedPercent_text, isPassed]]
- print_table(table)
-#end define
-
-def OfferDiff(ton, args):
- try:
- offerHash = args[0]
- offerHash = offerHash
- except:
- color_print("{red}Bad args. Usage:{endc} od ")
- return
- ton.GetOfferDiff(offerHash)
-#end define
-
-def GetConfig(ton, args):
- try:
- configId = args[0]
- configId = int(configId)
- except:
- color_print("{red}Bad args. Usage:{endc} gc ")
- return
- data = ton.GetConfig(configId)
- text = json.dumps(data, indent=2)
- print(text)
-#end define
-
-def PrintComplaintsList(ton, args):
- past = "past" in args
- data = ton.GetComplaints(past=past)
- if (data is None or len(data) == 0):
- print("No data")
- return
- if "--json" in args:
- text = json.dumps(data, indent=2)
- print(text)
- else:
- table = list()
- table += [["Election id", "ADNL", "Fine (part)", "Votes", "Approved", "Is passed"]]
- for key, item in data.items():
- electionId = item.get("electionId")
- adnl = item.get("adnl")
- suggestedFine = item.get("suggestedFine")
- suggestedFinePart = item.get("suggestedFinePart")
- Fine_text = "{0} ({1})".format(suggestedFine, suggestedFinePart)
- votedValidators = len(item.get("votedValidators"))
- approvedPercent = item.get("approvedPercent")
- approvedPercent_text = "{0}%".format(approvedPercent)
- isPassed = item.get("isPassed")
- if "adnl" not in args:
- adnl = Reduct(adnl)
- if isPassed == True:
- isPassed = bcolors.green_text("true")
- if isPassed == False:
- isPassed = bcolors.red_text("false")
- table += [[electionId, adnl, Fine_text, votedValidators, approvedPercent_text, isPassed]]
- print_table(table)
-#end define
-
-def NewDomain(ton, args):
- try:
- domainName = args[0]
- walletName = args[1]
- adnlAddr = args[2]
- except:
- color_print("{red}Bad args. Usage:{endc} nd ")
- return
- domain = dict()
- domain["name"] = domainName
- domain["adnlAddr"] = adnlAddr
- domain["walletName"] = walletName
- ton.NewDomain(domain)
- color_print("NewDomain - {green}OK{endc}")
-#end define
-
-def PrintDomainsList(ton, args):
- data = ton.GetDomains()
- if (data is None or len(data) == 0):
- print("No data")
- return
- table = list()
- table += [["Domain", "Wallet", "Expiration date", "ADNL address"]]
- for item in data:
- domainName = item.get("name")
- walletName = item.get("walletName")
- endTime = item.get("endTime")
- endTime = timestamp2datetime(endTime, "%d.%m.%Y")
- adnlAddr = item.get("adnlAddr")
- table += [[domainName, walletName, endTime, adnlAddr]]
- print_table(table)
-#end define
-
-def ViewDomainStatus(ton, args):
- try:
- domainName = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} vds ")
- return
- domain = ton.GetDomain(domainName)
- endTime = domain.get("endTime")
- endTime = timestamp2datetime(endTime, "%d.%m.%Y")
- adnlAddr = domain.get("adnlAddr")
- table = list()
- table += [["Domain", "Expiration date", "ADNL address"]]
- table += [[domainName, endTime, adnlAddr]]
- print_table(table)
-#end define
-
-def DeleteDomain(ton, args):
- try:
- domainName = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} dd ")
- return
- ton.DeleteDomain(domainName)
- color_print("DeleteDomain - {green}OK{endc}")
-#end define
-
-def GetDomainFromAuction(ton, args):
- try:
- walletName = args[0]
- addr = args[1]
- except:
- color_print("{red}Bad args. Usage:{endc} gdfa ")
- return
- ton.GetDomainFromAuction(walletName, addr)
- color_print("GetDomainFromAuction - {green}OK{endc}")
-#end define
-
-def PrintElectionEntriesList(ton, args):
- past = "past" in args
- data = ton.GetElectionEntries(past=past)
- if (data is None or len(data) == 0):
- print("No data")
- return
- if "--json" in args:
- text = json.dumps(data, indent=2)
- print(text)
- else:
- table = list()
- table += [["ADNL", "Pubkey", "Wallet", "Stake", "Max-factor"]]
- for key, item in data.items():
- adnl = item.get("adnlAddr")
- pubkey = item.get("pubkey")
- walletAddr = item.get("walletAddr")
- stake = item.get("stake")
- maxFactor = item.get("maxFactor")
- if "adnl" not in args:
- adnl = Reduct(adnl)
- if "pubkey" not in args:
- pubkey = Reduct(pubkey)
- if "wallet" not in args:
- walletAddr = Reduct(walletAddr)
- table += [[adnl, pubkey, walletAddr, stake, maxFactor]]
- print_table(table)
-#end define
-
-def PrintValidatorList(ton, args):
- past = "past" in args
- fast = "fast" in args
- data = ton.GetValidatorsList(past=past, fast=fast)
- if (data is None or len(data) == 0):
- print("No data")
- return
- if "--json" in args:
- text = json.dumps(data, indent=2)
- print(text)
- else:
- table = list()
- table += [["id", "ADNL", "Pubkey", "Wallet", "Efficiency", "Online"]]
- for i, item in enumerate(data):
- adnl = item.get("adnlAddr")
- pubkey = item.get("pubkey")
- walletAddr = item.get("walletAddr")
- efficiency = item.get("efficiency")
- online = item.get("online")
- if "adnl" not in args:
- adnl = Reduct(adnl)
- if "pubkey" not in args:
- pubkey = Reduct(pubkey)
- if "wallet" not in args:
- walletAddr = Reduct(walletAddr)
- if "offline" in args and online != False:
- continue
- if online == True:
- online = bcolors.green_text("true")
- if online == False:
- online = bcolors.red_text("false")
- table += [[str(i), adnl, pubkey, walletAddr, efficiency, online]]
- print_table(table)
-#end define
-
-def Reduct(item):
- item = str(item)
- if item is None:
- result = None
- else:
- end = len(item)
- result = item[0:6] + "..." + item[end-6:end]
- return result
-#end define
-
def GetSettings(ton, args):
try:
name = args[0]
@@ -1382,7 +932,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]
@@ -1434,6 +984,7 @@ def disable_mode(local, ton, args):
local.exit()
#end define
+
def Xrestart(inputArgs):
if len(inputArgs) < 2:
color_print("{red}Bad args. Usage:{endc} xrestart ")
@@ -1473,21 +1024,6 @@ def ImportShardOverlayCert(ton, args):
ton.ImportShardOverlayCert()
#end define
-def GetPoolData(ton, args):
- try:
- pool_name = args[0]
- except:
- color_print("{red}Bad args. Usage:{endc} get_pool_data ")
- return
- if ton.IsAddr(pool_name):
- pool_addr = pool_name
- else:
- pool = ton.GetLocalPool(pool_name)
- pool_addr = pool.addrB64
- pool_data = ton.GetPoolData(pool_addr)
- print(json.dumps(pool_data, indent=4))
-#end define
-
### Start of the program
def mytonctrl():
diff --git a/mytonctrl/resources/translate.json b/mytonctrl/resources/translate.json
index 9ece2f83..70cbccca 100644
--- a/mytonctrl/resources/translate.json
+++ b/mytonctrl/resources/translate.json
@@ -129,26 +129,6 @@
"ru": "Удалить закладку",
"zh_TW": "刪除書籤"
},
- "nd_cmd": {
- "en": "New domain",
- "ru": "Арендовать новый домен",
- "zh_TW": "新建域名"
- },
- "dl_cmd": {
- "en": "Show domain list",
- "ru": "Показать арендованные домены",
- "zh_TW": "顯示域名列表"
- },
- "vds_cmd": {
- "en": "View domain status",
- "ru": "Показать статус домена",
- "zh_TW": "查看域名狀態"
- },
- "dd_cmd": {
- "en": "Delete domain",
- "ru": "Удалить домен",
- "zh_TW": "刪除域名"
- },
"ol_cmd": {
"en": "Show offers list",
"ru": "Показать действующие предложения",
@@ -265,9 +245,14 @@
"zh_TW": "選舉狀態: {0}"
},
"local_status_head": {
- "en": "{cyan}===[ Local validator status ]==={endc}",
- "ru": "{cyan}===[ Статус локального валидатора ]==={endc}",
- "zh_TW": "{cyan}===[ 本地驗證者狀態 ]==={endc}"
+ "en": "{cyan}===[ Node status ]==={endc}",
+ "ru": "{cyan}===[ Статус ноды ]==={endc}",
+ "zh_TW": "{cyan}===[ 节点狀態 ]==={endc}"
+ },
+ "node_ip_address": {
+ "en": "Node IP address: {{bold}}{0}{{endc}}",
+ "ru": "IP адрес Ноды: {{bold}}{0}{{endc}}",
+ "zh_TW": "節點 IP 地址: {{bold}}{0}{{endc}}"
},
"local_status_validator_index": {
"en": "Validator index: {0}",
@@ -334,16 +319,51 @@
"ru": "Статус локального валидатора: {0}, {1}",
"zh_TW": "本地驗證者狀態: {0}, {1}"
},
+ "local_status_validator_initial_sync": {
+ "en": "Local validator initial sync status: {0}",
+ "ru": "Статус начальной синхронизации локального валидатора: {0}",
+ "zh_TW": "本地驗證者初始同步狀態: {0}"
+ },
"local_status_validator_out_of_sync": {
"en": "Local validator out of sync: {0}",
"ru": "Рассинхронизация локального валидатора: {0}",
"zh_TW": "本地驗證者不同步: {0}"
},
+ "local_status_master_out_of_sync": {
+ "en": "Masterchain out of sync: {0}",
+ "ru": "Рассинхронизация Мастерчейна локального валидатора: {0}",
+ "zh_TW": "主鏈不同步: {0}"
+ },
+ "local_status_shard_out_of_sync": {
+ "en": "Shardchain out of sync: {0}",
+ "ru": "Рассинхронизация Шардчейна локального валидатора: {0}",
+ "zh_TW": "分片鏈不同步: {0}"
+ },
"local_status_validator_out_of_ser": {
"en": "Local validator last state serialization: {0}",
"ru": "Серализация стейта локального валидатора была: {0}",
"zh_TW": "本地驗證者最後一次狀態序列化: {0}"
},
+ "active_validator_groups": {
+ "en": "Active validator groups (masterchain,shardchain): {0},{1}",
+ "ru": "Активные группы валидатора (мастерчейн, шардчейн): {0},{1}",
+ "zh_TW": "活动验证器组(主链、碎片链: {0},{1}"
+ },
+ "collated_blocks": {
+ "en": "Collated blocks since validation round started (success/error): {0}/{1}",
+ "ru": "Собранные блоки с начала раунда валидации (успех/ошибка): {0}/{1}",
+ "zh_TW": "自驗證週期開始以來的彙編區塊 (成功/錯誤): {0}/{1}"
+ },
+ "validated_blocks": {
+ "en": "Validated blocks since validation round started (success/error): {0}/{1}",
+ "ru": "Проверенные блоки с начала раунда валидации (успех/ошибка): {0}/{1}",
+ "zh_TW": "自驗證週期開始以來的驗證區塊 (成功/錯誤): {0}/{1}"
+ },
+ "ls_queries": {
+ "en": "Liteserver queries for the past {0} sec (success/error): {1}/{2}",
+ "ru": "Запросы к лайтсерверу за последние {0} сек (успех/ошибка): {1}/{2}",
+ "zh_TW": "過去 {0} 秒的輕量級服務器查詢 (成功/錯誤): {1}/{2}"
+ },
"local_status_db": {
"en": "Local validator database size: {0}, {1}",
"ru": "Размер БД локального валидатора: {0}, {1}",
@@ -430,40 +450,95 @@
"zh_TW": "{red}這個版本已經過時了。請更新至第二版本: `update mytonctrl2`{endc}"
},
"disk_usage_warning": {
- "en": "{red} Disk is almost full, clean the TON database immediately: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
- "ru": "{red} Диск почти заполнен, немедленно очистите базу данных TON: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
- "zh_TW": "{red} 磁盤幾乎滿了,立即清理 TON 數據庫: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}"
+ "en": "{red}Disk is almost full, clean the TON database immediately: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
+ "ru": "{red}Диск почти заполнен, немедленно очистите базу данных TON: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
+ "zh_TW": "{red}磁盤幾乎滿了,立即清理 TON 數據庫: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}"
},
"ton_update_available": {
"en": "{green}TON update available. {red}Please update it with `upgrade` command.{endc}",
"ru": "{green}Доступно обновление TON. {red}Пожалуйста, обновите его с помощью команды `upgrade`.{endc}",
"zh_TW": "{green}TON 有可用更新. {red}請使用 `upgrade` 命令進行更新.{endc}"
},
- "disk_usage_warning": {
- "en": "{red} Disk is almost full, clean the TON database immediately: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
- "ru": "{red} Диск почти заполнен, немедленно очистите базу данных TON: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}",
- "zh_TW": "{red} 磁盤幾乎滿了,立即清理 TON 數據庫: https://docs.ton.org/participate/nodes/node-maintenance-and-security#database-grooming {endc}"
+ "subscribe_tg_channel_warning": {
+ "en": "{red}Make sure you are subscribed to the TON validators channel on Telegram: https://t.me/tonstatus {endc}\nTo disable this warning use command `set subscribe_tg_channel true`",
+ "ru": "{red}Убедитесь, что вы подписаны на канал валидаторов TON в Telegram: https://t.me/tonstatus {endc}\nЧтобы отключить это предупреждение, используйте команду `set subscribe_tg_channel true`",
+ "zh_TW": "{red}確保您已訂閱了 Telegram 上的 TON 驗證者頻道: https://t.me/tonstatus {endc}\n要禁用此警告,請使用命令 `set subscribe_tg_channel true`"
+ },
+ "initial_sync_warning": {
+ "en": "{red}Initial Node sync is not completed. The displayed status is incomplete. {endc}",
+ "ru": "{red}Начальная синхронизация ноды не завершена. Отображаемый статус не полный. {endc}",
+ "zh_TW": "{red}初始節點同步未完成。顯示的狀態不完整。 {endc}"
},
"sync_warning": {
- "en": "{red} Node is out of sync. The displayed status is incomplete. {endc}",
- "ru": "{red} Нода не синхронизирована с сетью. Отображаемый статус не полный. {endc}",
- "zh_TW": "{red} 节点不与网络同步。顯示的狀態不完整。 {endc}"
+ "en": "{red}Node is out of sync. The displayed status is incomplete. {endc}",
+ "ru": "{red}Нода не синхронизирована с сетью. Отображаемый статус не полный. {endc}",
+ "zh_TW": "{red}节点不与网络同步。顯示的狀態不完整。 {endc}"
},
"validator_balance_warning": {
- "en": "{red} Validator wallet balance is low. {endc}",
- "ru": "{red} Баланс кошелька валидатора низкий. {endc}",
- "zh_TW": "{red} 驗證者錢包餘額不足。 {endc}"
+ "en": "{red}Validator wallet balance is low. {endc}",
+ "ru": "{red}Баланс кошелька валидатора низкий. {endc}",
+ "zh_TW": "{red}驗證者錢包餘額不足。 {endc}"
},
"vps_warning": {
- "en": "{red} Validator is running on a VPS. Use a dedicated server for better performance. {endc}",
- "ru": "{red} Валидатор работает на VPS. Используйте выделенный сервер (дедик) для лучшей производительности. {endc}",
- "zh_TW": "{red} 驗證者在 VPS 上運行。使用專用服務器以獲得更好的性能。 {endc}"
+ "en": "{red}Validator is running on a VPS. Use a dedicated server for better performance. {endc}",
+ "ru": "{red}Валидатор работает на VPS. Используйте выделенный сервер (дедик) для лучшей производительности. {endc}",
+ "zh_TW": "{red}驗證者在 VPS 上運行。使用專用服務器以獲得更好的性能。 {endc}"
},
"vport_error": {
"en": "{red}Error - UDP port of the validator is not accessible from the outside.{endc}",
"ru": "{red}Ошибка - UDP порт валидатора недоступен извне.{endc}",
"zh_TW": "{red}錯誤 - 驗證器的 UDP 端口無法從外部訪問.{endc}"
},
+ "slashed_warning": {
+ "en": "{{red}}You were fined by {0} TON for low efficiency in the previous round.{{endc}}",
+ "ru": "{{red}}Вы были оштрафованы на {0} TON за низкую эффективность в предыдущем раунде.{{endc}}",
+ "zh_TW": "{{red}}您因上一輪效率低而被罰款 {0} TON。{{endc}}"
+ },
+ "add_custom_overlay_cmd": {
+ "en": "Add custom overlay",
+ "ru": "Добавить пользовательский оверлей",
+ "zh_TW": "添加自定義覆蓋"
+ },
+ "list_custom_overlays_cmd": {
+ "en": "List participating custom overlays",
+ "ru": "Список участвуемых пользовательских оверлеев",
+ "zh_TW": "列出參與的自定義覆蓋"
+ },
+ "delete_custom_overlay_cmd": {
+ "en": "Delete custom overlay",
+ "ru": "Удалить пользовательский оверлей",
+ "zh_TW": "刪除自定義覆蓋"
+ },
+ "enable_alert_cmd": {
+ "en": "Enable specific Telegram Bot alert",
+ "ru": "Включить определенное оповещение через Telegram Bot",
+ "zh_TW": "啟用特定的 Telegram Bot 警報"
+ },
+ "disable_alert_cmd": {
+ "en": "Disable specific Telegram Bot alert",
+ "ru": "Отключить определенное оповещение через Telegram Bot",
+ "zh_TW": "禁用特定的 Telegram Bot 警報"
+ },
+ "list_alerts_cmd": {
+ "en": "List all available Telegram Bot alerts",
+ "ru": "Список всех доступных оповещений через Telegram Bot",
+ "zh_TW": "列出所有可用的 Telegram Bot 警報"
+ },
+ "test_alert_cmd": {
+ "en": "Send test alert via Telegram Bot",
+ "ru": "Отправить тестовое оповещение через Telegram Bot",
+ "zh_TW": "通過 Telegram Bot 發送測試警報"
+ },
+ "setup_alert_bot_cmd": {
+ "en": "Setup Telegram Bot for alerts",
+ "ru": "Настроить Telegram Bot для оповещений",
+ "zh_TW": "設置 Telegram Bot 以接收警報"
+ },
+ "benchmark_cmd": {
+ "en": "Run benchmark",
+ "ru": "Запустить бенчмарк",
+ "zh_TW": "運行基準測試"
+ },
"000a": {
"en": "001",
"ru": "002",
diff --git a/mytonctrl/scripts/cleanup.sh b/mytonctrl/scripts/cleanup.sh
deleted file mode 100644
index d3d10a2e..00000000
--- a/mytonctrl/scripts/cleanup.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-set -e
-
-# Проверить sudo
-if [ "$(id -u)" != "0" ]; then
- echo "Please run script as root"
- exit 1
-fi
-
-# Цвета
-COLOR='\033[92m'
-ENDC='\033[0m'
-
-db_path=/var/ton-work/db
-
-function get_directory_size {
- buff=$(du -sh ${db_path} | awk '{print $1}')
- echo ${buff}
-}
-
-echo -e "${COLOR}[1/7]${ENDC} Start node/validator DB cleanup process"
-echo -e "${COLOR}[2/7]${ENDC} Stop node/validator"
-systemctl stop validator
-
-echo -e "${COLOR}[3/7]${ENDC} Node/validator DB size before cleanup = $(get_directory_size)"
-find /var/ton-work/db -name 'LOG.old*' -exec rm {} +
-
-echo -e "${COLOR}[4/7]${ENDC} Node/validator DB size after deleting old files = $(get_directory_size)"
-rm -r /var/ton-work/db/files/packages/temp.archive.*
-
-echo -e "${COLOR}[5/7]${ENDC} Node/validator DB size after deleting temporary files = $(get_directory_size)"
-echo -e "${COLOR}[6/7]${ENDC} Start node/validator"
-systemctl start validator
-
-echo -e "${COLOR}[7/7]${ENDC} Node/validator DB cleanup process completed"
diff --git a/mytonctrl/scripts/create_backup.sh b/mytonctrl/scripts/create_backup.sh
new file mode 100644
index 00000000..dbcfe5b8
--- /dev/null
+++ b/mytonctrl/scripts/create_backup.sh
@@ -0,0 +1,46 @@
+dest="mytonctrl_backup_$(hostname)_$(date +%s).tar.gz"
+mtc_dir="$HOME/.local/share/mytoncore"
+user=$(logname)
+ton_dir="/var/ton-work"
+keys_dir="/var/ton-work/keys"
+# Get arguments
+while getopts d:m:t:k: flag
+do
+ case "${flag}" in
+ d) dest=${OPTARG};;
+ m) mtc_dir=${OPTARG};;
+ t) ton_dir=${OPTARG};;
+ k) keys_dir=${OPTARG};;
+ *)
+ echo "Flag -${flag} is not recognized. Aborting"
+ exit 1 ;;
+ esac
+done
+
+COLOR='\033[92m'
+ENDC='\033[0m'
+
+tmp_dir="/tmp/mytoncore/backupv2"
+rm -rf $tmp_dir
+mkdir $tmp_dir
+mkdir $tmp_dir/db
+
+cp $ton_dir/db/config.json ${tmp_dir}/db
+cp -r $ton_dir/db/keyring ${tmp_dir}/db
+cp -r $keys_dir ${tmp_dir}
+cp -r $mtc_dir $tmp_dir
+
+python3 -c "import json;f=open('${tmp_dir}/db/config.json');json.load(f);f.close()" || exit 1 # Check if config.json is copied correctly
+python3 -c "import json;f=open('${tmp_dir}/mytoncore/mytoncore.db');json.load(f);f.close()" || exit 2 # Check if mytoncore.db is copied correctly
+
+echo -e "${COLOR}[1/2]${ENDC} Copied files to ${tmp_dir}"
+
+tar -zcf $dest -C $tmp_dir .
+
+chown $user:$user $dest
+
+echo -e "${COLOR}[2/2]${ENDC} Backup successfully created in ${dest}!"
+
+rm -rf $tmp_dir
+
+echo -e "If you wish to use archive package to migrate node to different machine please make sure to stop validator and mytoncore on donor (this) host prior to migration."
diff --git a/mytonctrl/scripts/restore_backup.sh b/mytonctrl/scripts/restore_backup.sh
new file mode 100644
index 00000000..042657d7
--- /dev/null
+++ b/mytonctrl/scripts/restore_backup.sh
@@ -0,0 +1,75 @@
+name="backup.tar.gz"
+mtc_dir="$HOME/.local/share/mytoncore"
+ip=0
+user=$(logname)
+# Get arguments
+while getopts n:m:i:u: flag
+do
+ case "${flag}" in
+ n) name=${OPTARG};;
+ m) mtc_dir=${OPTARG};;
+ i) ip=${OPTARG};;
+ u) user=${OPTARG};;
+ *)
+ echo "Flag -${flag} is not recognized. Aborting"
+ exit 1 ;;
+ esac
+done
+
+if [ ! -f "$name" ]; then
+ echo "Backup file not found, aborting."
+ exit 1
+fi
+
+COLOR='\033[92m'
+ENDC='\033[0m'
+
+systemctl stop validator
+systemctl stop mytoncore
+
+echo -e "${COLOR}[1/4]${ENDC} Stopped validator and mytoncore"
+
+
+tmp_dir="/tmp/mytoncore/backup"
+rm -rf $tmp_dir
+mkdir $tmp_dir
+tar -xvzf $name -C $tmp_dir
+
+if [ ! -d ${tmp_dir}/db ]; then
+ echo "Old version of backup detected"
+ mkdir ${tmp_dir}/db
+ mv ${tmp_dir}/config.json ${tmp_dir}/db
+ mv ${tmp_dir}/keyring ${tmp_dir}/db
+
+fi
+
+rm -rf /var/ton-work/db/keyring
+
+chown -R $user:$user ${tmp_dir}/mytoncore
+chown -R $user:$user ${tmp_dir}/keys
+chown validator:validator ${tmp_dir}/keys
+chown -R validator:validator ${tmp_dir}/db
+
+cp -rfp ${tmp_dir}/db /var/ton-work
+cp -rfp ${tmp_dir}/keys /var/ton-work
+cp -rfpT ${tmp_dir}/mytoncore $mtc_dir
+
+chown -R validator:validator /var/ton-work/db/keyring
+
+echo -e "${COLOR}[2/4]${ENDC} Extracted files from archive"
+
+rm -r /var/ton-work/db/dht-*
+
+if [ $ip -ne 0 ]; then
+ echo "Replacing IP in node config"
+ python3 -c "import json;path='/var/ton-work/db/config.json';f=open(path);d=json.load(f);f.close();d['addrs'][0]['ip']=int($ip);f=open(path, 'w');f.write(json.dumps(d, indent=4));f.close()"
+else
+ echo "IP is not provided, skipping IP replacement"
+fi
+
+echo -e "${COLOR}[3/4]${ENDC} Deleted DHT files"
+
+systemctl start validator
+systemctl start mytoncore
+
+echo -e "${COLOR}[4/4]${ENDC} Started validator and mytoncore"
diff --git a/mytonctrl/scripts/update.sh b/mytonctrl/scripts/update.sh
index 463d422c..c9de406b 100644
--- a/mytonctrl/scripts/update.sh
+++ b/mytonctrl/scripts/update.sh
@@ -12,7 +12,7 @@ author="ton-blockchain"
repo="mytonctrl"
branch="master"
srcdir="/usr/src/"
-bindir="/usr/bin/"
+tmpdir="/tmp/mytonctrl_src/"
# Get arguments
while getopts a:r:b: flag
@@ -28,17 +28,19 @@ done
COLOR='\033[92m'
ENDC='\033[0m'
-# Go to work dir
-cd ${srcdir}
+mkdir -p ${tmpdir}
+cd ${tmpdir}
+rm -rf ${tmpdir}/${repo}
+echo "https://github.com/${author}/${repo}.git -> ${branch}"
+git clone --recursive https://github.com/${author}/${repo}.git || exit 1
-# uninstall previous version
rm -rf ${srcdir}/${repo}
pip3 uninstall -y mytonctrl
# Update code
-echo "https://github.com/${author}/${repo}.git -> ${branch}"
-git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git
-cd ${repo}
+cd ${srcdir}
+cp -rf ${tmpdir}/${repo} ${srcdir}
+cd ${repo} && git checkout ${branch}
pip3 install -U .
systemctl daemon-reload
diff --git a/mytonctrl/scripts/upgrade.sh b/mytonctrl/scripts/upgrade.sh
index 68062226..fe952562 100644
--- a/mytonctrl/scripts/upgrade.sh
+++ b/mytonctrl/scripts/upgrade.sh
@@ -13,6 +13,7 @@ repo="ton"
branch="master"
srcdir="/usr/src/"
bindir="/usr/bin/"
+tmpdir="/tmp/ton_src/"
# Get arguments
while getopts a:r:b: flag
@@ -29,7 +30,7 @@ COLOR='\033[92m'
ENDC='\033[0m'
# Установить дополнительные зависимости
-apt-get install -y libsecp256k1-dev libsodium-dev ninja-build fio rocksdb-tools liblz4-dev libjemalloc-dev
+apt-get install -y libsecp256k1-dev libsodium-dev ninja-build fio rocksdb-tools liblz4-dev libjemalloc-dev automake libtool
# bugfix if the files are in the wrong place
wget "https://ton-blockchain.github.io/global.config.json" -O global.config.json
@@ -59,13 +60,22 @@ else
opensslPath=${bindir}/openssl_3
fi
+rm -rf ${tmpdir}/${repo}
+mkdir -p ${tmpdir}/${repo}
+cd ${tmpdir}/${repo}
+echo "https://github.com/${author}/${repo}.git -> ${branch}"
+git clone --recursive https://github.com/${author}/${repo}.git . || exit 1
+
# Go to work dir
cd ${srcdir}/${repo}
ls -A1 | xargs rm -rf
# Update code
-echo "https://github.com/${author}/${repo}.git -> ${branch}"
-git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git .
+cp -rfT ${tmpdir}/${repo} .
+git checkout ${branch}
+
+git submodule sync --recursive
+git submodule update
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
diff --git a/mytonctrl/utils.py b/mytonctrl/utils.py
index ebb46ca6..49f9ceb5 100644
--- a/mytonctrl/utils.py
+++ b/mytonctrl/utils.py
@@ -17,6 +17,14 @@ def GetItemFromList(data, index):
pass
+def is_hex(s):
+ try:
+ int(s, 16)
+ return True
+ except ValueError:
+ return False
+
+
def fix_git_config(git_path: str):
args = ["git", "status"]
try:
diff --git a/mytoninstaller/config.py b/mytoninstaller/config.py
index 83039853..bf66a127 100644
--- a/mytoninstaller/config.py
+++ b/mytoninstaller/config.py
@@ -43,15 +43,6 @@ def backup_config(local, config_path):
#end define
-def BackupVconfig(local):
- local.add_log("Backup validator config file 'config.json' to 'config.json.backup'", "debug")
- vconfig_path = local.buffer.vconfig_path
- backupPath = vconfig_path + ".backup"
- args = ["cp", vconfig_path, backupPath]
- subprocess.run(args)
-#end define
-
-
def BackupMconfig(local):
local.add_log("Backup mytoncore config file 'mytoncore.db' to 'mytoncore.db.backup'", "debug")
mconfig_path = local.buffer.mconfig_path
diff --git a/mytoninstaller/mytoninstaller.py b/mytoninstaller/mytoninstaller.py
index 160b9210..c820efc9 100644
--- a/mytoninstaller/mytoninstaller.py
+++ b/mytoninstaller/mytoninstaller.py
@@ -24,17 +24,16 @@
EnableLiteServer,
EnableDhtServer,
EnableJsonRpc,
- EnableTonHttpApi,
+ enable_ton_http_api,
DangerousRecoveryValidatorConfigFile,
CreateSymlinks,
enable_ls_proxy,
enable_ton_storage,
enable_ton_storage_provider,
- EnableMode
+ EnableMode, ConfigureFromBackup, ConfigureOnlyNode, SetInitialSync
)
from mytoninstaller.config import (
CreateLocalConfig,
- BackupVconfig,
BackupMconfig,
)
@@ -133,16 +132,18 @@ def Status(local, args):
node_args = get_node_args()
color_print("{cyan}===[ Node arguments ]==={endc}")
for key, value in node_args.items():
- print(f"{key}: {value}")
+ for v in value:
+ print(f"{key}: {v}")
#end define
def set_node_argument(local, args):
if len(args) < 1:
- color_print("{red}Bad args. Usage:{endc} set_node_argument [arg-value] [-d (to delete)]")
+ color_print("{red}Bad args. Usage:{endc} set_node_argument [arg-value] [-d (to delete)].\n"
+ "Examples: 'set_node_argument --archive-ttl 86400' or 'set_node_argument --archive-ttl -d' or 'set_node_argument -M' or 'set_node_argument --add-shard 0:2000000000000000 0:a000000000000000'")
return
arg_name = args[0]
- args = [arg_name, args[1] if len(args) > 1 else ""]
+ args = [arg_name, " ".join(args[1:])]
script_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'set_node_argument.py')
run_as_root(['python3', script_path] + args)
color_print("set_node_argument - {green}OK{endc}")
@@ -194,8 +195,8 @@ def PrintLiteServerConfig(local, args):
def CreateLocalConfigFile(local, args):
initBlock = GetInitBlock()
initBlock_b64 = dict2b64(initBlock)
- user = local.buffer["user"]
- args = ["python3", "-m", "mytoninstaller", "-u", local.buffer.user, "-e", "clc", "-i", initBlock_b64]
+ user = local.buffer.user or os.environ.get("USER", "root")
+ args = ["python3", "-m", "mytoninstaller", "-u", user, "-e", "clc", "-i", initBlock_b64]
run_as_root(args)
#end define
@@ -223,7 +224,7 @@ def Event(local, name):
if name == "enableJR":
EnableJsonRpc(local)
if name == "enableTHA":
- EnableTonHttpApi(local)
+ enable_ton_http_api(local)
if name == "enableLSP":
enable_ls_proxy(local)
if name == "enableTSP":
@@ -259,7 +260,7 @@ def General(local, console):
Refresh(local)
if "-c" in sys.argv:
cx = sys.argv.index("-c")
- args = sys.argv[cx+1:]
+ args = sys.argv[cx+1].split()
Command(local, args, console)
if "-e" in sys.argv:
ex = sys.argv.index("-e")
@@ -277,16 +278,29 @@ def General(local, console):
mx = sys.argv.index("-m")
mode = sys.argv[mx+1]
local.buffer.mode = mode
+ if "--only-mtc" in sys.argv:
+ ox = sys.argv.index("--only-mtc")
+ local.buffer.only_mtc = str2bool(sys.argv[ox+1])
+ if "--only-node" in sys.argv:
+ ox = sys.argv.index("--only-node")
+ local.buffer.only_node = str2bool(sys.argv[ox+1])
+ if "--backup" in sys.argv:
+ bx = sys.argv.index("--backup")
+ backup = sys.argv[bx+1]
+ if backup != "none":
+ local.buffer.backup = backup
#end if
FirstMytoncoreSettings(local)
FirstNodeSettings(local)
EnableValidatorConsole(local)
EnableLiteServer(local)
- BackupVconfig(local)
BackupMconfig(local)
CreateSymlinks(local)
EnableMode(local)
+ ConfigureFromBackup(local)
+ ConfigureOnlyNode(local)
+ SetInitialSync(local)
#end define
diff --git a/mytoninstaller/node_args.py b/mytoninstaller/node_args.py
index ec99eea3..cd8c6e47 100644
--- a/mytoninstaller/node_args.py
+++ b/mytoninstaller/node_args.py
@@ -2,34 +2,31 @@
def get_validator_service():
path = '/etc/systemd/system/validator.service'
- with open(path, 'r') as f:
- return f.read()
+ with open(path, 'r') as file:
+ return file.read()
#end define
def get_node_start_command():
service = get_validator_service()
for line in service.split('\n'):
- if 'ExecStart' in line:
+ if line.startswith('ExecStart'):
return line.split('=')[1].strip()
#end define
+def get_node_args(start_command: str = None):
+ if start_command is None:
+ start_command = get_node_start_command()
+ #end if
-def get_node_args(command: str = None):
- if command is None:
- command = get_node_start_command()
- result = {}
- key = ''
- for c in command.split(' ')[1:]:
- if c.startswith('--') or c.startswith('-'):
- if key:
- result[key] = ''
- key = c
- elif key:
- result[key] = c
- key = ''
- if key:
- result[key] = ''
+ result = dict() # {key: [value1, value2]}
+ node_args = start_command.split(' ')[1:]
+ key = None
+ for item in node_args:
+ if item.startswith('-'):
+ key = item
+ result[key] = list()
+ else:
+ result[key].append(item)
return result
#end define
-
diff --git a/mytoninstaller/scripts/jsonrpcinstaller.sh b/mytoninstaller/scripts/jsonrpcinstaller.sh
index e6338480..926d61e1 100755
--- a/mytoninstaller/scripts/jsonrpcinstaller.sh
+++ b/mytoninstaller/scripts/jsonrpcinstaller.sh
@@ -28,7 +28,7 @@ ENDC='\033[0m'
# Установка компонентов python3
echo -e "${COLOR}[1/4]${ENDC} Installing required packages"
-pip3 install Werkzeug json-rpc cloudscraper pyotp
+pip3 install Werkzeug json-rpc cloudscraper pyotp jsonpickle # todo: set versions
# Клонирование репозиториев с github.com
echo -e "${COLOR}[2/4]${ENDC} Cloning github repository"
diff --git a/mytoninstaller/scripts/set_node_argument.py b/mytoninstaller/scripts/set_node_argument.py
index 19fe67a5..bad88016 100644
--- a/mytoninstaller/scripts/set_node_argument.py
+++ b/mytoninstaller/scripts/set_node_argument.py
@@ -11,20 +11,35 @@ def set_node_arg(arg_name: str, arg_value: str = ''):
"""
assert arg_name.startswith('-'), 'arg_name must start with "-" or "--"'
service = get_validator_service()
- command = get_node_start_command()
- if command.split(' ')[0] != '/usr/bin/ton/validator-engine/validator-engine':
- raise Exception('Invalid node start command in service file')
- if command is None:
+ start_command = get_node_start_command()
+ if start_command is None:
raise Exception('Cannot find node start command in service file')
- args = get_node_args(command)
+ first_arg = start_command.split(' ')[0]
+ if first_arg != '/usr/bin/ton/validator-engine/validator-engine':
+ raise Exception('Invalid node start command in service file')
+ #end if
+
+ node_args = get_node_args(start_command)
if arg_value == '-d':
- args.pop(arg_name, None)
+ node_args.pop(arg_name, None)
else:
- args[arg_name] = arg_value
- new_command = command.split(' ')[0] + ' ' + ' '.join([f'{k} {v}' for k, v in args.items()])
- new_service = service.replace(command, new_command)
- with open('/etc/systemd/system/validator.service', 'w') as f:
- f.write(new_service)
+ if ' ' in arg_value:
+ node_args[arg_name] = arg_value.split()
+ else:
+ node_args[arg_name] = [arg_value]
+ #end if
+
+ buffer = list()
+ buffer.append(first_arg)
+ for key, value_list in node_args.items():
+ if len(value_list) == 0:
+ buffer.append(f"{key}")
+ for value in value_list:
+ buffer.append(f"{key} {value}")
+ new_start_command = ' '.join(buffer)
+ new_service = service.replace(start_command, new_start_command)
+ with open('/etc/systemd/system/validator.service', 'w') as file:
+ file.write(new_service)
restart_node()
#end define
diff --git a/scripts/ton_http_api_installer.sh b/mytoninstaller/scripts/ton_http_api_installer.sh
similarity index 60%
rename from scripts/ton_http_api_installer.sh
rename to mytoninstaller/scripts/ton_http_api_installer.sh
index 9327f870..a5eab69c 100644
--- a/scripts/ton_http_api_installer.sh
+++ b/mytoninstaller/scripts/ton_http_api_installer.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
-# Проверить sudo
+# check sudo
if [ "$(id -u)" != "0" ]; then
echo "Please run script as root"
exit 1
@@ -11,30 +11,36 @@ fi
COLOR='\033[92m'
ENDC='\033[0m'
-# Установка компонентов python3
-pip3 install virtualenv
+# install python3 packages
+pip3 install virtualenv==20.27.1
-# Подготовить папку с виртуальным окружением
+# prepare the virtual environment
echo -e "${COLOR}[1/4]${ENDC} Preparing the virtual environment"
venv_path="/opt/virtualenv/ton_http_api"
virtualenv ${venv_path}
-# Установка компонентов python3
+# install python3 packages
echo -e "${COLOR}[2/4]${ENDC} Installing required packages"
user=$(logname)
venv_pip3="${venv_path}/bin/pip3"
${venv_pip3} install ton-http-api
chown -R ${user}:${user} ${venv_path}
-# Прописать автозагрузку
+# add to startup
echo -e "${COLOR}[3/4]${ENDC} Add to startup"
venv_ton_http_api="${venv_path}/bin/ton-http-api"
tonlib_path="/usr/bin/ton/tonlib/libtonlibjson.so"
-ls_config="/usr/bin/ton/localhost.config.json"
-cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import add2systemd; add2systemd(name='ton_http_api', user='${user}', start='${venv_ton_http_api} --host 127.0.0.1 --port 8801 --liteserver-config ${ls_config} --cdll-path ${tonlib_path} --tonlib-keystore /tmp/tonlib_keystore/')"
+ls_config="/usr/bin/ton/local.config.json"
+cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import add2systemd; add2systemd(name='ton_http_api', user='${user}', start='${venv_ton_http_api} --logs-level=INFO --host 127.0.0.1 --port 8801 --liteserver-config ${ls_config} --cdll-path ${tonlib_path} --tonlib-keystore /tmp/tonlib_keystore/')"
python3 -c "${cmd}"
+systemctl daemon-reload
systemctl restart ton_http_api
-# Конец
+# check connection
+echo -e "Requesting masterchain info from local ton http api"
+sleep 5
+curl http://127.0.0.1:8801/getMasterchainInfo
+
+# end
echo -e "${COLOR}[4/4]${ENDC} ton_http_api service installation complete"
exit 0
diff --git a/mytoninstaller/scripts/tonhttpapiinstaller.sh b/mytoninstaller/scripts/tonhttpapiinstaller.sh
deleted file mode 100755
index c22b98e0..00000000
--- a/mytoninstaller/scripts/tonhttpapiinstaller.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-set -e
-
-# Проверить sudo
-if [ "$(id -u)" != "0" ]; then
- echo "Please run script as root"
- exit 1
-fi
-
-# Get arguments
-while getopts u: flag
-do
- case "${flag}" in
- u) user=${OPTARG};;
- esac
-done
-
-# Цвета
-COLOR='\033[92m'
-ENDC='\033[0m'
-
-# Установка компонентов python3
-echo -e "${COLOR}[1/3]${ENDC} Installing required packages"
-pip3 install -U ton-http-api
-
-# Установка модуля
-echo -e "${COLOR}[2/3]${ENDC} Add to startup"
-mkdir -p /var/ton-http-api/ton_keystore/
-chown -R $user /var/ton-http-api/
-
-SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
-cmd="ton-http-api --port=8000 --logs-level=INFO --cdll-path=/usr/bin/ton/tonlib/libtonlibjson.so --liteserver-config /usr/bin/ton/local.config.json --tonlib-keystore=/var/ton-http-api/ton_keystore/ --parallel-requests-per-liteserver=1024"
-${SCRIPT_DIR}/add2systemd.sh -n ton-http-api -s "${cmd}" -u ${user} -g ${user}
-systemctl restart ton-http-api
-
-# Конец
-echo -e "${COLOR}[3/3]${ENDC} TonHttpApi installation complete"
-exit 0
diff --git a/mytoninstaller/settings.py b/mytoninstaller/settings.py
index d04c5673..4f798120 100644
--- a/mytoninstaller/settings.py
+++ b/mytoninstaller/settings.py
@@ -9,12 +9,12 @@
import pkg_resources
from mypylib.mypylib import (
- add2systemd,
- get_dir_from_path,
- run_as_root,
- color_print,
+ add2systemd,
+ get_dir_from_path,
+ run_as_root,
+ color_print,
ip2int,
- Dict
+ Dict, int2ip
)
from mytoninstaller.utils import StartValidator, StartMytoncore, start_service, stop_service, get_ed25519_pubkey
from mytoninstaller.config import SetConfig, GetConfig, get_own_ip, backup_config
@@ -22,6 +22,9 @@
def FirstNodeSettings(local):
+ if local.buffer.only_mtc:
+ return
+
local.add_log("start FirstNodeSettings fuction", "debug")
# Создать переменные
@@ -34,7 +37,11 @@ def FirstNodeSettings(local):
validatorAppPath = local.buffer.validator_app_path
globalConfigPath = local.buffer.global_config_path
vconfig_path = local.buffer.vconfig_path
- archive_ttl = 2592000 if local.buffer.mode == 'liteserver' else 86400
+
+ if os.getenv('ARCHIVE_TTL'):
+ archive_ttl = int(os.getenv('ARCHIVE_TTL'))
+ else:
+ archive_ttl = 2592000 if local.buffer.mode == 'liteserver' else 86400
# Проверить конфигурацию
if os.path.isfile(vconfig_path):
@@ -59,7 +66,14 @@ def FirstNodeSettings(local):
# Прописать автозагрузку
cpus = psutil.cpu_count() - 1
cmd = f"{validatorAppPath} --threads {cpus} --daemonize --global-config {globalConfigPath} --db {ton_db_dir} --logname {tonLogPath} --archive-ttl {archive_ttl} --verbosity 1"
- add2systemd(name="validator", user=vuser, start=cmd) # post="/usr/bin/python3 /usr/src/mytonctrl/mytoncore.py -e \"validator down\""
+
+ if os.getenv('ADD_SHARD'):
+ add_shard = os.getenv('ADD_SHARD')
+ cmd += f' -M'
+ for shard in add_shard.split():
+ cmd += f' --add-shard {shard}'
+
+ add2systemd(name="validator", user=vuser, start=cmd, pre='/bin/sleep 2') # post="/usr/bin/python3 /usr/src/mytonctrl/mytoncore.py -e \"validator down\""
# Получить внешний ip адрес
ip = get_own_ip()
@@ -84,33 +98,52 @@ def FirstNodeSettings(local):
StartValidator(local)
#end define
+def is_testnet(local):
+ testnet_zero_state_root_hash = "gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg="
+ with open(local.buffer.global_config_path) as f:
+ config = json.load(f)
+ if config['validator']['zero_state']['root_hash'] == testnet_zero_state_root_hash:
+ return True
+ return False
def DownloadDump(local):
- dump = local.buffer.dump
- if dump == False:
- return
- #end if
-
- local.add_log("start DownloadDump fuction", "debug")
- url = "https://dump.ton.org"
- dumpSize = requests.get(url + "/dumps/latest.tar.size.archive.txt").text
- print("dumpSize:", dumpSize)
- needSpace = int(dumpSize) * 3
- diskSpace = psutil.disk_usage("/var")
- if needSpace > diskSpace.free:
- return
- #end if
-
- # apt install
- cmd = "apt install plzip pv curl -y"
- os.system(cmd)
-
- # download dump
- cmd = "curl -s {url}/dumps/latest.tar.lz | pv | plzip -d -n8 | tar -xC /var/ton-work/db".format(url=url)
- os.system(cmd)
+ dump = local.buffer.dump
+ if dump is False:
+ return
+ #end if
+
+ local.add_log("start DownloadDump function", "debug")
+ url = "https://dump.ton.org/dumps/latest"
+ if is_testnet(local):
+ url += '_testnet'
+ dumpSize = requests.get(url + ".tar.size.archive.txt").text
+ print("dumpSize:", dumpSize)
+ needSpace = int(dumpSize) * 3
+ diskSpace = psutil.disk_usage("/var")
+ if needSpace > diskSpace.free:
+ return
+ #end if
+
+ # apt install
+ cmd = "apt install plzip pv aria2 curl -y"
+ os.system(cmd)
+
+ # download dump using aria2c to a temporary file
+ temp_file = "/tmp/latest.tar.lz"
+ cmd = f"aria2c -x 8 -s 8 -c {url}.tar.lz -d / -o {temp_file}"
+ os.system(cmd)
+
+ # process the downloaded file
+ cmd = f"pv {temp_file} | plzip -d -n8 | tar -xC /var/ton-work/db"
+ os.system(cmd)
+
+ # clean up the temporary file after processing
+ if os.path.exists(temp_file):
+ os.remove(temp_file)
+ local.add_log(f"Temporary file {temp_file} removed", "debug")
+ #end if
#end define
-
def FirstMytoncoreSettings(local):
local.add_log("start FirstMytoncoreSettings fuction", "debug")
user = local.buffer.user
@@ -185,6 +218,8 @@ def FirstMytoncoreSettings(local):
#end define
def EnableValidatorConsole(local):
+ if local.buffer.only_mtc:
+ return
local.add_log("start EnableValidatorConsole function", "debug")
# Create variables
@@ -287,6 +322,9 @@ def EnableValidatorConsole(local):
#end define
def EnableLiteServer(local):
+ if local.buffer.only_mtc:
+ return
+
local.add_log("start EnableLiteServer function", "debug")
# Create variables
@@ -447,16 +485,36 @@ def EnableJsonRpc(local):
color_print(text)
#end define
-def EnableTonHttpApi(local):
- local.add_log("start EnablePytonv3 function", "debug")
- user = local.buffer.user
+def tha_exists():
+ try:
+ resp = requests.get('http://127.0.0.1:8801/healthcheck', timeout=3)
+ except:
+ return False
+ if resp.status_code == 200 and resp.text == '"OK"':
+ return True
+ return False
+#end define
- ton_http_api_installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'tonhttpapiinstaller.sh')
- exit_code = run_as_root(["bash", ton_http_api_installer_path, "-u", user])
+def enable_ton_http_api(local):
+ try:
+ if not tha_exists():
+ do_enable_ton_http_api(local)
+ except Exception as e:
+ local.add_log(f"Error in enable_ton_http_api: {e}", "warning")
+ pass
+#end define
+
+def do_enable_ton_http_api(local):
+ local.add_log("start do_enable_ton_http_api function", "debug")
+ if not os.path.exists('/usr/bin/ton/local.config.json'):
+ from mytoninstaller.mytoninstaller import CreateLocalConfigFile
+ CreateLocalConfigFile(local, [])
+ ton_http_api_installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'ton_http_api_installer.sh')
+ exit_code = run_as_root(["bash", ton_http_api_installer_path])
if exit_code == 0:
- text = "EnableTonHttpApi - {green}OK{endc}"
+ text = "do_enable_ton_http_api - {green}OK{endc}"
else:
- text = "EnableTonHttpApi - {red}Error{endc}"
+ text = "do_enable_ton_http_api - {red}Error{endc}"
color_print(text)
#end define
@@ -892,9 +950,80 @@ def CreateSymlinks(local):
def EnableMode(local):
args = ["python3", "-m", "mytoncore", "-e"]
- if local.buffer.mode == 'liteserver':
- args.append("enable_liteserver_mode")
+ if local.buffer.mode and local.buffer.mode != "none":
+ args.append("enable_mode_" + local.buffer.mode)
else:
return
args = ["su", "-l", local.buffer.user, "-c", ' '.join(args)]
subprocess.run(args)
+
+
+def set_external_ip(local, ip):
+ mconfig_path = local.buffer.mconfig_path
+
+ mconfig = GetConfig(path=mconfig_path)
+
+ mconfig.liteClient.liteServer.ip = ip
+ mconfig.validatorConsole.addr = f'{ip}:{mconfig.validatorConsole.addr.split(":")[1]}'
+
+ # write mconfig
+ local.add_log("write mconfig", "debug")
+ SetConfig(path=mconfig_path, data=mconfig)
+
+
+def ConfigureFromBackup(local):
+ if not local.buffer.backup:
+ return
+ from modules.backups import BackupModule
+ mconfig_path = local.buffer.mconfig_path
+ mconfig_dir = get_dir_from_path(mconfig_path)
+ local.add_log("start ConfigureFromBackup function", "info")
+ backup_file = local.buffer.backup
+
+ os.makedirs(local.buffer.ton_work_dir, exist_ok=True)
+ if not local.buffer.only_mtc:
+ ip = str(ip2int(get_own_ip()))
+ BackupModule.run_restore_backup(["-m", mconfig_dir, "-n", backup_file, "-i", ip])
+
+ if local.buffer.only_mtc:
+ BackupModule.run_restore_backup(["-m", mconfig_dir, "-n", backup_file])
+ local.add_log("Installing only mtc", "info")
+ vconfig_path = local.buffer.vconfig_path
+ vconfig = GetConfig(path=vconfig_path)
+ try:
+ node_ip = int2ip(vconfig['addrs'][0]['ip'])
+ except:
+ local.add_log("Can't get ip from validator", "error")
+ return
+ set_external_ip(local, node_ip)
+
+
+def ConfigureOnlyNode(local):
+ if not local.buffer.only_node:
+ return
+ from modules.backups import BackupModule
+ mconfig_path = local.buffer.mconfig_path
+ mconfig_dir = get_dir_from_path(mconfig_path)
+ local.add_log("start ConfigureOnlyNode function", "info")
+
+ process = BackupModule.run_create_backup(["-m", mconfig_dir, ])
+ if process.returncode != 0:
+ local.add_log("Backup creation failed", "error")
+ return
+ local.add_log("Backup successfully created. Use this file on the controller server with `--only-mtc` flag on installation.", "info")
+
+ mconfig = GetConfig(path=mconfig_path)
+ mconfig.onlyNode = True
+ SetConfig(path=mconfig_path, data=mconfig)
+
+ start_service(local, 'mytoncore')
+
+
+def SetInitialSync(local):
+ mconfig_path = local.buffer.mconfig_path
+
+ mconfig = GetConfig(path=mconfig_path)
+ mconfig.initialSync = True
+ SetConfig(path=mconfig_path, data=mconfig)
+
+ start_service(local, 'mytoncore')
diff --git a/requirements.txt b/requirements.txt
index f7705c47..01f7a310 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,5 @@
-crc16
-requests
-psutil
-fastcrc
-jsonpickle
-pynacl
+crc16==0.1.1
+requests==2.32.3
+psutil==6.1.0
+fastcrc==0.3.2
+pynacl==1.5.0
diff --git a/scripts/install.py b/scripts/install.py
new file mode 100644
index 00000000..bfb569b4
--- /dev/null
+++ b/scripts/install.py
@@ -0,0 +1,101 @@
+import os
+import subprocess
+import inquirer
+
+
+def run_cli():
+ questions = [
+ inquirer.List(
+ "mode",
+ message="Select installation mode (More on https://docs.ton.org/participate/nodes/node-types)",
+ choices=["validator", "liteserver"],
+ ),
+ inquirer.List(
+ "network",
+ message="Select network",
+ choices=["Mainnet", "Testnet", "Other"],
+ ),
+ inquirer.Text(
+ "config",
+ message="Provide network config uri",
+ ignore=lambda x: x["network"] != "Other", # do not ask this question if network is not 'Other'
+ validate=lambda _, x: x.startswith("http"),
+ ),
+ inquirer.Text(
+ "archive-ttl",
+ message="Send the number of seconds to keep the block data in the node database. Default is 2592000 (30 days)",
+ ignore=lambda x: x["mode"] != "liteserver", # do not ask this question if mode is not liteserver
+ validate=lambda _, x: not x or x.isdigit(), # must be empty string or a number
+ # default=2592000
+ ),
+ inquirer.List(
+ "validator-mode",
+ message="Select mode for validator usage. You can skip and set up this later",
+ ignore=lambda x: x["mode"] != "validator", # do not ask this question if mode is not validator
+ choices=["Validator wallet", "Nominator pool", "Single pool", "Liquid Staking", "Skip"],
+ ),
+ inquirer.Confirm(
+ "dump",
+ message="Do you want to download blockchain's dump? "
+ "This reduces synchronization time but requires to download a large file",
+ ),
+ inquirer.Text(
+ "add-shard",
+ message="Set shards node will sync. Skip to sync all shards. "
+ "Format: :. Divide multiple shards with space. "
+ "Example: `0:2000000000000000 0:6000000000000000`",
+ validate=lambda _, x: not x or all([":" in i for i in x.split()])
+ )
+ ]
+
+ answers = inquirer.prompt(questions)
+
+ return answers
+
+
+def parse_args(answers: dict):
+ mode = answers["mode"]
+ network = answers["network"].lower()
+ config = answers["config"]
+ archive_ttl = answers["archive-ttl"]
+ add_shard = answers["add-shard"]
+ validator_mode = answers["validator-mode"]
+ dump = answers["dump"]
+
+ res = f' -n {network}'
+
+ if network not in ('mainnet', 'testnet'):
+ res += f' -c {config}'
+
+ if archive_ttl:
+ os.putenv('ARCHIVE_TTL', archive_ttl) # set env variable
+ if add_shard:
+ os.putenv('ADD_SHARD', add_shard)
+
+ if validator_mode and validator_mode not in ('Skip', 'Validator wallet'):
+ if validator_mode == 'Nominator pool':
+ validator_mode = 'nominator-pool'
+ elif validator_mode == 'Single pool':
+ validator_mode = 'single-nominator'
+ elif validator_mode == 'Liquid Staking':
+ validator_mode = 'liquid-staking'
+ res += f' -m {validator_mode}'
+ else:
+ res += f' -m {mode}'
+
+ if dump:
+ res += ' -d'
+
+ return res
+
+
+def main():
+ answers = run_cli()
+ command = parse_args(answers)
+ # subprocess.run('bash scripts/install.sh ' + command, shell=True)
+ print('bash install.sh ' + command)
+ subprocess.run(['bash', 'install.sh'] + command.split())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/install.sh b/scripts/install.sh
index cfe84c76..1b483411 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -15,19 +15,27 @@ fi
author="ton-blockchain"
repo="mytonctrl"
branch="master"
-mode="validator"
+network="mainnet"
+ton_node_version="master" # Default version
+
show_help_and_exit() {
- echo 'Supported argumets:'
- echo ' -c PATH Provide custom config for toninstaller.sh'
- echo ' -t Disable telemetry'
- echo ' -i Ignore minimum reqiurements'
- echo ' -d Use pre-packaged dump. Reduces duration of initial synchronization.'
- echo ' -a Set MyTonCtrl git repo author'
- echo ' -r Set MyTonCtrl git repo'
- echo ' -b Set MyTonCtrl git repo branch'
- echo ' -m MODE Install MyTonCtrl with specified mode (validator or liteserver)'
- echo ' -h Show this help'
+ echo 'Supported arguments:'
+ echo ' -c PATH Provide custom config for toninstaller.sh'
+ echo ' -t Disable telemetry'
+ echo ' -i Ignore minimum requirements'
+ echo ' -d Use pre-packaged dump. Reduces duration of initial synchronization.'
+ echo ' -a Set MyTonCtrl git repo author'
+ echo ' -r Set MyTonCtrl git repo'
+ echo ' -b Set MyTonCtrl git repo branch'
+ echo ' -m MODE Install MyTonCtrl with specified mode (validator or liteserver)'
+ echo ' -n NETWORK Specify the network (mainnet or testnet)'
+ echo ' -v VERSION Specify the ton node version (commit, branch, or tag)'
+ echo ' -u USER Specify the user to be used for MyTonCtrl installation'
+ echo ' -p PATH Provide backup file for MyTonCtrl installation'
+ echo ' -o Install only MyTonCtrl. Must be used with -p'
+ echo ' -l Install only TON node'
+ echo ' -h Show this help'
exit
}
@@ -40,26 +48,58 @@ config="https://ton-blockchain.github.io/global.config.json"
telemetry=true
ignore=false
dump=false
+only_mtc=false
+only_node=false
+backup=none
+mode=none
+cpu_required=16
+mem_required=64000000 # 64GB in KB
+
+while getopts ":c:tidola:r:b:m:n:v:u:p:h" flag; do
+ case "${flag}" in
+ c) config=${OPTARG};;
+ t) telemetry=false;;
+ i) ignore=true;;
+ d) dump=true;;
+ a) author=${OPTARG};;
+ r) repo=${OPTARG};;
+ b) branch=${OPTARG};;
+ m) mode=${OPTARG};;
+ n) network=${OPTARG};;
+ v) ton_node_version=${OPTARG};;
+ u) user=${OPTARG};;
+ o) only_mtc=true;;
+ l) only_node=true;;
+ p) backup=${OPTARG};;
+ h) show_help_and_exit;;
+ *)
+ echo "Flag -${flag} is not recognized. Aborting"
+ exit 1 ;;
+ esac
+done
+if [ "$only_mtc" = true ] && [ "$backup" = "none" ]; then
+ echo "Backup file must be provided if only mtc installation"
+ exit 1
+fi
+
-while getopts c:tida:r:b:m: flag
-do
- case "${flag}" in
- c) config=${OPTARG};;
- t) telemetry=false;;
- i) ignore=true;;
- d) dump=true;;
- a) author=${OPTARG};;
- r) repo=${OPTARG};;
- b) branch=${OPTARG};;
- m) mode=${OPTARG};;
- h) show_help_and_exit;;
- *)
- echo "Flag -${flag} is not recognized. Aborting"
- exit 1 ;;
- esac
-done
+if [ "${mode}" = "none" ] && [ "$backup" = "none" ]; then # no mode or backup was provided
+ echo "Running cli installer"
+ wget https://raw.githubusercontent.com/${author}/${repo}/${branch}/scripts/install.py
+ python3 -m pip install --upgrade pip
+ pip3 install inquirer==3.4.0 --break-system-packages
+ python3 install.py
+ exit
+fi
+
+# Set config based on network argument
+if [ "${network}" = "testnet" ]; then
+ config="https://ton-blockchain.github.io/testnet-global.config.json"
+ cpu_required=8
+ mem_required=16000000 # 16GB in KB
+fi
# check machine configuration
echo -e "${COLOR}[1/5]${ENDC} Checking system requirements"
@@ -68,9 +108,9 @@ cpus=$(lscpu | grep "CPU(s)" | head -n 1 | awk '{print $2}')
memory=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
echo "This machine has ${cpus} CPUs and ${memory}KB of Memory"
-if [ "$ignore" = false ] && ([ "${cpus}" -lt 16 ] || [ "${memory}" -lt 64000000 ]); then
- echo "Insufficient resources. Requires a minimum of 16 processors and 64Gb RAM."
- exit 1
+if [ "$ignore" = false ] && ([ "${cpus}" -lt "${cpu_required}" ] || [ "${memory}" -lt "${mem_required}" ]); then
+ echo "Insufficient resources. Requires a minimum of "${cpu_required}" processors and "${mem_required}" RAM."
+ exit 1
fi
echo -e "${COLOR}[2/5]${ENDC} Checking for required TON components"
@@ -79,20 +119,30 @@ BIN_DIR=/usr/bin
# create dirs for OSX
if [[ "$OSTYPE" =~ darwin.* ]]; then
- SOURCES_DIR=/usr/local/src
- BIN_DIR=/usr/local/bin
- mkdir -p ${SOURCES_DIR}
+ SOURCES_DIR=/usr/local/src
+ BIN_DIR=/usr/local/bin
+ mkdir -p ${SOURCES_DIR}
fi
+
+if [ ! -f ~/.config/pip/pip.conf ]; then # create pip config
+ mkdir -p ~/.config/pip
+cat > ~/.config/pip/pip.conf < ${version_path}
chown ${user}:${user} ${version_dir} ${version_path}
+# create symbolic link if branch not eq mytonctrl
+if [ "${repo}" != "mytonctrl" ]; then
+ ln -sf ${SOURCES_DIR}/${repo} ${SOURCES_DIR}/mytonctrl
+fi
+
echo -e "${COLOR}[5/5]${ENDC} Mytonctrl installation completed"
exit 0
diff --git a/scripts/ton_installer.sh b/scripts/ton_installer.sh
index 500a92f3..5dc962cf 100644
--- a/scripts/ton_installer.sh
+++ b/scripts/ton_installer.sh
@@ -8,15 +8,20 @@ if [ "$(id -u)" != "0" ]; then
exit 1
fi
-# Get arguments
-config=https://ton-blockchain.github.io/global.config.json
-while getopts c: flag
-do
- case "${flag}" in
- c) config=${OPTARG};;
- esac
+while getopts ":c:v:h" flag; do
+ case "${flag}" in
+ c) config=${OPTARG};;
+ v) ton_node_version=${OPTARG};;
+ h) show_help_and_exit;;
+ *)
+ echo "Flag -${flag} is not recognized. Aborting"
+ exit 1 ;;
+ esac
done
+echo "config: ${config}"
+echo "checkout to ${ton_node_version}"
+
# Цвета
COLOR='\033[95m'
ENDC='\033[0m'
@@ -50,7 +55,7 @@ if [ "$OSTYPE" == "linux-gnu" ]; then
elif [ -f /etc/debian_version ]; then
echo "Ubuntu/Debian Linux detected."
apt-get update
- apt-get install -y build-essential curl git cmake clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip libsecp256k1-dev libsodium-dev liblz4-dev libjemalloc-dev
+ apt-get install -y build-essential curl git cmake clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip libsecp256k1-dev libsodium-dev liblz4-dev libjemalloc-dev automake libtool
# Install ninja
apt-get install -y ninja-build
@@ -71,7 +76,7 @@ elif [[ "$OSTYPE" =~ darwin.* ]]; then
echo "Please, write down your username, because brew package manager cannot be run under root user:"
read LOCAL_USERNAME
-
+
su $LOCAL_USERNAME -c "brew update"
su $LOCAL_USERNAME -c "brew install openssl cmake llvm"
elif [ "$OSTYPE" == "freebsd"* ]; then
@@ -87,7 +92,7 @@ else
fi
# Установка компонентов python3
-pip3 install psutil crc16 requests
+pip3 install psutil==6.1.0 crc16==0.1.1 requests==2.32.3
# build openssl 3.0
echo -e "${COLOR}[2/6]${ENDC} Building OpenSSL 3.0"
@@ -104,6 +109,20 @@ echo -e "${COLOR}[3/6]${ENDC} Preparing for compilation"
cd $SOURCES_DIR
rm -rf $SOURCES_DIR/ton
git clone --recursive https://github.com/ton-blockchain/ton.git
+
+echo "checkout to ${ton_node_version}"
+
+if [ "${ton_node_version}" != "master" ]; then
+ cd $SOURCES_DIR/ton
+ git checkout ${ton_node_version}
+ cd ../
+fi
+
+cd $SOURCES_DIR/ton
+git submodule sync --recursive
+git submodule update
+cd ../
+
git config --global --add safe.directory $SOURCES_DIR/ton
# Подготавливаем папки для компиляции
diff --git a/tools/extract_backup_node_keys.sh b/tools/extract_backup_node_keys.sh
new file mode 100644
index 00000000..adc4448c
--- /dev/null
+++ b/tools/extract_backup_node_keys.sh
@@ -0,0 +1,48 @@
+name="backup.tar.gz"
+dest="cleared_backup_$(hostname)_$(date +%s).tar.gz"
+ton_db=""
+tmp_dir="tmp/backup"
+user=$(logname)
+
+
+# Get arguments
+while getopts n:d:t: flag
+do
+ case "${flag}" in
+ n) name=${OPTARG};;
+ d) dest=${OPTARG};;
+ t) ton_db=${OPTARG};;
+ *)
+ echo "Flag -${flag} is not recognized. Aborting"
+ exit 1 ;;
+ esac
+done
+
+rm -rf $tmp_dir
+mkdir tmp/backup -p
+
+if [ ! -z "$ton_db" ]; then
+ mkdir ${tmp_dir}/db
+ cp -r "$ton_db"/db/keyring ${tmp_dir}/db
+ cp "$ton_db"/db/config.json ${tmp_dir}/db
+else
+ tar -xzf $name -C $tmp_dir
+fi
+
+rm -rf ${tmp_dir}/mytoncore
+rm -rf ${tmp_dir}/keys
+mv ${tmp_dir}/db/keyring ${tmp_dir}/db/old_keyring
+mkdir ${tmp_dir}/db/keyring
+
+keys=$(python3 -c "import json;import base64;f=open('${tmp_dir}/db/config.json');config=json.load(f);f.close();keys=set();[([keys.add(base64.b64decode(key['key']).hex().upper()) for key in v['temp_keys']], [keys.add(base64.b64decode(adnl['id']).hex().upper()) for adnl in v['adnl_addrs']]) for v in config['validators']];print('\n'.join(list(keys)))")
+
+for key in $keys; do
+ mv ${tmp_dir}/db/old_keyring/${key} ${tmp_dir}/db/keyring
+done
+
+rm -rf ${tmp_dir}/db/old_keyring
+
+tar -zcf $dest -C $tmp_dir .
+chown $user:$user $dest
+
+echo -e "Node keys backup successfully created in ${dest}!"
diff --git a/tools/inject_backup_node_keys.py b/tools/inject_backup_node_keys.py
new file mode 100644
index 00000000..9566c788
--- /dev/null
+++ b/tools/inject_backup_node_keys.py
@@ -0,0 +1,49 @@
+import argparse
+import base64
+import json
+import subprocess
+tmp_dir = "tmp/cleared_backup"
+
+
+def b64tohex(b64str: str):
+ return base64.b64decode(b64str).hex().upper()
+
+
+def run_vc(cmd: str):
+ args = ['/usr/bin/ton/validator-engine-console/validator-engine-console', '-k', '/var/ton-work/keys/client', '-p', '/var/ton-work/keys/server.pub', '-a', vc_address, '--cmd', cmd]
+ subprocess.run(args)
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('-n')
+parser.add_argument('-a')
+
+args = parser.parse_args()
+name = args.n
+vc_address = args.a
+
+if not name or not vc_address:
+ print("Usage: inject_backup_node_keys.py -n -a ")
+ exit(1)
+
+
+subprocess.run(f"rm -rf {tmp_dir}", shell=True)
+subprocess.run(f"mkdir -p {tmp_dir}", shell=True)
+
+subprocess.run(f'tar -xzf {name} -C {tmp_dir}', shell=True)
+
+subprocess.run(f'cp -rf {tmp_dir}/db/keyring /var/ton-work/db/', shell=True)
+subprocess.run(f'chown -R validator:validator /var/ton-work/db/keyring', shell=True)
+
+with open(f'{tmp_dir}/db/config.json', 'r') as f:
+ config = json.load(f)
+
+for v in config['validators']:
+ run_vc(f'addpermkey {b64tohex(v["id"])} {v["election_date"]} {v["expire_at"]}')
+ for tkey in v['temp_keys']:
+ run_vc(f'addtempkey {b64tohex(v["id"])} {b64tohex(tkey["key"])} {v["expire_at"]}')
+ for adnl in v['adnl_addrs']:
+ run_vc(f'addadnl {b64tohex(adnl["id"])} 0')
+ run_vc(f'addvalidatoraddr {b64tohex(v["id"])} {b64tohex(adnl["id"])} {v["expire_at"]}')
+
+subprocess.run(f'systemctl restart validator', shell=True)