diff --git a/.gitignore b/.gitignore index 7f752b37..4123b044 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,124 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +#PyCharm +.idea/ +.idea/$CACHE_FILE$ +.idea/.gitignore +.idea/encodings.xml +.idea/inspectionProfiles/ +.idea/misc.xml +.idea/modules.xml +.idea/ton_client.iml +.idea/vcs.xml + *.DS_Store .idea/ __pycache__/ test.py +.vscode/ +venv/ +venv38/ +sandbox/ diff --git a/.gitmodules b/.gitmodules index e6ac5652..fdb48b48 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "mypylib"] path = mypylib - url = https://github.com/igroman787/mypylib.git + url = https://github.com/igroman787/mypylib [submodule "mypyconsole"] path = mypyconsole - url = https://github.com/igroman787/mypyconsole.git + url = https://github.com/igroman787/mypyconsole diff --git a/README.Ru.md b/README.Ru.md deleted file mode 100644 index 91acd3d9..00000000 --- a/README.Ru.md +++ /dev/null @@ -1,119 +0,0 @@ -## Что это -Данная консольная программа является оберткой над `fift`, `lite-client` и `validator-engine-console`. Она была создана для облегчения управления кошельками, доменами и валидатором на операционной системе `Linux`. -![](screens/mytonctrl-status_ru.png) - -## Функционал -- [x] Показать статус сети TON -- [x] Управление локальными кошельками - - [x] Создать локальный кошелек - - [x] Активировать локальный кошелек - - [x] Показать локальные кошельки - - [x] Импортировать кошелек из файла (.pk) - - [x] Сохранить адрес кошелька в файл (.addr) - - [x] Удалить локальный кошелек -- [x] Показать статус аккаунта - - [x] Показать баланс аккаунта - - [x] Показать историю аккаунта - - [x] Показать статус аккаунта из закладок -- [x] Перевод средств на кошелек - - [x] Перевод фиксированной суммы - - [x] Перевод всей суммы (all) - - [x] Перевод всей суммы с диактивацией кошелька (alld) - - [x] Перевод средств на кошелек из закладок - - [x] Перевод средств на кошелек через цепочку самоудаляемых кошельков -- [x] Управление закладками - - [x] Добавить аккаунт в закладки - - [x] Показать закладки - - [x] Удалить закладку -- [x] Управление предложениями - - [x] Показать предложения - - [x] Проголосовать за предложение - - [x] Автоматическое голосование за ранее проголосованные предложения -- [x] Управление доменами - - [x] Арендовать новый домен - - [x] Показать арендованные домены - - [x] Показать статус домена - - [x] Удалить домен - - [ ] Автоматическое продление доменов -- [x] Управление валидатором - - [x] Участвовать в выборах валидатора - - [x] Возвращать ставку + вознаграждение - - [x] Автозапуск валидатора при аварийном завершении (systemd) - - [x] Отправлять статистику валидатора на https://toncenter.com - -## Список проверенных операционных систем -| Operating System | Status | -|-------------------------------|----------------------------| -| Ubuntu 16.04 LTS (Xenial Xerus) | Error: TON compilation error | -| Ubuntu 18.04 LTS (Bionic Beaver) | OK | -| Ubuntu 20.04 LTS (Focal Fossa) | OK | -| Ubuntu 22.04 LTS (Jammy Jellyfish) | OK | -| Debian 8 | Error: Unable to locate package libgsl-dev | -| Debian 9 | Error: TON compilation error | -| Debian 10 | OK | - - -## Описание установочных скриптов -- `toninstaller.sh` - Данный скрипт клонирует исходники `TON` и `mytonctrl` в папки `/usr/src/ton` и `/usr/src/mytonctrl`, компилирует программы из исходников и прописывает их в `/usr/bin/`. -- `mytoninstaller.py` - Данный скрипт производит настройку валидатора, `mytonctrl` и создание ключей для подключения к валидатору. - -## Режимы установки -Есть два режима установки: `lite` и `full`. Оба они **компилируют** и устанавливают компоненты `TON`. Однако `lite` версия не настраивает и не запускает ноду/валидатор. - -## Установка (Ubuntu) -1. Скачайте и выполните скрипт `install.sh` с нужным вам режимом установки (``). В ходе установки у вас будет несколько раз запрошен пароль суперпользователя. -```sh -wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh -sudo bash install.sh -m -``` - -2. Готово. Можете пробовать запустить программу `mytonctrl`. -```sh -mytonctrl -``` - -## Установка (Debian) -1. Скачайте и выполните скрипт `install.sh` с нужным вам режимом установки. В ходе установки у вас будет несколько раз запрошен пароль суперпользователя. -```sh -wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh -su root -c 'bash install.sh -m ' -``` - -2. Готово. Можете пробовать запустить программу `mytonctrl`. -```sh -mytonctrl -``` - -## Телеметрия -По умолчанию `mytonctrl` отправляет статистику валидатора на сервер https://toncenter.com -Это необходимо для выявления аномалий в сети а так же для быстрого реагирования разработчиков. -Для отключения телеметрии при установке воспользуйтесь флагом `-t`: -```sh -sudo bash install.sh -m -t -``` - -Для отключения телеметрии после установки: -```sh -MyTonCtrl> set sendTelemetry false -``` - -## Веб админка -Для возможности управления нодой/валидатором через браузер нужно установить дополнительный модуль: -`mytonctrl` -> `installer` -> `enable JR` - -Далее нужно создать пароль для подключения: -`mytonctrl` -> `installer` -> `setwebpass` - -Готово. Теперь можно идти на сайт https://tonadmin.org и войти используя свои данные. -git: https://github.com/igroman787/mtc-jsonrpc - -## Локальная копия toncenter -Для того что бы поднять на сервере локальную копию https://toncenter.com нужно установить дополнительный модуль: -`mytonctrl` -> `installer` -> `enable PT` - -Готово. Локальная копия toncenter доступна по адресу `http://:8000` -git: https://github.com/igroman787/pytonv3 - -## Полезные ссылки -1. https://github.com/ton-blockchain/mytonctrl/blob/master/docs/ru/manual-ubuntu.md -2. https://ton.org/docs/ diff --git a/README.md b/README.md index a41b61d4..c577d8b4 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ # MyTonCtrl -[Данный текст доступен на русском языке.](README.Ru.md) ## Contents - [What is MyTonCtrl?](#what-is-myttonctrl) +- [MyTonCtrl Documentation](#mytonctrl-documentation) - [Functionality](#functionality) - [List of tested operating systems](#list-of-tested-operating-systems) - [Installation](#installation) @@ -15,7 +15,6 @@ - [Installation modes](#installation-modes) - [Installation for Ubuntu](#installation-for-ubuntu) - [Installation for Debian](#installation-for-debian) -- [MyTonCtrl Documentation](#mytonctrl-documentation) - [Telemetry](#telemetry) - [MyTonCtrl installer mode](#mytonctrl-installer-mode) - [Web admin panel](#web-admin-panel) @@ -24,10 +23,14 @@ # What is MyTonCtrl? -MyTonCtrl is a console application that serves as a convenient wrapper for `fift`, `lite-client`, and `validator-engine-console`. It has been specifically developed to streamline wallet, domain, and validator management tasks on the Linux operating system. +MyTonCtrl is a console application that serves as a convenient wrapper for `fift`, `lite-client`, and `validator-engine-console`. It has been specifically developed for node (validator) management tasks on the Linux operating system. ![MyTonCtrl Status](screens/mytonctrl-status.png) +# MyTonCtrl Documentation + +Mytonctrl's documentation can be found at https://docs.ton.org/participate/run-nodes/mytonctrl. + # Functionality - [x] Show TON network status - [x] Management of local wallets @@ -84,7 +87,10 @@ MyTonCtrl is a console application that serves as a convenient wrapper for `fift - `mytoninstaller.py`: configures the validator and `mytonctrl`; generates validator connection keys. ## Installation modes -There are two installation modes: `lite` and`full`. They both **compile** and install `TON` components. However the `lite` version does not configure or run the node/validator. +There are two installation modes: `liteserver` and `validator`. They both **compile** and install `TON` components and run the node/validator. Use `liteserver` mode if you want to use your node as Liteserver only. +Use `validator` mode if you want to participate in the validator elections (you still can use that node as Liteserver). + +Learn more about node types: https://docs.ton.org/participate/nodes/node-types ## Installation for Ubuntu 1. Download and execute the `install.sh` script in the desired installation mode. During installation the script prompts you for the superuser password several times. @@ -111,17 +117,6 @@ There are two installation modes: `lite` and`full`. They both **compile** and in mytonctrl ``` -# MyTonCtrl Documentation - -This repository contains the following technical documents for MyTonCtrl, categorized by language. Simply click the links below to navigate to the document you're interested in. - -| | FAQ | Import Wallets | Ubuntu Manual | Nominator Pool | -|:-:|:---:|:-------------:|:-------------:|:--------------:| -| **English (EN)** | [Link](./docs/en/FAQ.md) | [Link](./docs/en/import-wallets.md) | [Link](./docs/en/manual-ubuntu.md) | [Link](./docs/en/nominator-pool.md) | -| **Russian (RU)** | [Link](./docs/ru/FAQ.md) | [Link](./docs/ru/import-wallets.md) | [Link](./docs/ru/manual-ubuntu.md) | [Link](./docs/ru/nominator-pool.md) | -| **Traditional Chinese** | [Link](./docs/zh_TW/FAQ.md) | [Link](./docs/zh_TW/import-wallets.md) | [Link](./docs/zh_TW/manual-ubuntu.md) | [Link](./docs/zh_TW/nominator-pool.md) | - - # Telemetry By default, `mytonctrl` sends validator statistics to the https://toncenter.com server. It is necessary to identify network abnormalities, as well as to quickly give feedback to developers. @@ -155,4 +150,4 @@ Ready. A local copy of toncenter is available at `http://:800 git: https://github.com/igroman787/pytonv3 # Useful links -* https://docs.ton.org/ \ No newline at end of file +* https://docs.ton.org/ diff --git a/custom_overlays.py b/custom_overlays.py deleted file mode 100644 index 03cad4fa..00000000 --- a/custom_overlays.py +++ /dev/null @@ -1,191 +0,0 @@ -import base64 -import json -import requests - -from mypylib.mypylib import color_print - - -def hex2base64(h): - b = bytes.fromhex(h) - b64 = base64.b64encode(b) - s = b64.decode("utf-8") - return s - - -def parse_config(name: str, config: dict, vset: list = None): - """ - Converts config to validator-console friendly format - :param name: custom overlay name - :param config: config - :param vset: list of validators adnl addresses, can be None if `@validators` not in config - :return: - """ - result = { - "name": name, - "nodes": [] - } - for k, v in config.items(): - if k == '@validators' and v: - if vset is None: - raise Exception("Validators set is not defined but @validators is in config") - for v_adnl in vset: - result["nodes"].append({ - "adnl_id": hex2base64(v_adnl), - "msg_sender": False, - }) - else: - result["nodes"].append({ - "adnl_id": hex2base64(k), - "msg_sender": v["msg_sender"], - }) - if v["msg_sender"]: - result["nodes"][-1]["msg_sender_priority"] = v["msg_sender_priority"] - return result - - -def add_custom_overlay(args): - from mytonctrl import ton, local - if len(args) != 2: - color_print("{red}Bad args. Usage:{endc} add_custom_overlay ") - return - path = args[1] - with open(path, 'r') as f: - config = json.load(f) - ton.set_custom_overlay(args[0], config) - if '@validators' in config: - print('Dynamic overlay will be added within 1 minute') - else: - result = add_custom_overlay_to_vc(local, ton, parse_config(args[0], config)) - if not result: - print('Failed to add overlay to validator console') - color_print("add_custom_overlay - {red}ERROR{endc}") - return - color_print("add_custom_overlay - {green}OK{endc}") - - -def list_custom_overlays(args): - from mytonctrl import ton - if not ton.get_custom_overlays(): - color_print("{red}No custom overlays{endc}") - return - for k, v in ton.get_custom_overlays().items(): - color_print(f"Custom overlay {{bold}}{k}{{endc}}:") - print(json.dumps(v, indent=4)) - - -def delete_custom_overlay(args): - from mytonctrl import ton - if len(args) != 1: - color_print("{red}Bad args. Usage:{endc} delete_custom_overlay ") - return - if '@validators' in ton.get_custom_overlays().get(args[0], {}): - ton.delete_custom_overlay(args[0]) - print('Dynamic overlay will be deleted within 1 minute') - else: - ton.delete_custom_overlay(args[0]) - result = delete_custom_overlay_from_vc(ton, args[0]) - if not result: - print('Failed to delete overlay from validator console') - color_print("delete_custom_overlay - {red}ERROR{endc}") - return - color_print("delete_custom_overlay - {green}OK{endc}") - - -def check_node_eligible_for_custom_overlay(ton, config: dict): - vconfig = ton.GetValidatorConfig() - my_adnls = vconfig.adnl - node_adnls = [i["adnl_id"] for i in config["nodes"]] - for adnl in my_adnls: - if adnl.id in node_adnls: - return True - return False - - -def delete_custom_overlay_from_vc(ton, name: str): - result = ton.validatorConsole.Run(f"delcustomoverlay {name}") - return 'success' in result - - -def add_custom_overlay_to_vc(local, ton, config: dict): - if not check_node_eligible_for_custom_overlay(ton, config): - local.add_log(f"Node has no adnl address required for custom overlay {config.get('name')}", "debug") - return False - local.add_log(f"Adding custom overlay {config.get('name')}", "debug") - path = ton.tempDir + f'/custom_overlay_{config["name"]}.json' - with open(path, 'w') as f: - json.dump(config, f) - result = ton.validatorConsole.Run(f"addcustomoverlay {path}") - return 'success' in result - - -def custom_overlays(local, ton): - config = get_default_custom_overlay(local, ton) - if config is not None: - ton.set_custom_overlay('default', config) - deploy_custom_overlays(local, ton) - - -def deploy_custom_overlays(local, ton): - result = ton.validatorConsole.Run("showcustomoverlays") - if 'unknown command' in result: - return # node old version - names = [] - for line in result.split('\n'): - if line.startswith('Overlay'): - names.append(line.split(' ')[1].replace('"', '').replace(':', '')) - - config34 = ton.GetConfig34() - current_el_id = config34['startWorkTime'] - current_vset = [i["adnlAddr"] for i in config34['validators']] - - config36 = ton.GetConfig36() - next_el_id = config36['startWorkTime'] if config36['validators'] else 0 - next_vset = [i["adnlAddr"] for i in config36['validators']] - - for name in names: - # check that overlay still exists in mtc db - pure_name = name - suffix = name.split('_')[-1] - if suffix.startswith('elid') and suffix.split('elid')[-1].isdigit(): # probably election id - pure_name = '_'.join(name.split('_')[:-1]) - el_id = int(suffix.split('elid')[-1]) - if el_id not in (current_el_id, next_el_id): - local.add_log(f"Overlay {name} is not in current or next election, deleting", "debug") - delete_custom_overlay_from_vc(ton, name) # delete overlay if election id is not in current or next election - continue - - if pure_name not in ton.get_custom_overlays(): - local.add_log(f"Overlay {name} ({pure_name}) is not in mtc db, deleting", "debug") - delete_custom_overlay_from_vc(ton, name) # delete overlay if it's not in mtc db - - for name, config in ton.get_custom_overlays().items(): - if name in names: - continue - if '@validators' in config: - new_name = name + '_elid' + str(current_el_id) - if new_name not in names: - node_config = parse_config(new_name, config, current_vset) - add_custom_overlay_to_vc(local, ton, node_config) - - if next_el_id != 0: - new_name = name + '_elid' + str(next_el_id) - if new_name not in names: - node_config = parse_config(new_name, config, next_vset) - add_custom_overlay_to_vc(local, ton, node_config) - else: - node_config = parse_config(name, config) - add_custom_overlay_to_vc(local, ton, node_config) - - -def get_default_custom_overlay(local, ton): - if not local.db.get('useDefaultCustomOverlays', True): - return None - network = ton.GetNetworkName() - default_url = 'https://ton-blockchain.github.io/fallback_custom_overlays.json' - url = local.db.get('defaultCustomOverlaysUrl', default_url) - resp = requests.get(url) - if resp.status_code != 200: - local.add_log(f"Failed to get default custom overlays from {url}", "error") - return None - config = resp.json() - return config.get(network) diff --git a/docs/en/FAQ.md b/docs/en/FAQ.md deleted file mode 100644 index d016e3a8..00000000 --- a/docs/en/FAQ.md +++ /dev/null @@ -1,124 +0,0 @@ -Here is the optimized version in English: - -# MyTonCtrl Directory Usage - -MyTonCtrl is a wrapper that stores its files in two places: - -1. `~/.local/share/mytonctrl/` - Long-term files such as logs are stored here. -2. `/tmp/mytonctrl/` - Temporary files are stored here. - -MyTonCtrl also includes another script, mytoncore, which in turn stores files in the following locations: - -1. `~/.local/share/mytoncore/` - Permanent files, the main configuration will be stored here. -2. `/tmp/mytoncore/` - Temporary files, parameters used for elections will be saved here. - -MyTonCtrl downloads the source code for itself and the validator into the following directories: - -1. `/usr/src/mytonctrl/` -2. `/usr/src/ton/` - -MyTonCtrl compiles the validator components into the following directory: - -1. `/usr/bin/ton/` - -MyTonCtrl creates a working directory for the validator here: - -1. `/var/ton/` - ---- - -## If MyTonCtrl was installed as root: - -The configurations will be stored differently: - -1. `/usr/local/bin/mytonctrl/` -2. `/usr/local/bin/mytoncore/` - ---- - -## How to remove MyTonCtrl: - -Run the script as an administrator and remove the compiled TON components: - -```bash -sudo bash /usr/src/mytonctrl/scripts/uninstall.sh -sudo rm -rf /usr/bin/ton -``` - -During this process, ensure you have sufficient permissions to delete or modify these files or directories. - - -# Dealing with Errors and Directory Changes with MyTonCtrl - -If you encounter issues running MyTonCtrl as a different user or wish to change the working directory of the validator, this guide provides some solutions. - -## Running MyTonCtrl as Different User - -Running MyTonCtrl as a different user may trigger the following error: - -``` -Error: expected str, bytes or os.PathLike object, not NoneType -``` - -To resolve this, you should run MyTonCtrl as the user who installed it. - -## Changing Validator Working Directory Pre-installation - -If you wish to change the working directory of the validator prior to installation, there are two ways to do so: - -1. **Fork the project** - You can fork the project and make your changes there. Learn how to fork a project with `man git-fork`. -2. **Create a symbolic link** - You can also create a symbolic link with the following command: - - ```bash - ln -s /opt/ton/var/ton - ``` -This command will create a link `/var/ton` that points to `/opt/ton`. - -## Changing Validator Working Directory Post-installation - -If you want to change the working directory of the validator from `/var/ton/` after installation, perform the following steps: - -1. **Stop services** - You will need to stop the services with these commands: - - ```bash - systemctl stop validator - systemctl stop mytoncore - ``` - -2. **Move validator files** - You then need to move the validator files with this command: - - ```bash - mv /var/ton/* /opt/ton/ - ``` - -3. **Update configuration paths** - Replace the paths in the configuration located at `~/.local/share/mytoncore/mytoncore.db`. - -4. **Note on experience** - There is no prior experience with such a transfer, so consider this when moving forward. - -Remember to make sure you have sufficient permissions to make these changes or run these commands. - -# Understanding Validator Status and Restarting Validator in MyTonCtrl - -This document will help you understand how to confirm if MyTonCtrl has become a full validator and how to restart your validator. - -## Checking Validator Status in MyTonCtrl - -You can confirm if your node has become a full validator by checking the following conditions: - -1. **Validator Desynchronization** - The desynchronization of the local validator should be less than 20. -2. **Validator Index** - The validator index should be greater than -1. - -To view the performance factor of your validator, you can use the `vl` command in MyTonCtrl: - -1. Find your validator in the list by its ADNL address (`adnlAddr`). -2. If the `mr` and `wr` coefficients are close to 1, it means that your validator is working correctly. - -## Restarting Your Validator - -If you need to restart your validator, you can do so by running the following command: - -```bash -systemctl restart validator -``` - -Ensure you have sufficient permissions to execute these commands and make necessary adjustments. Always remember to back up important data before performing operations that could potentially affect your validator. \ No newline at end of file diff --git a/docs/en/import-wallets.md b/docs/en/import-wallets.md deleted file mode 100644 index 508f2655..00000000 --- a/docs/en/import-wallets.md +++ /dev/null @@ -1,36 +0,0 @@ -# Importing Wallets - -MyTonCtrl supports various types of wallet-like contracts, including wallet-v1, wallet-v3, [lockup-wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/universal), and others. Often, it provides a straightforward way to interact with these contracts. - -## Importing Using a Private Key - -If you have access to a private key, you can easily import a wallet. Enter the following command into the console: - -``` -iw -``` - -Here, `` is your private key in base64 format. - -## Importing Using a Mnemonic Phrase - -If you have a mnemonic phrase (a sequence of 24 words like `tattoo during ...`), follow these steps: - -1. Install Node.js. -2. Clone and install [mnemonic2key](https://github.com/ton-blockchain/mnemonic2key): - ``` - git clone https://github.com/ton-blockchain/mnemonic2key.git - cd mnemonic2key - npm install - ``` -3. Run the following command, replacing `word1`, `word2`... with your mnemonic phrase and `address` with the address of your wallet contract: - ``` - node index.js word1 word2 ... word24 [address] - ``` -4. The script will generate `wallet.pk` and `wallet.addr`. Rename them to `imported_wallet.pk` and `imported_wallet.addr`. -5. Copy both files to the `~/.local/share/mytoncore/wallets/` directory. -6. Open the mytonctrl console and list the wallets using the `wl` command. -7. Verify that the wallet has been imported and displays the correct balance. -8. You can now send funds using the `mg` command. Enter `mg` to view the help documentation. - -Remember to replace placeholders (words inside `< >`) with your actual values when running commands. \ No newline at end of file diff --git a/docs/en/manual-ubuntu.md b/docs/en/manual-ubuntu.md deleted file mode 100644 index aee1ae72..00000000 --- a/docs/en/manual-ubuntu.md +++ /dev/null @@ -1,67 +0,0 @@ -# How to Become a Validator with mytonctrl (v0.2, OS Ubuntu) - -Here are the steps to become a validator using mytonctrl. This example is applicable for the Ubuntu Operating System. - -## 1. Install mytonctrl: - -1. Download the installation script. We recommend installing the tool under your local user account, not as Root. In our example, a local user account is used: - - ```sh - wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh - ``` - - ![wget output](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_wget-ls_ru.png) - -2. Run the installation script as an administrator: - - ```sh - sudo bash install.sh -m full - ``` - -## 2. Conduct an Operability Test: - -1. Run **mytonctrl** from the local user account used for installation in step 1: - - ```sh - mytonctrl - ``` - -2. Check the **mytonctrl** statuses, particularly the following: - -* **mytoncore status**: Should be in green. -* **Local validator status**: Should also be in green. -* **Local validator out of sync**: Initially, a large number is displayed. As soon as the newly created validator connects with other validators, the number will be around 250k. As synchronization progresses, this number decreases. When it falls below 20, the validator is synchronized. - - ![status](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/mytonctrl-status.png) - - -## 3. View the List of Available Wallets - -Check out the list of available wallets. For instance, during the installation of **mytonctrl**, the **validator_wallet_001** wallet is created: - -![wallet list](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-wl_ru.png) - -## 4. Send the Required Number of Coins to the Wallet and Activate It - -To determine the minimum amount of coins required to participate in one election round, head to **tonmon.xyz** > **Participant stakes**. - -* Use the `vas` command to display the history of transfers -* Activate the wallet using the `aw` command - - ![account history](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-vas-aw_ru.png) - -## 5. Your Validator is Now Ready - -**mytoncore** will automatically join the elections. It divides the wallet balance into two parts and uses them as a stake to participate in the elections. You can also manually set the stake size: - -`set stake 50000` — this sets the stake size to 50k coins. If the bet is accepted and our node becomes a validator, the bet can only be withdrawn in the second election (according to the rules of the electorate). - -![setting stake](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-set_ru.png) - -You can also command for help anytime. - -![help command](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-help_ru.png) - -To check **mytoncrl** logs, open `~/.local/share/mytoncore/mytoncore.log` for a local user or `/usr/local/bin/mytoncore/mytoncore.log` for Root. - -![logs](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytoncore-log.png) diff --git a/docs/en/nominator-pool.md b/docs/en/nominator-pool.md deleted file mode 100644 index fd80ee6d..00000000 --- a/docs/en/nominator-pool.md +++ /dev/null @@ -1,118 +0,0 @@ -# Nominator Pool - -## Running the Validator in Nominator Pool Mode - -1. Set up the hardware for the validator - you will need 8 vCPUs, 64GB memory, 1TB SSD, a fixed IP address, and 1Gb/s internet speed. - - For maintaining network stability, it's recommended to distribute validator nodes in different geographical locations worldwide rather than concentrating them in a single data center. You can use [this site](https://status.toncenter.com/) to assess the load of various locations. The map indicates high data center utilization in Europe, especially in Finland, Germany, and Paris. Therefore, using providers such as Hetzner and OVH is not recommended. - - > Ensure your hardware matches or exceeds the specifications above. Running the validator on insufficient hardware negatively impacts the network and could result in penalties. - - > Note that as of May 2021, Hetzner has prohibited mining on its servers, and this ban includes both PoW and PoS algorithms. Even installing a regular node may be considered a violation of their terms of service. - - > **Recommended providers include:** [Amazon](https://aws.amazon.com/), [DigitalOcean](https://www.digitalocean.com/), [Linode](https://www.linode.com/), [Alibaba Cloud](https://alibabacloud.com/), [Latitude](https://www.latitude.sh/). - -2. Install and synchronize **mytonctrl** as described in the guide [here](https://github.com/ton-blockchain/mytonctrl/blob/master/docs/en/manual-ubuntu.md) — follow **only** steps 1, 2, and 3. - - You can also refer to this [Video Instruction](https://ton.org/docs/#/nodes/run-node) for additional help. - -3. Transfer 1 TON to the validator wallet address shown in the `wl` list. - -4. Use the `aw` command to activate your validator wallet. - -5. Create two pools (for even and odd validation rounds): - - ```bash - new_pool p1 0 1 1000 300000 - new_pool p2 0 1 1001 300000 - ``` - - where: - * `p1` is the pool name; - * `0` % is the validator's reward share (e.g., use 40 for 40%); - * `1` is the maximum number of nominators in the pool (should be <= 40); - * `1000` TON is the minimum validator stake (should be >= 1K TON); - * `300000` TON is the minimum nominator stake (should be >= 10K TON); - - > (!) Pool configurations do not have to be identical, you can add 1 to the minimum stake of one pool to make them different. - - > (!) Use https://tonmon.xyz/ to determine the current minimum validator stake. - -6. Type `pools_list` to display pool addresses: - - ```bash - pools_list - Name Status Balance Address - p1 empty 0 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT - p2 empty 0 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL - ``` - -7. Send 1 TON to each pool and activate the pools: - - ```bash - mg validator_wallet_001 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT 1 - mg validator_wallet_001 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL 1 - activate_pool p1 - activate_pool p2 - ``` - -8. Type `pools_list` to display pools: - - ```bash - pools_list - Name Status Balance Address - p1 active 0.731199733 kf98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780v_W - p2 active 0.731199806 kf9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV8UO - ``` - -9. Open each pool via the link "https://tonscan.org/nominator/" and verify pool configurations. - -10. Proceed with the validator deposit to each pool: - - ```bash - deposit_to_pool validator_wallet_001 1005 - deposit_to_pool validator_wallet_001 1005 - ``` - - In these commands, `1005` TON is the deposit amount. Be aware that 1 TON will be deducted by the pool for processing the deposit. - -11. Proceed with the nominator deposit to each pool: - - Visit the pool link (from **Step 9**) and click **ADD STAKE**. - You can also make a deposit using **mytonctrl**, using the following commands: - - ```bash - mg nominator_wallet_001 300001 -C d - mg nominator_wallet_001 300001 -C d - ``` - - > (!) The nominator wallet must be initialized in basechain (workchain 0). - - > (!) Keep in mind that the validator wallet and nominator wallet must be stored separately! The validator wallet should be stored on the server with the validator node to ensure processing of all system transactions. Meanwhile, the nominator wallet should be stored in your cold cryptocurrency wallet. - - > To withdraw a nominator deposit, send a transaction with the comment `w` to the pool address (attach 1 TON to process the transaction). You can also perform this action using **mytonctrl**. - -12. Activate pool mode: - - ```bash - set usePool true - set stake null - ``` - -13. Invite nominators to deposit into your pools. The participation in validation will commence automatically. - - > (!) Ensure that you have at least 200 TON/month in your validator wallet for operation fees. - -## Pool Configuration - -If you're intending to lend to yourself, use `new_pool p1 0 1 1000 300000` (maximum of 1 nominator, 0% validator share). - -If you're creating a pool for numerous nominators, you might use something like this: `new_pool p1 40 40 10000 10000` (maximum of 40 nominators, 40% validator share, minimum participant stakes of 10K TON). - -## Transitioning a Regular Validator to Nominator Pool Mode - -1. Input `set stake 0` to discontinue election participation. - -2. Await the return of both your stakes from the elector. - -3. Proceed with the steps under "Running the Validator in Nominator Pool Mode" from the **4th step** onwards. \ No newline at end of file diff --git a/docs/ru/FAQ.md b/docs/ru/FAQ.md deleted file mode 100644 index e1436e1d..00000000 --- a/docs/ru/FAQ.md +++ /dev/null @@ -1,122 +0,0 @@ -# Использование директорий MyTonCtrl - -MyTonCtrl - это оболочка, которая сохраняет свои файлы в двух местах: - -1. `~/.local/share/mytonctrl/` - Долгосрочные файлы, такие как логи, хранятся здесь. -2. `/tmp/mytonctrl/` - Временные файлы хранятся здесь. - -MyTonCtrl также включает другой скрипт, mytoncore, который в свою очередь хранит файлы в следующих местах: - -1. `~/.local/share/mytoncore/` - Постоянные файлы, главная конфигурация будет храниться здесь. -2. `/tmp/mytoncore/` - Временные файлы, параметры, используемые для выборов, будут сохранены здесь. - -MyTonCtrl загружает исходный код для самого себя и валидатора в следующие директории: - -1. `/usr/src/mytonctrl/` -2. `/usr/src/ton/` - -MyTonCtrl компилирует компоненты валидатора в следующую директорию: - -1. `/usr/bin/ton/` - -MyTonCtrl создает рабочую директорию для валидатора здесь: - -1. `/var/ton/` - ---- - -## Если MyTonCtrl был установлен как root: - -Конфигурации будут храниться иначе: - -1. `/usr/local/bin/mytonctrl/` -2. `/usr/local/bin/mytoncore/` - ---- - -## Как удалить MyTonCtrl: - -Запустите скрипт как администратор и удалите скомпилированные компоненты TON: - -```bash -sudo bash /usr/src/mytonctrl/scripts/uninstall.sh -sudo rm -rf /usr/bin/ton -``` - -Во время этого процесса убедитесь, что у вас есть достаточные права для удаления или изменения этих файлов или директорий. - - -# Обработка ошибок и изменение рабочего каталога с MyTonCtrl - -Если вы столкнулись с проблемами при выполнении MyTonCtrl от другого пользователя или хотите изменить рабочий каталог валидатора, эта инструкция предлагает несколько решений. - -## Запуск MyTonCtrl от другого пользователя - -Запуск MyTonCtrl от другого пользователя может вызвать следующую ошибку: - -``` -Error: expected str, bytes or os.PathLike object, not NoneType -``` - -Для ее решения вы должны запустить MyTonCtrl от пользователя, который его установил. - -## Изменение рабочего каталога валидатора перед установкой - -Если вы хотите изменить рабочий каталог валидатора до установки, есть два способа это сделать: - -1. **Сделайте форк проекта** - Вы можете сделать форк проекта и внести свои изменения туда. Узнайте, как сделать форк проекта с помощью `man git-fork`. -2. **Создайте символическую ссылку** - Вы также можете создать символическую ссылку с помощью следующей команды: - - ```bash - ln -s /opt/ton/var/ton - ``` -Эта команда создаст ссылку `/var/ton`, которая указывает на `/opt/ton`. - -## Изменение рабочего каталога валидатора после установки - -Если вы хотите изменить рабочий каталог валидатора с `/var/ton/` после установки, выполните следующие действия: - -1. **Остановите службы** - Вам потребуется остановить службы с помощью этих команд: - - ```bash - systemctl stop validator - systemctl stop mytoncore - ``` - -2. **Переместите файлы валидатора** - Затем вам нужно переместить файлы валидатора с помощью этой команды: - - ```bash - mv /var/ton/* /opt/ton/ - ``` - -3. **Обновите пути в конфигурации** - Замените пути в конфигурации, расположенной в `~/.local/share/mytoncore/mytoncore.db`. - -4. **Примечание об опыте** - Предыдущего опыта такого переноса не было, поэтому учитывайте это при продвижении вперед. - -Не забудьте убедиться, что у вас достаточно прав для внесения этих изменений или выполнения этих команд. - -# Как понять статус валидатора и перезапустить валидатор в MyTonCtrl - -Этот документ поможет вам понять, как подтвердить, стала ли MyTonCtrl полноценным валидатором, и как перезапустить ваш валидатор. - -## Проверка статуса валидатора в MyTonCtrl - -Вы можете подтвердить, что ваша нода стала полноценным валидатором, проверив следующие условия: - -1. **Десинхронизация валидатора** - Десинхронизация локального валидатора должна быть меньше 20. -2. **Индекс валидатора** - Индекс валидатора должен быть больше -1. - -Чтобы просмотреть коэффициент работы своего валидатора, вы можете использовать команду `vl` в MyTonCtrl: - -1. Найдите свой валидатор в списке по его ADNL-адресу (`adnlAddr`). -2. Если коэффициенты `mr` и `wr` близки к 1, это означает, что ваш валидатор работает правильно. - -## Перезапуск вашего валидатора - -Если вам нужно перезапустить ваш валидатор, вы можете сделать это, выполнив следующую команду: - -```bash -systemctl restart validator -``` - -Убедитесь, что у вас есть достаточные права для выполнения этих команд и сделайте необходимые корректировки. Всегда помните о резервном копировании важных данных перед выполнением операций, которые могут потенциально повлиять на ваш валидатор. \ No newline at end of file diff --git a/docs/ru/import-wallets.md b/docs/ru/import-wallets.md deleted file mode 100644 index 700b7ec9..00000000 --- a/docs/ru/import-wallets.md +++ /dev/null @@ -1,36 +0,0 @@ -# Импорт кошельков - -MyTonCtrl поддерживает различные типы контрактов, похожих на кошелек: wallet-v1, wallet-v3, [lockup-wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/universal) и другие. Часто это самый простой способ работы с контрактом. - -## Импорт через приватный ключ - -Если у вас есть приватный ключ, вы можете легко импортировать кошелек. Введите следующую команду в консоль: - -``` -iw <адрес-кошелька> <секретный-ключ-кошелька> -``` - -Здесь `<секретный-ключ-кошелька>` - это ваш приватный ключ в формате base64. - -## Импорт через мнемоническую фразу - -Если у вас есть мнемоническая фраза (последовательность из 24 слов, например, `tattoo during ...`), следуйте этим шагам: - -1. Установите Node.js. -2. Клонируйте и установите [mnemonic2key](https://github.com/ton-blockchain/mnemonic2key): - ``` - git clone https://github.com/ton-blockchain/mnemonic2key.git - cd mnemonic2key - npm install - ``` -3. Запустите следующую команду, замените `word1`, `word2`... на вашу мнемоническую фразу и `address` на адрес вашего контракта кошелька: - ``` - node index.js word1 word2 ... word24 [address] - ``` -4. Скрипт сгенерирует `wallet.pk` и `wallet.addr`. Переименуйте их в `imported_wallet.pk` и `imported_wallet.addr`. -5. Скопируйте оба файла в каталог `~/.local/share/mytoncore/wallets/`. -6. Откройте консоль mytonctrl и перечислите кошельки с помощью команды `wl`. -7. Убедитесь, что кошелек был импортирован и отображает правильный баланс. -8. Теперь вы можете отправить средства с помощью команды `mg`. Введите `mg`, чтобы просмотреть справочную документацию. - -Помните, что при выполнении команд следует заменить заполнители (слова внутри `< >`) на ваши фактические значения. \ No newline at end of file diff --git a/docs/ru/manual-ubuntu.md b/docs/ru/manual-ubuntu.md deleted file mode 100644 index 83307dcb..00000000 --- a/docs/ru/manual-ubuntu.md +++ /dev/null @@ -1,67 +0,0 @@ -# Как стать валидатором с помощью mytonctrl (v0.2, ОС Ubuntu) - -Вот шаги по становлению валидатором с использованием mytonctrl. Этот пример применим для операционной системы Ubuntu. - -## 1. Установка mytonctrl: - -1. Загрузите скрипт установки. Мы рекомендуем устанавливать инструмент под вашей локальной учетной записью пользователя, а не как Root. В нашем примере используется локальная учетная запись пользователя: - - ```sh - wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh - ``` - - ![wget output](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_wget-ls_ru.png) - -2. Запустите скрипт установки как администратор: - - ```sh - sudo bash install.sh -m full - ``` - -## 2. Тест на работоспособность: - -1. Запустите **mytonctrl** с локальной учетной записью пользователя, используемой для установки на шаге 1: - - ```sh - mytonctrl - ``` - -2. Проверьте статусы **mytonctrl**, в частности следующие: - -* **mytoncore status**: должен быть зеленым. -* **Local validator status**: также должен быть зеленым. -* **Local validator out of sync**: Сначала отображается большое число. Как только новый валидатор подключается к другим валидаторам, число будет около 250k. По мере синхронизации это число уменьшается. Когда оно падает ниже 20, валидатор синхронизирован. - - ![status](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/mytonctrl-status.png) - -Смотрим доступные кошельки. Кошелек validator_wallet_001 был создан при установке mytonctrl: - -![wallet list](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-wl_ru.png) - -## 3. Просмотр списка доступных кошельков -Просмотрите список доступных кошельков. В нашем примере кошелек **validator_wallet_001** был создан при установке **mytonctrl**: - -![wallet list](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-wl_ru.png) - -## 4. Отправьте необходимое количество монет в кошелек и активируйте его -Чтобы проверить минимальное количество монет, необходимое для участия в одном раунде выборов, перейдите на **tonmon.xyz** > **Участники ставок**. - -* Команда `vas` отображает историю переводов -* Команда `aw` активирует кошелек - - ![account history](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-vas-aw_ru.png) - -## 5. Теперь ваш валидатор готов к работе -**mytoncore** автоматически присоединяется к выборам. Он делит баланс кошелька на две части и использует их в качестве ставки для участия в выборах. Вы также можете вручную установить размер ставки: - -`set stake 50000` — установить размер ставки в 50k монет. Если ставка принята и наш узел становится валидатором, ставку можно вернуть только на вторых выборах (согласно правилам электората). - -![setting stake](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-set_ru.png) - -Не стесняйтесь обращаться за помощью. - -![help command](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-help_ru.png) - -Для проверки логов **mytoncrl** откройте `~/.local/share/mytoncore/mytoncore.log` для локального пользователя или `/usr/local/bin/mytoncore/mytoncore.log` для Root. - -![logs](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytoncore-log.png) \ No newline at end of file diff --git a/docs/ru/nominator-pool.md b/docs/ru/nominator-pool.md deleted file mode 100644 index a735f1b8..00000000 --- a/docs/ru/nominator-pool.md +++ /dev/null @@ -1,118 +0,0 @@ -# Номинаторский пул - -## Запуск валидатора в режиме номинаторского пула - -1. Подготовьте аппаратное обеспечение для валидатора - 8 виртуальных ядер, 64GB памяти, 1TB SSD, фиксированный IP-адрес, скорость интернета 1Gb/s. - - Для поддержания стабильности сети рекомендуется разместить валидаторы в различных местах по всему миру, а не концентрировать их в одном дата-центре. Вы можете использовать [этот сайт](https://status.toncenter.com/) для определения нагрузки на различные места. Согласно карте, высокая нагрузка на дата-центры в Европе, особенно в Финляндии, Германии и Париже. Поэтому использование провайдеров, таких как Hetzner и OVH, не рекомендуется. - - > Ваше оборудование должно соответствовать указанной конфигурации или быть выше. Запуск валидатора на слабом оборудовании негативно влияет на сеть и может привести к штрафам. - - > С мая 2021 года Hetzner запретил майнинг на своих серверах, в настоящее время под это правило попадают алгоритмы PoW и PoS. Установка даже обычного узла будет считаться нарушением условий договора. - - > **Рекомендуемые провайдеры:** [Amazon](https://aws.amazon.com/), [DigitalOcean](https://www.digitalocean.com/), [Linode](https://www.linode.com/), [Alibaba Cloud](https://alibabacloud.com/), [Latitude](https://www.latitude.sh/). - -2. Установите и синхронизируйте **mytonctrl** в соответствии с описанием в [этой инструкции](https://github.com/ton-blockchain/mytonctrl/blob/master/docs/ru/manual-ubuntu.md) — следуйте **только** пунктам 1, 2 и 3. - - Вы также можете обратиться к этой [Видеоинструкции](https://ton.org/docs/#/nodes/run-node) для дополнительной помощи. - -3. Переведите 1 TON на адрес кошелька валидатора, который отображается в списке `wl`. - -4. Используйте команду `aw` для активации кошелька валидатора. - -5. Создайте два пула (для четного и нечетного раунда проверки): - - ``` - new_pool p1 0 1 1000 300000 - new_pool p2 0 1 1001 300000 - ``` - - где - * `p1` — имя пула; - * `0` % — это доля вознаграждения валидатора (например, используйте 40 для 40%); - * `1` - максимальное количество номинаторов в пуле (должно быть <= 40); - * `1000` TON — минимальная ставка валидатора (должна быть >= 1K TON); - * `300000` TON – минимальная ставка номинатора (должна быть >= 10K TON); - - > (!) Конфигурации пулов не должны быть идентичными, вы можете добавить 1 к минимальной ставке одного пула, чтобы сделать их разными. - - > (!) Используйте https://tonmon.xyz/, чтобы определить текущую минимальную ставку валидатора. - -6. Введите `pools_list` чтобы отобразить адреса пулов: - - ``` - pools_list - Name Status Balance Address - p1 empty 0 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT - p2 empty 0 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL - ``` - -7. Отправьте по 1 TON в каждый пул и активируйте пулы: - - ``` - mg validator_wallet_001 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT 1 - mg validator_wallet_001 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL 1 - activate_pool p1 - activate_pool p2 - ``` - -8. Введите `pools_list` чтобы отобразить адреса пулов: - - ``` - pools_list - Name Status Balance Address - p1 active 0.731199733 kf98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780v_W - p2 active 0.731199806 kf9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV8UO - ``` - -9. Откройте каждый пул по ссылке "https://tonscan.org/nominator/" и проверьте конфигурацию пулов. - -10. Совершите депозит валидатора в каждый пул: - - ```bash - deposit_to_pool validator_wallet_001 1005 - deposit_to_pool validator_wallet_001 1005 - ``` - - где `1005` TON - это сумма депозита. Обратите внимание, что пул вычтет 1 TON за обработку депозита. - -11. Совершите депозит номинатора в каждый пул: - - Перейдите по ссылке пула (из **шага 9**) и нажмите **ADD STAKE**. - Вы также можете сделать депозит, используя **mytonctrl**, с помощью следующих команд: - - ```bash - mg nominator_wallet_001 300001 -C d - mg nominator_wallet_001 300001 -C d - ``` - - > (!) Кошелек номинатора должен быть инициализирован в basechain (workchain 0). - - > (!) Обратите внимание, что кошельки валидатора и номинатора должны храниться отдельно! Кошелек валидатора должен храниться на сервере с узлом валидатора для обработки всех системных транзакций. Кошелек номинатора следует хранить в вашем холодном криптовалютном кошельке. - - > Чтобы снять депозит номинатора, отправьте транзакцию с комментарием `w` на адрес пула (должен быть прикреплен 1 TON для обработки транзакции). Вы также можете сделать это с помощью **mytonctrl**. - -12. Активируйте режим пула: - - ```bash - set usePool true - set stake null - ``` - -13. Пригласите номинаторов сделать депозиты в ваши пулы. Участие в валидации начнется автоматически. - - > (!) Убедитесь, что у вас на кошельке валидатора есть как минимум 200 TON в месяц на операционные расходы. - -## Настройка пулов - -Если вы хотите давать в долг самому себе, то используйте `new_pool p1 0 1 1000 300000` (максимум 1 номинант, 0% доли валидатора). - -Если вы создаете пул для многих номинантов, то используйте что-то вроде этого: `new_pool p1 40 40 10000 10000` (максимум 40 номинантов, 40% доли валидатора, минимальная ставка участника 10K TON). - -## Переключение обычного валидатора в режим номинантского пула - -1. Введите `set stake 0`, чтобы отключить участие в выборах. - -2. Дождитесь, когда оба ваших депозита вернутся от электора. - -3. Следуйте инструкциям "Запуск валидатора в режиме номинантского пула", начиная с **4-го шага**. \ No newline at end of file diff --git a/docs/zh_TW/FAQ.md b/docs/zh_TW/FAQ.md deleted file mode 100644 index 54bfce6e..00000000 --- a/docs/zh_TW/FAQ.md +++ /dev/null @@ -1,121 +0,0 @@ -# MyTonCtrl 使用的目錄 - -MyTonCtrl 是一個包裝器,會在兩個地方儲存檔案: - -1. `~/.local/share/mytonctrl/` - 儲存長期檔案,如日誌檔 -2. `/tmp/mytonctrl/` - 儲存臨時檔案 - -MyTonCtrl 也包含了另一個腳本 mytoncore,它將檔案儲存到以下位置: - -1. `~/.local/share/mytoncore/` - 持久檔案,主要的設定檔會儲存在這裡 -2. `/tmp/mytoncore/` - 臨時檔案,選舉使用的參數將會保存在此 - -MyTonCtrl 會將自身和驗證者的源碼下載到以下資料夾: - -1. `/usr/src/mytonctrl/` -2. `/usr/src/ton/` - -MyTonCtrl 會將驗證者的組件編譯到以下資料夾: - -1. `/usr/bin/ton/` - -MyTonCtrl 會在這裡建立一個驗證者工作的資料夾: - -1. `/var/ton/` - ---- - -## 若 MyTonCtrl 以 root 使用者安裝: - -那麼,設定檔會以不同的方式存放: - -1. `/usr/local/bin/mytonctrl/` -2. `/usr/local/bin/mytoncore/` - ---- - -## 如何移除 MyTonCtrl: - -以管理員身份執行腳本並移除已編譯的 TON 組件: - -```bash -sudo bash /usr/src/mytonctrl/scripts/uninstall.sh -sudo rm -rf /usr/bin/ton -``` - -在這個過程中,確保你有足夠的權限來刪除或更改這些檔案或目錄。 - -# 處理錯誤和更改 MyTonCtrl 的工作目錄 - -如果您在使用不同的使用者執行 MyTonCtrl 時遇到問題,或者您想要在安裝前更改驗證者的工作目錄,以下的指南可以提供幾種解決方案。 - -## 以不同的使用者執行 MyTonCtrl - -當以不同的使用者執行 MyTonCtrl 時,可能會出現以下錯誤: - -``` -Error: expected str, bytes or os.PathLike object, not NoneType -``` - -要解決此問題,您需要以安裝 MyTonCtrl 的使用者執行程式。 - -## 在安裝前更改驗證者的工作目錄 - -如果您想在安裝前更改驗證者的工作目錄,有兩種方式可以達成: - -1. **分叉專案** - 您可以分叉專案並在其中進行修改。查看 `man git-fork` 指令以瞭解如何進行分叉操作。 -2. **創建符號連結** - 您也可以使用以下指令創建一個符號連結: - - ```bash - ln -s /opt/ton/var/ton - ``` -此指令會創建一個名為 `/var/ton` 的連結,導向 `/opt/ton` 目錄。 - -## 在安裝後更改驗證者的工作目錄 - -如果您希望在安裝後將驗證者的工作目錄從 `/var/ton/` 改為其他目錄,請按照以下步驟進行: - -1. **停止服務** - 使用以下指令停止相關服務: - - ```bash - systemctl stop validator - systemctl stop mytoncore - ``` - -2. **移動驗證者的檔案** - 接著使用以下指令將驗證者的檔案移至新的目錄: - - ```bash - mv /var/ton/* /opt/ton/ - ``` - -3. **更改配置中的路徑** - 將 `~/.local/share/mytoncore/mytoncore.db` 中的路徑替換為新的路徑。 - -4. **注意事項** - 之前並未有過如此的轉移經驗,所以進行此操作時請小心。 - -請確認您擁有足夠的權限來執行這些修改和指令。 - -# 如何在 MyTonCtrl 中確認驗證者狀態並重啟驗證者 - -本文檔將幫助你了解如何確認 MyTonCtrl 是否已成為全功能驗證者,以及如何重啟你的驗證者。 - -## 在 MyTonCtrl 中確認驗證者狀態 - -你可以透過以下條件來確認你的節點是否已成為全功能驗證者: - -1. **驗證者的異步狀態** - 本地驗證者的異步狀態應該小於 20。 -2. **驗證者的索引** - 驗證者的索引應該大於 -1。 - -你可以透過 MyTonCtrl 的 `vl` 命令查看驗證者的工作率: - -1. 在列表中按照其 ADNL 地址(`adnlAddr`)找到你的驗證者。 -2. 如果 `mr` 和 `wr` 的係數接近 1,則表示你的驗證者運行正常。 - -## 重啟你的驗證者 - -如果你需要重啟你的驗證者,可以執行以下命令: - -```bash -systemctl restart validator -``` - -請確保你具有執行這些命令的適當權限,並進行必要的調整。在執行可能影響你的驗證者的操作之前,請始終記得備份重要數據。 \ No newline at end of file diff --git a/docs/zh_TW/import-wallets.md b/docs/zh_TW/import-wallets.md deleted file mode 100644 index 8564ed68..00000000 --- a/docs/zh_TW/import-wallets.md +++ /dev/null @@ -1,36 +0,0 @@ -# 匯入錢包 - -MyTonCtrl支援多種類型的類似錢包的合約,包括wallet-v1,wallet-v3,[lockup-wallet](https://github.com/ton-blockchain/lockup-wallet-contract/tree/main/universal)等等。通常,這是處理這些合約的最簡單方法。 - -## 使用私鑰匯入 - -如果你知道私鑰,你只需要在控制台輸入以下指令: - -``` -iw -``` - -在此,`` 是以base64格式的私鑰。 - -## 使用助記詞匯入 - -如果你知道助記詞短語(像是由24個單詞組成的短語,例如 `tattoo during ...`),請執行以下步驟: - -1. 安裝Node.js。 -2. 複製並安裝 [mnemonic2key](https://github.com/ton-blockchain/mnemonic2key): - ``` - git clone https://github.com/ton-blockchain/mnemonic2key.git - cd mnemonic2key - npm install - ``` -3. 執行以下命令,其中 `word1`,`word2` ... 是你的助記詞短語,`address` 是你的錢包合約地址: - ``` - node index.js word1 word2 ... word24 [address] - ``` -4. 腳本將生成 `wallet.pk` 和 `wallet.addr`,將它們重命名為 `imported_wallet.pk` 和 `imported_wallet.addr`。 -5. 將這兩個檔案複製到 `~/.local/share/mytoncore/wallets/` 目錄。 -6. 開啟mytonctrl控制台,並使用 `wl` 命令列出錢包。 -7. 確認錢包已經匯入並且餘額正確。 -8. 現在你可以使用 `mg` 命令發送金錢(輸入 `mg` 可查看使用說明)。 - -在執行命令時,記得將尖括號內的佔位符(例如 ``、``)替換為實際的值。 \ No newline at end of file diff --git a/docs/zh_TW/manual-ubuntu.md b/docs/zh_TW/manual-ubuntu.md deleted file mode 100644 index 4ab6da0a..00000000 --- a/docs/zh_TW/manual-ubuntu.md +++ /dev/null @@ -1,67 +0,0 @@ -# 如何使用 mytonctrl 成為驗證者 (v0.2, Ubuntu操作系統) - -以下為使用 mytonctrl 成為驗證者的步驟。此範例適用於Ubuntu操作系統。 - -## 1. 安裝 mytonctrl: - -1. 下載安裝腳本。我們建議在您的本地用戶帳戶下安裝該工具,而非 Root。在我們的示例中,使用的是本地用戶帳戶: - - ```sh - wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh - ``` - - ![wget output](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_wget-ls_ru.png) - -2. 以管理員身份運行安裝腳本: - - ```sh - sudo bash install.sh -m full - ``` - -## 2. 操作測試: - -1. 從在第一步中用於安裝的本地用戶帳戶運行 **mytonctrl**: - - ```sh - mytonctrl - ``` - -2. 檢查 **mytonctrl** 的狀態,特別是以下幾點: - -* **mytoncore status**:應為綠色。 -* **Local validator status**:應為綠色。 -* **Local validator out of sync**:首次顯示出一個大數字。新創建的驗證者一旦與其他驗證者聯繫,該數字約為250k。隨著同步的進行,這個數字會減少。當它降至20以下時,驗證者就同步了。 - - ![status](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/mytonctrl-status.png) - - -## 3. 查看可用錢包列表 - -檢查可用的錢包列表。例如,在安裝 **mytonctrl** 時,會創建 **validator_wallet_001** 錢包: - -![wallet list](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-wl_ru.png) - -## 4. 向錢包發送所需數量的幣並激活它 - -要確定參與一輪選舉所需的最小幣數,請轉到 **tonmon.xyz** > **參與者賭注**。 - -* 使用 `vas` 命令顯示轉賬歷史 -* 使用 `aw` 命令激活錢包 - - ![account history](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-vas-aw_ru.png) - -## 5. 現在你的驗證人準備好了 - -**mytoncore** 將自動加入選舉。它將錢包餘額分為兩部分,並將它們作為賭注參與選舉。您也可以手動設定賭注大小: - -`set stake 50000` — 這將賭注大小設定為50k幣。如果賭注被接受,並且我們的節點成為驗證人,則只能在第二次選舉中撤回賭注(根據選民的規則)。 - -![setting stake](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-set_ru.png) - -您也可以隨時命令求助。 - -![help command](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytonctrl-help_ru.png) - -要檢查 **mytoncrl** 日誌,對於本地用戶,打開 `~/.local/share/mytoncore/mytoncore.log`,對於 Root,打開 `/usr/local/bin/mytoncore/mytoncore.log`。 - -![logs](https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/screens/manual-ubuntu_mytoncore-log.png) \ No newline at end of file diff --git a/docs/zh_TW/nominator-pool.md b/docs/zh_TW/nominator-pool.md deleted file mode 100644 index e3fac6fe..00000000 --- a/docs/zh_TW/nominator-pool.md +++ /dev/null @@ -1,117 +0,0 @@ -# Nominator pool - -## 在提名人池模式下運行驗證人 - -1. 為驗證人準備硬體 - 8個虛擬 CPU、64GB 內存、1TB SSD、固定 IP 地址、1Gb/s 的網路速度。 - - 為了維護網路穩定性,建議將驗證節點分布在世界不同地點,而不是集中在一個數據中心。可以使用 https://status.toncenter.com/ 確定位置的負載。根據地圖,你可以看到歐洲、芬蘭、德國和巴黎的數據中心使用率高。因此,我們不建議使用像 Hetzner 和 OVH 這樣的提供商。 - - > 你的硬體必須符合指定的配置或更高。不要在弱硬體上運行驗證器 - 這將對網路產生負面影響,並將對你進行罰款。 - - > 由於 2021 年 5 月,Hetzner 已經禁止在其伺服器上挖礦,目前 PoW 和 PoS 算法都適用於此規則。即使安裝了常規節點,也將被視為違反協議條款。 - - > **推薦的提供商:** [amazon](https://aws.amazon.com/), [digitalocean](https://www.digitalocean.com/), [linode](https://www.linode.com/), [alibaba cloud](https://alibabacloud.com/), [latitude](https://www.latitude.sh/). - -2. 安裝並同步 **mytonctrl**,如 https://github.com/ton-blockchain/mytonctrl/blob/master/docs/zh_TW/manual-ubuntu.md 中描述的那樣,**僅**執行步驟1、2和3。 - - [視頻指導](https://ton.org/docs/#/nodes/run-node)。 - -3. 將 1 TON 發送到顯示在 `wl` 列表中的驗證人錢包地址。 - -4. 輸入 `aw` 以啟動驗證人錢包。 - -5. 建立兩個池子(對於偶數和奇數的驗證輪次): - - ``` - new_pool p1 0 1 1000 300000 - new_pool p2 0 1 1001 300000 - ``` - 其中 - * `p1` 是池名稱; - * `0` % 是驗證人的獎勵份額(例如,對於 40% 使用 40); - * `1` 是池中的最大提名人數量(應 <= 40); - * `1000` TON 是最低驗證人的股份(應 >= 1K TON); - * `300000` TON 是最低提名人的股份(應 >= 10K TON); - - > (!) 池的配置不必相同,你可以向其中一個池的最小股份增加 1 以使它們不同。 - - > (!) 使用 https://tonmon.xyz/ 確定當前最小驗證人股份。 - -6. 輸入 `pools_list` 顯示池地址: - - ``` - pools_list - Name Status Balance Address - p1 empty 0 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT - p2 empty 0 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL - ``` - -7. 向每個池發送 1 TON 並激活池: - - ``` - mg validator_wallet_001 0f98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780qIT 1 - mg validator_wallet_001 0f9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV5jL 1 - activate_pool p1 - activate_pool p2 - ``` - -8. 輸入 `pools_list` 來顯示池: - - ``` - pools_list - Name Status Balance Address - p1 active 0.731199733 kf98YhXA9wnr0d5XRXT-I2yH54nyQzn0tuAYC4FunT780v_W - p2 active 0.731199806 kf9qtmnzs2-PumMisKDmv6KNjNfOMDQG70mQdp-BcAhnV8UO - ``` - -9. 打開每個池的連結 "https://tonscan.org/nominator/" 並驗證池配置。 - -10. 向每個池進行驗證者存款: - - ```bash - deposit_to_pool validator_wallet_001 1005 - deposit_to_pool validator_wallet_001 1005 - ``` - - 其中 `1005` TON 是存款金額。請注意,池會扣除 1 TON 用於處理存款。 - -11. 向每個池進行提名人存款: - - 轉到池鏈接(**第9步**)並單擊 **ADD STAKE**。 - 您也可以使用 **mytonctrl** 進行存款,使用以下命令進行。 - - ```bash - mg nominator_wallet_001 300001 -C d - mg nominator_wallet_001 300001 -C d - ``` - - > (!) 提名人錢包必須在 basechain (workchain 0) 初始化。 - - > (!) 請注意,驗證者錢包和提名人錢包必須單獨存放!驗證者錢包存放在具有驗證者節點的服務器上,以確保所有系統交易的處理。提名人錢包存放在您的冷加密貨幣錢包中。 - - > 如需撤回提名人存款,發送附註 `w` 的交易到池地址(必須附加 1 TON 以處理交易)。您也可以使用 **mytonctrl** 進行此操作。 - -12. 啟動池模式: - - ```bash - set usePool true - set stake null - ``` - -13. 邀請提名人向您的池存款。驗證參與將自動開始。 - - > (!) 您需要在驗證者錢包上至少有 200 TON/月的操作費用。 - -## 設定池 - -如果你打算借給自己,則使用 `new_pool p1 0 1 1000 300000`(最大1個提名人,驗證者的份額為0%)。 - -如果你為許多提名人創建池,則可以使用類似以下的設定:`new_pool p1 40 40 10000 10000`(最多40個提名人,驗證者的份額為40%,最低參與者的賭注為10K TON)。 - -## 將一般驗證者轉換為提名人池模式 - -1. 輸入 `set stake 0` 來停止參加選舉。 - -2. 等待你的兩個賭注從選民那裡返回。 - -3. 從**第四步**開始,按照"在提名人池模式下運行驗證者"的步驟進行操作。 \ No newline at end of file diff --git a/functions/session_stats.py b/functions/session_stats.py new file mode 100644 index 00000000..ba08b06e --- /dev/null +++ b/functions/session_stats.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# -*- coding: utf_8 -*- + +import os +import sys +import time +import json +sys.path.append("/usr/src/mytonctrl/") +from mypylib.mypylib import Dict + +def read_session_stats(need_time_period): + #print(f"read_session_stats: {need_time_period}") + file_path = "/var/ton-work/log.session-stats" + average_block_time = calculate_average_block_time(file_path) + need_blocks = int(need_time_period/average_block_time) + + lines = read_last_lines(file_path, need_lines=need_blocks) + #lines = read_all_lines(file_path) + data = lines2data(lines) + + result = Dict() + result.my_blocks = 0 + result.my_need_blocks = 0 + result.all_master_blocks = 0 + result.all_blocks = len(data) + for buff in data: + if buff.id.workchain == -1: + result.all_master_blocks += 1 + first_producer = buff.rounds[0].producers[0] + if buff.self != first_producer.id: + continue + result.my_need_blocks += 1 + if buff.self == buff.creator: + result.my_blocks += 1 + #end for + return result +#end define + +def calculate_average_block_time(file_path): + blocks = 100 + lines = read_last_lines(file_path, need_lines=blocks) + data = lines2data(lines) + first_block, last_block = get_first_last_block(data) + + diff = int(last_block.timestamp) - int(first_block.timestamp) + average_block_time = round(diff/blocks, 2) + #print("average_block_time:", average_block_time) + return average_block_time +#end define + +def get_first_last_block(data, last_index=-1): + first_block = data[0] + last_block = data[last_index] + if first_block.id.workchain == last_block.id.workchain: + blocks = int(last_block.id.seqno) - int(first_block.id.seqno) + else: + first_block, last_block = get_first_last_block(data, last_index=last_index-1) + return first_block, last_block +#end define + +def lines2data(lines): + #start = time.time() + data = list() + for line in lines: + try: + buff = json.loads(line) + data.append(Dict(buff)) + except json.decoder.JSONDecodeError: + pass + #end for + + #end = time.time() + #diff = round(end - start, 2) + #print(f"lines2data completed in {diff} seconds") + return data +#end define + +def read_all_lines(file_path): + #start = time.time() + with open(file_path, 'rt') as file: + text = file.read() + #end with + lines = text.split('\n') + + #end = time.time() + #diff = round(end - start, 2) + #print(f"read_all_lines completed in {diff} seconds") + return lines +#end define + +def read_last_lines(file_path, need_lines): + lines = list() + max_lines = 30000 + if need_lines < 1: + return lines + elif need_lines > max_lines: + need_lines = max_lines + #print(f"read_last_lines: {need_lines}") + #start = time.time() + with open(file_path, 'rb') as file: + find_last_lines(file, need_lines) + for i in range(need_lines): + line = file.readline().decode() + lines.append(line) + #end with + + + #end = time.time() + #diff = round(end - start, 2) + #print(f"read_last_lines {len(lines)} completed in {diff} seconds") + return lines +#end define + +def find_last_lines(file, need_lines): + # catch OSError in case of a one line file + try: + find_lines = 0 + #file.seek(-2, os.SEEK_END) + #while find_lines != need_lines: + # if file.read(1) == b'\n': + # find_lines += 1 + # file.seek(-2, os.SEEK_CUR) + + file.seek(-100, os.SEEK_END) + while find_lines != need_lines: + if b'\n' in file.read(100): + find_lines += 1 + file.seek(-200, os.SEEK_CUR) + except OSError: + file.seek(0) +#end define + + + + + diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 00000000..8b92c260 --- /dev/null +++ b/modules/__init__.py @@ -0,0 +1,66 @@ +import typing +from dataclasses import dataclass + +from modules.module import MtcModule +from modules.pool import PoolModule +from modules.nominator_pool import NominatorPoolModule +from modules.single_pool import SingleNominatorModule +from modules.validator import ValidatorModule +from modules.controller import ControllerModule +from modules.liteserver import LiteserverModule + + +MODES = { + 'validator': ValidatorModule, + 'nominator-pool': NominatorPoolModule, + 'single-nominator': SingleNominatorModule, + 'liquid-staking': ControllerModule, + 'liteserver': LiteserverModule +} + + +def get_mode(mode_name: str) -> typing.Optional[MtcModule]: + return MODES.get(mode_name) + + +@dataclass +class Setting: + mode: typing.Optional[str] + default_value: typing.Any + description: str + + +SETTINGS = { + 'stake': Setting('validator', None, 'Stake amount'), + 'stakePercent': Setting('validator', 99, 'Stake percent if `stake` is null'), + 'isSlashing': Setting('validator', None, 'Create complaints to validators'), + 'validatorWalletName': Setting('validator', 'wallet_001', 'Validator\'s wallet name'), + 'maxFactor': Setting('validator', None, 'Param send to Elector. if null will be taken from 17 config param'), + 'participateBeforeEnd': Setting('validator', None, 'Amount of seconds before start of round to participate'), + 'liquid_pool_addr': Setting('liquid-staking', None, 'Liquid staking pool address'), + 'min_loan': Setting('liquid-staking', 41000, 'Min loan amount'), + 'max_loan': Setting('liquid-staking', 43000, 'Max loan amount'), + 'max_interest_percent': Setting('liquid-staking', 10, 'Max interest percent'), + 'duplicateSendfile': Setting(None, True, 'Duplicate external to public Liteservers'), + 'sendTelemetry': Setting(None, True, 'Send node telemetry'), + 'telemetryLiteUrl': Setting(None, 'https://telemetry.toncenter.com/report_status', 'Telemetry url'), + 'overlayTelemetryUrl': Setting(None, 'https://telemetry.toncenter.com/report_overlays', 'Overlay telemetry url'), + 'duplicateApi': Setting(None, 'sendTelemetry', 'Duplicate external to Toncenter'), + 'duplicateApiUrl': Setting(None, 'https://[testnet.]toncenter.com/api/v2/sendBoc', 'Toncenter api url for duplicate'), + 'checkAdnl': Setting(None, 'sendTelemetry', 'Check local udp port and adnl connection'), + 'liteclient_timeout': Setting(None, 3, 'Liteclient default timeout'), + 'console_timeout': Setting(None, 3, 'Validator console default timeout'), + 'fift_timeout': Setting(None, 3, 'Fift default timeout'), + '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'), +} + + +def get_setting(name: str) -> typing.Optional[Setting]: + return SETTINGS.get(name) + + +def get_mode_settings(name: str): + return {k: v for k, v in SETTINGS.items() if v.mode == name} + diff --git a/modules/collator_config.py b/modules/collator_config.py new file mode 100644 index 00000000..9234b08f --- /dev/null +++ b/modules/collator_config.py @@ -0,0 +1,91 @@ +import json +import requests + +from mypylib.mypylib import color_print +from modules.module import MtcModule + + +class CollatorConfigModule(MtcModule): + + @staticmethod + def check_config_url(url): + try: + r = requests.get(url, timeout=3) + if r.status_code != 200: + print(f'Failed to get config from {url}: {r.status_code} code; {r.text}') + return + return r.json() + except Exception as e: + print(f'Failed to get config from {url}: {e}') + return + + @staticmethod + def check_config_file(path): + try: + with open(path, 'r') as f: + return json.load(f) + except Exception as e: + print(f'Failed to read config from {path}: {e}') + return + + @staticmethod + def get_config(path): + if 'http' in path: + config = CollatorConfigModule.check_config_url(path) + else: + config = CollatorConfigModule.check_config_file(path) + if config is None: + raise Exception(f'Failed to get config') + return config + + def add_collator_config_to_vc(self, config: dict): + self.local.add_log(f"Adding collator options config to validator console", "debug") + path = self.ton.tempDir + f'/collator_config.json' + with open(path, 'w') as f: + json.dump(config, f) + result = self.ton.validatorConsole.Run(f"setcollatoroptionsjson {path}") + return 'success' in result, result + + def set_collator_config(self, args): + if len(args) != 1: + color_print("{red}Bad args. Usage:{endc} set_collator_config ") + return + location = args[0] + config = self.get_config(location) + self.ton.set_collator_config(location) + added, msg = self.add_collator_config_to_vc(config) + if not added: + print(f'Failed to add collator config to validator console: {msg}') + color_print("set_collator_config - {red}ERROR{endc}") + return + color_print("set_collator_config - {green}OK{endc}") + + def get_collator_config(self, args): + location = self.ton.get_collator_config_location() + print(f'Collator config location: {location}') + path = self.ton.tempDir + f'/current_collator_config.json' + output = self.ton.validatorConsole.Run(f'getcollatoroptionsjson {path}') + if 'saved config to' not in output: + print(f'Failed to get collator config: {output}') + color_print("get_collator_config - {red}ERROR{endc}") + return + with open(path, 'r') as f: + config = json.load(f) + print(f'Collator config:') + print(json.dumps(config, indent=4)) + color_print("get_collator_config - {green}OK{endc}") + + def update_collator_config(self, args): + location = self.ton.get_collator_config_location() + config = self.get_config(location) + added, msg = self.add_collator_config_to_vc(config) + if not added: + print(f'Failed to add collator config to validator console: {msg}') + color_print("update_collator_config - {red}ERROR{endc}") + return + color_print("update_collator_config - {green}OK{endc}") + + def add_console_commands(self, console): + console.AddItem("set_collator_config", self.set_collator_config, self.local.translate("set_collator_config_cmd")) + console.AddItem("update_collator_config", self.update_collator_config, self.local.translate("update_collator_config_cmd")) + console.AddItem("get_collator_config", self.get_collator_config, self.local.translate("get_collator_config_cmd")) diff --git a/modules/controller.py b/modules/controller.py new file mode 100644 index 00000000..0acc8b1e --- /dev/null +++ b/modules/controller.py @@ -0,0 +1,248 @@ +import json +import os +import time + +from mypylib.mypylib import color_print, print_table + +from mytonctrl.utils import GetItemFromList +from modules.module import MtcModule + + +class ControllerModule(MtcModule): + + description = 'Liquid staking controllers.' + default_value = False + + def do_create_controllers(self): + new_controllers = self.ton.GetControllers() + old_controllers = self.ton.local.db.get("using_controllers", list()) + if new_controllers == old_controllers: + return + + self.ton.local.add_log("start CreateControllers function", "debug") + wallet = self.ton.GetValidatorWallet() + liquid_pool_addr = self.ton.GetLiquidPoolAddr() + contract_path = self.ton.contractsDir + "jetton_pool/" + if not os.path.isdir(contract_path): + self.ton.DownloadContract("https://github.com/igroman787/jetton_pool") + + file_name0 = contract_path + "fift-scripts/deploy_controller0.boc" + file_name1 = contract_path + "fift-scripts/deploy_controller1.boc" + result_file_path0 = self.ton.SignBocWithWallet(wallet, file_name0, liquid_pool_addr, 1) + self.ton.SendFile(result_file_path0, wallet) + time.sleep(10) + result_file_path1 = self.ton.SignBocWithWallet(wallet, file_name1, liquid_pool_addr, 1) + self.ton.SendFile(result_file_path1, wallet) + + self.ton.local.db["old_controllers"] = old_controllers + self.ton.local.db["using_controllers"] = new_controllers + self.ton.local.save() + + def create_controllers(self, args): + self.do_create_controllers() + color_print("CreateControllers - {green}OK{endc}") + + def print_controllers_list(self, args): + new_controllers = self.ton.GetControllers() + using_controllers = self.ton.GetSettings("using_controllers") + old_controllers = self.ton.GetSettings("old_controllers") + user_controllers_list = self.ton.GetSettings("user_controllers_list") + print("using controllers:") + if using_controllers is not None: + self.print_controllers_list_process(using_controllers) + if new_controllers is not None and new_controllers != using_controllers: + print() + print("new controllers:") + self.print_controllers_list_process(new_controllers) + if old_controllers is not None and len(old_controllers) > 0: + print() + print("old controllers:") + self.print_controllers_list_process(old_controllers) + if user_controllers_list is not None and len(user_controllers_list) > 0: + print() + print("user controllers:") + self.print_controllers_list_process(user_controllers_list) + + def print_controllers_list_process(self, controllers): + table = list() + table += [["Address", "Status", "Balance", "Approved", "State"]] + for controllerAddr in controllers: + account = self.ton.GetAccount(controllerAddr) + controllerData = self.ton.GetControllerData(controllerAddr) + approved = True if controllerData and controllerData["approved"] == -1 else False + state = controllerData["state"] if controllerData else None + table += [[controllerAddr, account.status, account.balance, approved, state]] + print_table(table) + + def get_controller_data(self, args): + try: + controller_addr = args[0] + except: + color_print("{red}Bad args. Usage:{endc} get_controller_data ") + return + controller_data = self.ton.GetControllerData(controller_addr) + print(json.dumps(controller_data, indent=4)) + + def do_deposit_to_controller(self, controller_addr, amount): + self.ton.local.add_log("start DepositToController function", "debug") + wallet = self.ton.GetValidatorWallet() + file_name = self.ton.contractsDir + "jetton_pool/fift-scripts/top-up.boc" + result_file_path = self.ton.SignBocWithWallet(wallet, file_name, controller_addr, amount) + self.ton.SendFile(result_file_path, wallet) + + def deposit_to_controller(self, args): + try: + controller_addr = args[0] + amount = float(args[1]) + except: + color_print("{red}Bad args. Usage:{endc} deposit_to_controller ") + return + self.do_deposit_to_controller(controller_addr, amount) + + def withdraw_from_controller(self, args): + try: + controller_addr = args[0] + amount = GetItemFromList(args, 1) + except: + color_print("{red}Bad args. Usage:{endc} withdraw_from_controller [amount]") + return + self.ton.WithdrawFromController(controller_addr, amount) + + def calculate_annual_controller_percentage(self, args): + try: + percent_per_round = float(args[0]) + except: + percent_per_round = self.ton.GetSettings("max_interest_percent") + config15 = self.ton.GetConfig(15) + roundPeriod = config15["validators_elected_for"] + rounds = 365 * 24 * 3600 / roundPeriod + yearInterest = (1 + percent_per_round / 100) * rounds + yearInterestPercent = round(yearInterest / 100, 2) + print("roundPeriod", roundPeriod) + print("rounds", rounds) + print("percentPerRound", percent_per_round) + print("yearInterest", yearInterest) + print(f"yearInterestPercent: {yearInterestPercent}%") + + def controller_update_validator_set(self, args): + try: + controller_addr = args[0] + except: + color_print("{red}Bad args. Usage:{endc} controller_update_validator_set ") + return + self.ton.ControllerUpdateValidatorSet(controller_addr) + color_print("ControllerUpdateValidatorSet - {green}OK{endc}") + + def do_stop_controller(self, controller_addr): + stop_controllers_list = self.ton.local.db.get("stop_controllers_list") + if stop_controllers_list is None: + stop_controllers_list = list() + if controller_addr not in stop_controllers_list: + stop_controllers_list.append(controller_addr) + self.ton.local.db["stop_controllers_list"] = stop_controllers_list + + user_controllers = self.ton.local.db.get("user_controllers") + if user_controllers is not None and controller_addr in user_controllers: + user_controllers.remove(controller_addr) + self.ton.local.save() + + def stop_controller(self, args): + try: + controller_addr = args[0] + except: + color_print("{red}Bad args. Usage:{endc} stop_controller ") + return + self.do_stop_controller(controller_addr) + color_print("StopController - {green}OK{endc}") + + def stop_and_withdraw_controller(self, args): + try: + controller_addr = args[0] + amount = GetItemFromList(args, 1) + except: + color_print("{red}Bad args. Usage:{endc} stop_and_withdraw_controller [amount]") + return + if amount is None: + account = self.ton.GetAccount(controller_addr) + amount = account.balance - 10.1 + self.do_stop_controller(controller_addr) + self.ton.WithdrawFromController(controller_addr, amount) + color_print("StopAndWithdrawController - {green}OK{endc}") + + def do_add_controller(self, controller_addr): + user_controllers = self.ton.local.db.get("user_controllers") + if user_controllers is None: + user_controllers = list() + if controller_addr not in user_controllers: + user_controllers.append(controller_addr) + self.ton.local.db["user_controllers"] = user_controllers + + stop_controllers_list = self.ton.local.db.get("stop_controllers_list") + if stop_controllers_list is not None and controller_addr in stop_controllers_list: + stop_controllers_list.remove(controller_addr) + self.ton.local.save() + + def add_controller(self, args): + try: + controller_addr = args[0] + except: + color_print("{red}Bad args. Usage:{endc} add_controller ") + return + self.do_add_controller(controller_addr) + color_print("AddController - {green}OK{endc}") + + def do_check_liquid_pool(self): + liquid_pool_addr = self.ton.GetLiquidPoolAddr() + account = self.ton.GetAccount(liquid_pool_addr) + history = self.ton.GetAccountHistory(account, 5000) + addrs_list = list() + for message in history: + if message.srcAddr is None or message.value is None: + continue + src_addr_full = f"{message.srcWorkchain}:{message.srcAddr}" + dest_add_full = f"{message.destWorkchain}:{message.destAddr}" + if src_addr_full == account.addrFull: + fromto = dest_add_full + else: + fromto = src_addr_full + fromto = self.ton.AddrFull2AddrB64(fromto) + if fromto not in addrs_list: + addrs_list.append(fromto) + + for controllerAddr in addrs_list: + account = self.ton.GetAccount(controllerAddr) + version = self.ton.GetVersionFromCodeHash(account.codeHash) + if version is None or "controller" not in version: + continue + print(f"check controller: {controllerAddr}") + self.ton.ControllerUpdateValidatorSet(controllerAddr) + + def check_liquid_pool(self, args): + self.do_check_liquid_pool() + color_print("CheckLiquidPool - {green}OK{endc}") + + def do_calculate_loan_amount_test(self): + min_loan = self.ton.local.db.get("min_loan", 41000) + max_loan = self.ton.local.db.get("max_loan", 43000) + max_interest_percent = self.ton.local.db.get("max_interest_percent", 1.5) + max_interest = int(max_interest_percent / 100 * 16777216) + return self.ton.CalculateLoanAmount(min_loan, max_loan, max_interest) + + def calculate_loan_amount_test(self, args): + t = self.do_calculate_loan_amount_test() + print(t) + + def add_console_commands(self, console): + console.AddItem("create_controllers", self.create_controllers, self.local.translate("_")) + console.AddItem("update_controllers", self.create_controllers, self.local.translate("_")) + console.AddItem("controllers_list", self.print_controllers_list, self.local.translate("_")) + console.AddItem("get_controller_data", self.get_controller_data, self.local.translate("_")) + console.AddItem("deposit_to_controller", self.deposit_to_controller, self.local.translate("_")) + console.AddItem("withdraw_from_controller", self.withdraw_from_controller, self.local.translate("_")) + console.AddItem("calculate_annual_controller_percentage", self.calculate_annual_controller_percentage, self.local.translate("_")) + console.AddItem("controller_update_validator_set", self.controller_update_validator_set, self.local.translate("_")) + console.AddItem("stop_controller", self.stop_controller, self.local.translate("_")) + console.AddItem("stop_and_withdraw_controller", self.stop_and_withdraw_controller, self.local.translate("_")) + console.AddItem("add_controller", self.add_controller, self.local.translate("_")) + console.AddItem("check_liquid_pool", self.check_liquid_pool, self.local.translate("_")) + console.AddItem("test_calculate_loan_amount", self.calculate_loan_amount_test, self.local.translate("_")) diff --git a/modules/custom_overlays.py b/modules/custom_overlays.py new file mode 100644 index 00000000..efa82dd8 --- /dev/null +++ b/modules/custom_overlays.py @@ -0,0 +1,189 @@ +import json +import requests + +from mypylib.mypylib import color_print +from modules.module import MtcModule +from mytoncore.utils import hex2base64 + + +class CustomOverlayModule(MtcModule): + + @staticmethod + def parse_config(name: str, config: dict, vset: list = None): + """ + Converts config to validator-console friendly format + :param name: custom overlay name + :param config: config + :param vset: list of validators adnl addresses, can be None if `@validators` not in config + :return: + """ + result = { + "name": name, + "nodes": [] + } + for k, v in config.items(): + if k == '@validators' and v: + if vset is None: + raise Exception("Validators set is not defined but @validators is in config") + for v_adnl in vset: + result["nodes"].append({ + "adnl_id": hex2base64(v_adnl), + "msg_sender": False, + }) + else: + if "block_sender" in v: + result["nodes"].append({ + "adnl_id": hex2base64(k), + "block_sender": v["block_sender"], + }) + elif "msg_sender" in v: + result["nodes"].append({ + "adnl_id": hex2base64(k), + "msg_sender": v["msg_sender"], + }) + if v["msg_sender"]: + result["nodes"][-1]["msg_sender_priority"] = v["msg_sender_priority"] + else: + raise Exception("Unknown node type") + return result + + def add_custom_overlay(self, args): + if len(args) != 2: + color_print("{red}Bad args. Usage:{endc} add_custom_overlay ") + return + path = args[1] + with open(path, 'r') as f: + config = json.load(f) + self.ton.set_custom_overlay(args[0], config) + if '@validators' in config: + print('Dynamic overlay will be added within 1 minute') + else: + result = self.add_custom_overlay_to_vc(self.parse_config(args[0], config)) + if not result: + print('Failed to add overlay to validator console') + color_print("add_custom_overlay - {red}ERROR{endc}") + return + color_print("add_custom_overlay - {green}OK{endc}") + + def list_custom_overlays(self, args): + if not self.ton.get_custom_overlays(): + color_print("{red}No custom overlays{endc}") + return + for k, v in self.ton.get_custom_overlays().items(): + color_print(f"Custom overlay {{bold}}{k}{{endc}}:") + print(json.dumps(v, indent=4)) + + def delete_custom_overlay(self, args): + if len(args) != 1: + color_print("{red}Bad args. Usage:{endc} delete_custom_overlay ") + return + if '@validators' in self.ton.get_custom_overlays().get(args[0], {}): + self.ton.delete_custom_overlay(args[0]) + print('Dynamic overlay will be deleted within 1 minute') + else: + self.ton.delete_custom_overlay(args[0]) + result = self.delete_custom_overlay_from_vc(args[0]) + if not result: + print('Failed to delete overlay from validator console') + color_print("delete_custom_overlay - {red}ERROR{endc}") + return + color_print("delete_custom_overlay - {green}OK{endc}") + + def check_node_eligible_for_custom_overlay(self, config: dict): + vconfig = self.ton.GetValidatorConfig() + my_adnls = vconfig.adnl + node_adnls = [i["adnl_id"] for i in config["nodes"]] + for adnl in my_adnls: + if adnl.id in node_adnls: + return True + return False + + def delete_custom_overlay_from_vc(self, name: str): + result = self.ton.validatorConsole.Run(f"delcustomoverlay {name}") + return 'success' in result + + def add_custom_overlay_to_vc(self, config: dict): + if not self.check_node_eligible_for_custom_overlay(config): + self.ton.local.add_log(f"Node has no adnl address required for custom overlay {config.get('name')}", "debug") + return False + self.ton.local.add_log(f"Adding custom overlay {config.get('name')}", "debug") + path = self.ton.tempDir + f'/custom_overlay_{config["name"]}.json' + with open(path, 'w') as f: + json.dump(config, f) + result = self.ton.validatorConsole.Run(f"addcustomoverlay {path}") + return 'success' in result + + def custom_overlays(self): + config = self.get_default_custom_overlay() + if config is not None: + self.ton.set_custom_overlay('default', config) + self.deploy_custom_overlays() + + def deploy_custom_overlays(self): + result = self.ton.validatorConsole.Run("showcustomoverlays") + if 'unknown command' in result: + return # node old version + names = [] + for line in result.split('\n'): + if line.startswith('Overlay'): + names.append(line.split(' ')[1].replace('"', '').replace(':', '')) + + config34 = self.ton.GetConfig34() + current_el_id = config34['startWorkTime'] + current_vset = [i["adnlAddr"] for i in config34['validators']] + + config36 = self.ton.GetConfig36() + next_el_id = config36['startWorkTime'] if config36['validators'] else 0 + next_vset = [i["adnlAddr"] for i in config36['validators']] + + for name in names: + # check that overlay still exists in mtc db + pure_name = name + suffix = name.split('_')[-1] + if suffix.startswith('elid') and suffix.split('elid')[-1].isdigit(): # probably election id + pure_name = '_'.join(name.split('_')[:-1]) + el_id = int(suffix.split('elid')[-1]) + if el_id not in (current_el_id, next_el_id): + self.ton.local.add_log(f"Overlay {name} is not in current or next election, deleting", "debug") + self.delete_custom_overlay_from_vc(name) # delete overlay if election id is not in current or next election + continue + + if pure_name not in self.ton.get_custom_overlays(): + self.ton.local.add_log(f"Overlay {name} ({pure_name}) is not in mtc db, deleting", "debug") + self.delete_custom_overlay_from_vc(name) # delete overlay if it's not in mtc db + + for name, config in self.ton.get_custom_overlays().items(): + if name in names: + continue + if '@validators' in config: + new_name = name + '_elid' + str(current_el_id) + if new_name not in names: + node_config = self.parse_config(new_name, config, current_vset) + self.add_custom_overlay_to_vc(node_config) + + if next_el_id != 0: + new_name = name + '_elid' + str(next_el_id) + if new_name not in names: + node_config = self.parse_config(new_name, config, next_vset) + self.add_custom_overlay_to_vc(node_config) + else: + node_config = self.parse_config(name, config) + self.add_custom_overlay_to_vc(node_config) + + def get_default_custom_overlay(self): + if not self.ton.local.db.get('useDefaultCustomOverlays', True): + return None + network = self.ton.GetNetworkName() + default_url = 'https://ton-blockchain.github.io/fallback_custom_overlays.json' + url = self.ton.local.db.get('defaultCustomOverlaysUrl', default_url) + resp = requests.get(url, timeout=3) + if resp.status_code != 200: + self.ton.local.add_log(f"Failed to get default custom overlays from {url}", "error") + return None + config = resp.json() + return config.get(network) + + def add_console_commands(self, console): + console.AddItem("add_custom_overlay", self.add_custom_overlay, self.local.translate("add_custom_overlay_cmd")) + console.AddItem("list_custom_overlays", self.list_custom_overlays, self.local.translate("list_custom_overlays_cmd")) + console.AddItem("delete_custom_overlay", self.delete_custom_overlay, self.local.translate("delete_custom_overlay_cmd")) diff --git a/modules/liteserver.py b/modules/liteserver.py new file mode 100644 index 00000000..35acb224 --- /dev/null +++ b/modules/liteserver.py @@ -0,0 +1,12 @@ +import psutil + +from modules.module import MtcModule + + +class LiteserverModule(MtcModule): + + description = 'For liteserver usage only without validator.' + default_value = False + + def add_console_commands(self, console): + ... diff --git a/modules/module.py b/modules/module.py new file mode 100644 index 00000000..b1ce709a --- /dev/null +++ b/modules/module.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod + + +class MtcModule(ABC): + + description = '' # module text description + default_value = True # is module enabled by default + + def __init__(self, ton, local, *args, **kwargs): + from mytoncore.mytoncore import MyTonCore + self.ton: MyTonCore = ton + self.local = local + + @abstractmethod + def add_console_commands(self, console): ... diff --git a/modules/nominator_pool.py b/modules/nominator_pool.py new file mode 100644 index 00000000..14f4fbdd --- /dev/null +++ b/modules/nominator_pool.py @@ -0,0 +1,93 @@ +import os +import time + +from mypylib.mypylib import color_print +from modules.pool import PoolModule + + +class NominatorPoolModule(PoolModule): + + description = 'Standard nominator pools.' + default_value = False + + def do_create_pool(self, pool_name, validator_reward_share_percent, max_nominators_count, min_validator_stake, + min_nominator_stake): + self.ton.local.add_log("start CreatePool function", "debug") + validator_reward_share = int(validator_reward_share_percent * 100) + + self.check_download_pool_contract_scripts() + + file_path = self.ton.poolsDir + pool_name + if os.path.isfile(file_path + ".addr"): + self.ton.local.add_log("CreatePool warning: Pool already exists: " + file_path, "warning") + return + # end if + + fift_script = self.ton.contractsDir + "nominator-pool/func/new-pool.fif" + wallet = self.ton.GetValidatorWallet() + args = [fift_script, wallet.addrB64, validator_reward_share, max_nominators_count, min_validator_stake, + min_nominator_stake, file_path] + result = self.ton.fift.Run(args) + if "Saved pool" not in result: + raise Exception("CreatePool error: " + result) + # end if + + pools = self.ton.GetPools() + new_pool = self.ton.GetLocalPool(pool_name) + for pool in pools: + if pool.name != new_pool.name and pool.addrB64 == new_pool.addrB64: + new_pool.Delete() + raise Exception("CreatePool error: Pool with the same parameters already exists.") + # end for + # end define + + def new_pool(self, args): + try: + pool_name = args[0] + validator_reward_share_percent = float(args[1]) + max_nominators_count = int(args[2]) + min_validator_stake = int(args[3]) + min_nominator_stake = int(args[4]) + except: + color_print("{red}Bad args. Usage:{endc} new_pool ") + return + self.do_create_pool(pool_name, validator_reward_share_percent, max_nominators_count, min_validator_stake, min_nominator_stake) + color_print("NewPool - {green}OK{endc}") + + def do_activate_pool(self, pool, ex=True): + self.ton.local.add_log("start ActivatePool function", "debug") + account = self.ton.GetAccount(pool.addrB64) + if account.status == "empty": + raise Exception("do_activate_pool error: account status is empty") + elif account.status == "active": + self.local.add_log("do_activate_pool warning: account status is active", "warning") + else: + validator_wallet = self.ton.GetValidatorWallet() + self.ton.check_account_active(validator_wallet.addrB64) + self.ton.SendFile(pool.bocFilePath, pool, timeout=False, remove=False) + #end define + + def activate_pool(self, args): + try: + pool_name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} activate_pool ") + return + pool = self.ton.GetLocalPool(pool_name) + self.do_activate_pool(pool) + color_print("ActivatePool - {green}OK{endc}") + + def update_validator_set(self, args): + try: + pool_addr = args[0] + except: + color_print("{red}Bad args. Usage:{endc} update_validator_set ") + return + wallet = self.ton.GetValidatorWallet() + self.ton.PoolUpdateValidatorSet(pool_addr, wallet) + color_print("UpdateValidatorSet - {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")) diff --git a/modules/pool.py b/modules/pool.py new file mode 100644 index 00000000..ba37b16c --- /dev/null +++ b/modules/pool.py @@ -0,0 +1,102 @@ +import os + +from mypylib.mypylib import color_print, print_table +from modules.module import MtcModule + + +class PoolModule(MtcModule): + + description = 'Basic pools functions.' + default_value = False + + def print_pools_list(self, args): + table = list() + table += [["Name", "Status", "Balance", "Version", "Address"]] + data = self.ton.GetPools() + if data is None or len(data) == 0: + print("No data") + return + for pool in data: + account = self.ton.GetAccount(pool.addrB64) + if account.status != "active": + pool.addrB64 = pool.addrB64_init + version = self.ton.GetVersionFromCodeHash(account.codeHash) + table += [[pool.name, account.status, account.balance, version, pool.addrB64]] + print_table(table) + + def delete_pool(self, args): + try: + pool_name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} delete_pool ") + return + pool = self.ton.GetLocalPool(pool_name) + pool.Delete() + color_print("DeletePool - {green}OK{endc}") + # end define + + def do_import_pool(self, pool_name, addr_b64): + self.check_download_pool_contract_scripts() + addr_bytes = self.ton.addr_b64_to_bytes(addr_b64) + pool_path = self.ton.poolsDir + pool_name + with open(pool_path + ".addr", 'wb') as file: + file.write(addr_bytes) + # end define + + def import_pool(self, args): + try: + pool_name = args[0] + pool_addr = args[1] + except: + color_print("{red}Bad args. Usage:{endc} import_pool ") + return + self.do_import_pool(pool_name, pool_addr) + color_print("import_pool - {green}OK{endc}") + + def check_download_pool_contract_scripts(self): + contract_path = self.ton.contractsDir + "nominator-pool/" + 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/single_pool.py b/modules/single_pool.py new file mode 100644 index 00000000..5b5c6e2b --- /dev/null +++ b/modules/single_pool.py @@ -0,0 +1,85 @@ +import os + +import pkg_resources + +from mypylib.mypylib import color_print +from modules.pool import PoolModule + + +class SingleNominatorModule(PoolModule): + + description = 'Orbs\'s single nominator pools.' + default_value = False + + def do_create_single_pool(self, pool_name, owner_address): + self.ton.local.add_log("start create_single_pool function", "debug") + + self.check_download_pool_contract_scripts() + + file_path = self.ton.poolsDir + pool_name + if os.path.isfile(file_path + ".addr"): + self.ton.local.add_log("create_single_pool warning: Pool already exists: " + file_path, "warning") + return + + fift_script = pkg_resources.resource_filename('mytoncore', 'contracts/single-nominator-pool/init.fif') + code_boc = pkg_resources.resource_filename('mytoncore', + 'contracts/single-nominator-pool/single-nominator-code.hex') + validator_wallet = self.ton.GetValidatorWallet() + args = [fift_script, code_boc, owner_address, validator_wallet.addrB64, file_path] + result = self.ton.fift.Run(args) + if "Saved single nominator pool" not in result: + raise Exception("create_single_pool error: " + result) + + pools = self.ton.GetPools() + new_pool = self.ton.GetLocalPool(pool_name) + for pool in pools: + if pool.name != new_pool.name and pool.addrB64 == new_pool.addrB64: + new_pool.Delete() + raise Exception("create_single_pool error: Pool with the same parameters already exists.") + + def new_single_pool(self, args): + try: + pool_name = args[0] + owner_address = args[1] + except: + color_print("{red}Bad args. Usage:{endc} new_single_pool ") + return + self.do_create_single_pool(pool_name, owner_address) + color_print("new_single_pool - {green}OK{endc}") + + def do_activate_single_pool(self, pool): + self.local.add_log("start activate_single_pool function", "debug") + boc_mode = "--with-init" + validator_wallet = self.ton.GetValidatorWallet() + self.ton.check_account_active(validator_wallet.addrB64) + result_file_path = self.ton.SignBocWithWallet(validator_wallet, pool.bocFilePath, pool.addrB64_init, 1, boc_mode=boc_mode) + self.ton.SendFile(result_file_path, validator_wallet) + + def activate_single_pool(self, args): + try: + pool_name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} activate_single_pool ") + return + pool = self.ton.GetLocalPool(pool_name) + if not os.path.isfile(pool.bocFilePath): + self.local.add_log(f"Pool {pool_name} already activated", "warning") + return + self.do_activate_single_pool(pool) + color_print("activate_single_pool - {green}OK{endc}") + + def withdraw_from_single_pool(self, args): + try: + pool_addr = args[0] + amount = float(args[1]) + except: + color_print("{red}Bad args. Usage:{endc} withdraw_from_single_pool ") + return + self.ton.WithdrawFromPoolProcess(pool_addr, amount) + color_print("withdraw_from_single_pool - {green}OK{endc}") + #end define + + def add_console_commands(self, console): + console.AddItem("new_single_pool", self.new_single_pool, self.local.translate("new_single_pool_cmd")) + console.AddItem("activate_single_pool", self.activate_single_pool, self.local.translate("activate_single_pool_cmd")) + console.AddItem("withdraw_from_single_pool", self.withdraw_from_single_pool, self.local.translate("withdraw_from_single_pool_cmd")) diff --git a/modules/validator.py b/modules/validator.py new file mode 100644 index 00000000..e3569f93 --- /dev/null +++ b/modules/validator.py @@ -0,0 +1,38 @@ +from mypylib.mypylib import color_print +from modules.module import MtcModule + + +class ValidatorModule(MtcModule): + + description = ('Validator functions. Activates participating in elections and staking. ' + 'If pools and l/s modes are disabled stakes from validator wallet.') + + default_value = True + + def vote_offer(self, args): + if len(args) == 0: + color_print("{red}Bad args. Usage:{endc} vo ") + return + for offerHash in args: + self.ton.VoteOffer(offerHash) + color_print("VoteOffer - {green}OK{endc}") + + def vote_election_entry(self, args): + from mytoncore.functions import Elections + Elections(self.ton.local, self.ton) + color_print("VoteElectionEntry - {green}OK{endc}") + + def vote_complaint(self, args): + try: + election_id = args[0] + complaint_hash = args[1] + except: + color_print("{red}Bad args. Usage:{endc} vc ") + return + self.ton.VoteComplaint(election_id, complaint_hash) + color_print("VoteComplaint - {green}OK{endc}") + + 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")) + console.AddItem("vc", self.vote_complaint, self.local.translate("vc_cmd")) diff --git a/mypyconsole b/mypyconsole index a8e8969a..a8f9f569 160000 --- a/mypyconsole +++ b/mypyconsole @@ -1 +1 @@ -Subproject commit a8e8969a6cac6bf96a34655504278d273adad98a +Subproject commit a8f9f56972192247f37ca8f43a5e723e29994c60 diff --git a/mypylib b/mypylib index 1d8fd217..049205ee 160000 --- a/mypylib +++ b/mypylib @@ -1 +1 @@ -Subproject commit 1d8fd2172639b7c6d61ded311d2c3a47d60c16b0 +Subproject commit 049205eee1650d7d847497d78acba68c5a33cc37 diff --git a/mytoncore/__init__.py b/mytoncore/__init__.py new file mode 100644 index 00000000..a39eb41f --- /dev/null +++ b/mytoncore/__init__.py @@ -0,0 +1,4 @@ +from .utils import * +from .mytoncore import * +from mypylib.mypylib import MyPyClass +from mypyconsole.mypyconsole import MyPyConsole diff --git a/mytoncore/__main__.py b/mytoncore/__main__.py new file mode 100644 index 00000000..d905da8f --- /dev/null +++ b/mytoncore/__main__.py @@ -0,0 +1,5 @@ +from mytoncore.functions import mytoncore + + +if __name__ == '__main__': + mytoncore() diff --git a/mytoncore/complaints/remove-proofs-v2.fif b/mytoncore/complaints/remove-proofs-v2.fif new file mode 100644 index 00000000..0079dfab --- /dev/null +++ b/mytoncore/complaints/remove-proofs-v2.fif @@ -0,0 +1,90 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Removes proof cells from complaint." cr cr + + ." is a filename of the serialized TL-B ValidatorComplaint boc." cr + ."Saves the result boc into ``." cr 1 halt +} : usage +$# 2 = { cr } { usage } cond + +$1 =: filename +$2 =: savefile + +filename file>B dup +8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if +B>boc =: complaint + +."got: " cr +complaint ref, + ref, + b> // s, c +} : clear_producer_info + +{ // c + // c +} : clean_descr + +{ // c + // c +} : clean_descr_with_diff + + +// prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block) +// prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo; +// +// no_blk_gen#450e8bd9 from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr; +// no_blk_gen_diff#c737b0ca prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr; +// +// validator_complaint#bc validator_pubkey:bits256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint; + +complaint =: result_cell + +"result: " type cr +result_cell B +savefile tuck B>file +."(Saved to file " type .")" cr diff --git a/mytoncore/contracts/__init__.py b/mytoncore/contracts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mytoncore/contracts/single-nominator-pool/HOWTO-deploy.md b/mytoncore/contracts/single-nominator-pool/HOWTO-deploy.md new file mode 100644 index 00000000..104b8897 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/HOWTO-deploy.md @@ -0,0 +1,25 @@ +# Deploy single-nominator-pool + +### 1. Generate state-init +Command: +``` +./init.fif +``` + +Example: +``` +./init.fif snominator-code.hex EQDYDK1NivLsfSVxYE1aUt5xU-behhWSin29vgE7M6wzLMjN Ef8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAU +``` + +### 2. Sign and send a message + +Command: +``` +./wallet-v3.fif 2 -n -I snominator-init.boc +``` + +Example: +``` +./wallet-v3.fif mywallet Ef9rfl-0S4wuAs6-rwl6RgjXznkhQaZNvlq9jMDHBlDpMe8h 698983191 7 1 -n -I snominator-init.boc +``` +Expects to have `mywallet.addr` `mywallet.pk` files. diff --git a/mytoncore/contracts/single-nominator-pool/README.md b/mytoncore/contracts/single-nominator-pool/README.md new file mode 100644 index 00000000..67c03df8 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/README.md @@ -0,0 +1,54 @@ +# Fift scripts for single-validator contract + +### Init + +usage: `./init.fif ` \ +Creates a state-init to deploy a single-nominator-pool contract. + +`` is a filename of the compiled contract code BoC bytes or HEX. \ +Saves the contract address in `snominator.addr`. \ +Saves the init boc into `snominator-state-init.boc`. + +### Wallet V3 (Modded) + +> A basic [wallet-v3.fif](https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/wallet-v3.fif) but with an init-state option for deploy. + +usage: +``` +./wallet-v3.fif + + [-x *] + [-n|-b] [-t] [-B ] + [-C ] [-I ] [] +``` + +Creates a request to advanced wallet created by `new-wallet-v3.fif`, \ +with private key loaded from file `.pk` \ +and address from `.addr`, and saves it \ +into `.boc` (`wallet-query.boc` by default). + +### Withdraw + +usage: `./withdraw.fif ` \ +Creates a message body to withdraw from a single-nominator pool. + +### Upgrade + +usage: `./upgrade.fif ` \ +Creates a message body to update the nominator's code. \ +Takes `` - BoC file path as argument. \ +Saves the result into `upgrade.boc`. + +### Change Validator Address + +usage: `./change-validator.fif ` \ +Takes user friendly address as parameter - not file. \ +Creates change validator action msg body BoC. \ +Saves it into `change-validator.boc`. + +### Send Raw Message + +usage: `./send-raw-msg.fif ` \ +Creates a request to send a message through single-nominator-poll. \ +`` - BoC full msg file path. \ +Saves the result msg body into `send-raw-msg.boc`. diff --git a/mytoncore/contracts/single-nominator-pool/build.sh b/mytoncore/contracts/single-nominator-pool/build.sh new file mode 100644 index 00000000..b7f3c5df --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/build.sh @@ -0,0 +1,2 @@ +/usr/bin/ton/crypto/func -APS /usr/src/ton/crypto/smartcont/stdlib.fc snominator-code.fc -W snominator-code.boc -o snominator-code.fif +/usr/bin/ton/crypto/fift snominator-code.fif diff --git a/mytoncore/contracts/single-nominator-pool/change-validator.fif b/mytoncore/contracts/single-nominator-pool/change-validator.fif new file mode 100755 index 00000000..478e08f5 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/change-validator.fif @@ -0,0 +1,23 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include +"Asm.fif" include +"GetOpt.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Takes user friendly address as parameter - not file." cr + ."Creates change validator action msg body BoC." cr + ."Saves it into `change-validator.boc`." cr 1 halt +} : usage +$# 1 = { } { usage } cond + +true constant bounce +true =: allow-bounce +false =: force-bounce + +$1 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: to_addr + + =: body_boc +body_boc B +"change-validator.boc" tuck B>file +."(Saved query to file " type .")" cr diff --git a/mytoncore/contracts/single-nominator-pool/init.fif b/mytoncore/contracts/single-nominator-pool/init.fif new file mode 100755 index 00000000..d102c8bb --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/init.fif @@ -0,0 +1,40 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a state-init to deploy a single-nominator-pool contract." cr cr + + ." is a filename of the compiled contract code BoC bytes or HEX." cr + ."Saves the contract address in `.addr`." cr + ."Saves the init boc into `-init.boc`." cr 1 halt +} : usage +$# 4 = { cr } { usage } cond + +$1 =: filename +$2 parse-smc-addr drop 2=: owner-addr +$3 parse-smc-addr drop 2=: validator-addr +$4 =: file-base + +filename file>B dup +8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if +B>boc =: new-code-boc + +-1 =: wc // masterchain +1 Gram* =: ton-amount + + // data + dup =: init-boc +2 boc+>B +dup ."StateInit: " B>base64 type cr cr +dup ."HEX: " Bx. cr +file-base +"-query.boc" tuck B>file +."(Saved single nominator pool init into " type .")" cr cr + +init-boc hashu wc swap 2dup 2=: dest-addr +."New pool address = " 2dup .addr cr + +2dup file-base +".addr" save-address-verbose cr + +."Non-bounceable address (for init): " 2dup 7 .Addr cr +."Bounceable address (for later access): " 6 .Addr cr cr diff --git a/mytoncore/contracts/single-nominator-pool/recover-stake.fif b/mytoncore/contracts/single-nominator-pool/recover-stake.fif new file mode 100644 index 00000000..871b3855 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/recover-stake.fif @@ -0,0 +1,17 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." []" cr + ."Creates the message body to be sent from a validator controlling smart contract (wallet) to recover its share of unfrozen stakes and bonuses." cr + ."The result is saved into (`recover-query.boc` by default) and output in hexadecimal form, to be sent later as the body of a message from the wallet to elections smart contract, along with a small value (say, one Gram) to cover forwarding and processing fees" cr 1 halt +} : usage + +$# dup 0 < swap 1 > or ' usage if +def? $1 { @' $1 } { "recover-query.boc" } cond constant output_fname +now constant query_id +."query_id for stake recovery message is set to " query_id . cr + + +cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr diff --git a/mytoncore/contracts/single-nominator-pool/send-raw-msg.fif b/mytoncore/contracts/single-nominator-pool/send-raw-msg.fif new file mode 100755 index 00000000..f6becdbc --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/send-raw-msg.fif @@ -0,0 +1,18 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr cr + ."Creates a request to send a message through single-nominator-poll." cr + ." - BoC full msg file path." cr cr + ."Saves the result msg body into `send-raw-msg.boc`." cr 1 halt +} : usage +$# 2 = { } { usage } cond + +$1 file>B B>boc =: msg +$2 (number) 1 <> abort"not an integer: check your send-mode" =: mode + + 2 boc+>B +."Message body is " dup B>base64 type cr cr +."HEX: " dup Bx. cr cr + +"send-raw-msg.boc" tuck B>file ."Saved to " type cr diff --git a/mytoncore/contracts/single-nominator-pool/single-nominator-code.fc b/mytoncore/contracts/single-nominator-pool/single-nominator-code.fc new file mode 100644 index 00000000..aedb357d --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/single-nominator-code.fc @@ -0,0 +1,222 @@ +;; https://github.com/orbs-network/single-nominator/blob/main/contracts/single-nominator.fc +;; this contract is very similar to https://github.com/ton-blockchain/nominator-pool but much simpler since it only supports a single nominator +;; frankly speaking, we tried using nominator-pool but it's so complicated that we couldn't be sure there were no bugs hiding around +;; this contract is very simple and easy to review, it is laser focused on protecting your stake and nothing else! + +;; =============== consts ============================= + +const BOUNCEABLE = 0x18; +const ADDRESS_SIZE = 256; +const MIN_TON_FOR_STORAGE = 1000000000; ;; 10% from nominator-pool since we have a single cell +const MIN_TON_FOR_SEND_MSG = 1200000000; + +;; owner ops +const OP::WITHDRAW = 0x1000; +const OP::CHANGE_VALIDATOR_ADDRESS = 0x1001; +const OP::SEND_RAW_MSG = 0x7702; +const OP::UPGRADE = 0x9903; + +;; elector ops +const OP::NEW_STAKE = 0x4e73744b; +const OP::RECOVER_STAKE = 0x47657424; + +;; modes +const MODE::SEND_MODE_REMAINING_AMOUNT = 64; + +;; errors +const ERROR::WRONG_NOMINATOR_WC = 0x2000; +const ERROR::WRONG_QUERY_ID = 0x2001; +const ERROR::WRONG_SET_CODE = 0x2002; +const ERROR::WRONG_VALIDATOR_WC = 0x2003; +const ERROR::INSUFFICIENT_BALANCE = 0x2004; +const ERROR::INSUFFICIENT_ELECTOR_FEE = 0x2005; + +;; =============== storage ============================= + +;; storage#_ owner_address:MsgAddressInt validator_address:MsgAddressInt = Storage + +(slice, slice) load_data() inline { + slice ds = get_data().begin_parse(); + + slice owner_address = ds~load_msg_addr(); + slice validator_address = ds~load_msg_addr(); + + ds.end_parse(); + + return (owner_address, validator_address); +} + +() save_data(slice owner_address, slice validator_address) impure inline { + set_data(begin_cell() + .store_slice(owner_address) + .store_slice(validator_address) + .end_cell()); +} + +;; =============== messages ============================= + +;; defined below +() send_msg(slice to_address, int amount, cell payload, int flags, int send_mode) impure inline_ref; +slice make_address(int wc, int addr) inline_ref; +slice elector_address() inline_ref; +int check_new_stake_msg(slice cs) impure inline_ref; + +;; main entry point for receiving messages +;; my_balance contains balance after adding msg_value +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + var (owner_address, validator_address) = load_data(); + + if (in_msg_body.slice_empty?()) { + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); ;; int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + if (flags & 1) { + ;; ignore all bounced messages + return (); + } + slice sender = cs~load_msg_addr(); + + int op = in_msg_body~load_uint(32); + int query_id = 0; ;; filled below + + ;; if just a message with comment "w" - means withdraw + if ( (op == 0) & (equal_slice_bits(in_msg_body, "w")) & (in_msg_body.slice_refs() == 0) ) { + op = OP::WITHDRAW; + ;; in few lines, amount to withdraw is loaded from in_msg_body, + ;; so we set it to the maximum avaliable balance + in_msg_body = begin_cell().store_coins(my_balance - msg_value - MIN_TON_FOR_STORAGE).end_cell().begin_parse(); + } else { + query_id = in_msg_body~load_uint(64); + } + + ;; owner role - cold wallet (private key that is not connected to the internet) that owns the funds used for staking and acts as the single nominator + if (equal_slice_bits(sender, owner_address)) { + + ;; allow owner to withdraw funds - take the money home and stop validating with it + if (op == OP::WITHDRAW) { + int amount = in_msg_body~load_coins(); + amount = min(amount, my_balance - msg_value - MIN_TON_FOR_STORAGE); + throw_unless(ERROR::INSUFFICIENT_BALANCE, amount > 0); + send_msg(owner_address, amount, null(), BOUNCEABLE, MODE::SEND_MODE_REMAINING_AMOUNT); ;; owner pays gas fees + } + + ;; mainly used when the validator was compromised to prevent validator from entering new election cycles + if (op == OP::CHANGE_VALIDATOR_ADDRESS) { + slice new_validator_address = in_msg_body~load_msg_addr(); + save_data(owner_address, new_validator_address); + } + + ;; emergency safeguard to allow owner to send arbitrary messages as the nominator contract + if (op == OP::SEND_RAW_MSG) { + int mode = in_msg_body~load_uint(8); + cell msg = in_msg_body~load_ref(); + send_raw_message(msg, mode); + } + + ;; second emergency safeguard to allow owner to replace nominator logic - you should never need to use this + if (op == OP::UPGRADE) { + cell code = in_msg_body~load_ref(); + throw_if(ERROR::WRONG_SET_CODE, cell_null?(code)); + set_code(code); + } + } + + ;; validator role - the wallet whose private key is on the validator node (can sign blocks but can't steal the funds used for stake) + if (equal_slice_bits(sender, validator_address)) { + + ;; send stake to the elector for the next validation cycle (sent every period ~18 hours) + if (op == OP::NEW_STAKE) { + (int sender_wc, _) = parse_std_addr(sender); + (int my_wc, _) = parse_std_addr(my_address()); + throw_unless(ERROR::WRONG_VALIDATOR_WC, sender_wc == -1); ;; for voting purpose + throw_unless(ERROR::WRONG_NOMINATOR_WC, my_wc == -1); ;; must be deployed on masterchain + throw_unless(ERROR::WRONG_QUERY_ID, query_id); ;; query_id must be greater then 0 to receive confirmation message from elector + throw_unless(ERROR::INSUFFICIENT_ELECTOR_FEE, msg_value >= MIN_TON_FOR_SEND_MSG); ;; must be greater then new_stake sending to elector fee + int stake_amount = in_msg_body~load_coins(); + slice msg = in_msg_body; + check_new_stake_msg(in_msg_body); + throw_unless(ERROR::INSUFFICIENT_BALANCE, stake_amount <= my_balance - msg_value - MIN_TON_FOR_STORAGE); + + send_msg(elector_address(), stake_amount, begin_cell().store_uint(OP::NEW_STAKE, 32).store_uint(query_id, 64).store_slice(msg).end_cell(), BOUNCEABLE, MODE::SEND_MODE_REMAINING_AMOUNT); ;; bounceable, validator pays gas fees + } + + ;; recover stake from elector of previous validation cycle (sent every period ~18 hours) + if (op == OP::RECOVER_STAKE) { + cell payload = begin_cell().store_uint(OP::RECOVER_STAKE, 32).store_uint(query_id, 64).end_cell(); + send_msg(elector_address(), 0, payload, BOUNCEABLE, MODE::SEND_MODE_REMAINING_AMOUNT); ;; bounceable, validator pays gas fees + } + } +} + +;; taken from nominator-pool: https://github.com/ton-blockchain/nominator-pool/blob/2f35c36b5ad662f10fd7b01ef780c3f1949c399d/func/pool.fc#L217 +() send_msg(slice to_address, int amount, cell payload, int flags, int send_mode) impure inline_ref { + int has_payload = ~ cell_null?(payload); + + builder msg = begin_cell() + .store_uint(flags, 6) + .store_slice(to_address) + .store_coins(amount) + .store_uint(has_payload ? 1 : 0, 1 + 4 + 4 + 64 + 32 + 1 + 1); + + if (has_payload) { + msg = msg.store_ref(payload); + } + + send_raw_message(msg.end_cell(), send_mode); +} + +;; taken from nominator-pool: https://github.com/ton-blockchain/nominator-pool/blob/2f35c36b5ad662f10fd7b01ef780c3f1949c399d/func/pool.fc#L68 +slice make_address(int wc, int addr) inline_ref { + return begin_cell() + .store_uint(4, 3).store_int(wc, 8).store_uint(addr, ADDRESS_SIZE).end_cell().begin_parse(); +} + +;; taken from nominator-pool: https://github.com/ton-blockchain/nominator-pool/blob/2f35c36b5ad662f10fd7b01ef780c3f1949c399d/func/pool.fc#L78 +slice elector_address() inline_ref { + int elector = config_param(1).begin_parse().preload_uint(ADDRESS_SIZE); + return make_address(-1, elector); +} + +;; taken from nominator-pool: https://github.com/ton-blockchain/nominator-pool/blob/2f35c36b5ad662f10fd7b01ef780c3f1949c399d/func/pool.fc#L139 +;; check the validity of the new_stake message +;; https://github.com/ton-blockchain/ton/blob/b38d227a469666d83ac535ad2eea80cb49d911b8/crypto/smartcont/elector-code.fc#L208 +int check_new_stake_msg(slice cs) impure inline_ref { + var validator_pubkey = cs~load_uint(256); + var stake_at = cs~load_uint(32); + var max_factor = cs~load_uint(32); + var adnl_addr = cs~load_uint(256); + var signature = cs~load_ref().begin_parse().preload_bits(512); + cs.end_parse(); + return stake_at; ;; supposed start of next validation round (utime_since) +} + +;; =============== getters ============================= + +(slice, slice) get_roles() method_id { + var (owner_address, validator_address) = load_data(); + return (owner_address, validator_address); +} + +;; nominator-pool interface with mytonctrl: https://github.com/ton-blockchain/nominator-pool/blob/2f35c36b5ad662f10fd7b01ef780c3f1949c399d/func/pool.fc#L198 +;; since we are relying on the existing interface between mytonctrl and nominator-pool, we return values that instruct mytonctrl +;; to recover stake on every cycle, although mytonctrl samples every 10 minutes we assume its current behavior that new_stake +;; and recover_stake are only sent once per cycle and don't waste gas +(int, int, int, int, (int, int, int, int, int), cell, cell, int, int, int, int, int, cell) get_pool_data() method_id { + return ( + 2, ;; state - funds staked at elector and should be recovered by mytonctrl + 1, ;; nominators_count - owner is the single nominator + 0, ;; stake_amount_sent - unused, mytonctrl does not rely on this param + 0, ;; validator_amount - unused, since gas is always paid by validator there is no concept of validator_amount + (0, 0, 0, 0, 0), ;; pool config - unused, since not inviting third party nominators + null(), ;; nominators - unused, mytonctrl does not rely on this param + null(), ;; withdraw_requests - unused, not needed since owner controls the validator + 0, ;; stake_at - unused, mytonctrl does not rely on this param + 0, ;; saved_validator_set_hash - unused, required for maintaining validator_amount that we don't need + 2, ;; validator_set_changes_count - funds staked at elector and should be recovered by mytonctrl + 0, ;; validator_set_change_time - back in the past so mytonctrl will always attempt to recover stake + 0, ;; stake_held_for - back in the past so mytonctrl will always attempt to recover stake + null() ;; config_proposal_votings - unused, not needed since owner controls the validator + ); +} diff --git a/mytoncore/contracts/single-nominator-pool/single-nominator-code.hex b/mytoncore/contracts/single-nominator-pool/single-nominator-code.hex new file mode 100644 index 00000000..ee02a24e --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/single-nominator-code.hex @@ -0,0 +1 @@ +b5ee9c7241020d010001f0000114ff00f4a413f4bcf2c80b01020162050202012004030015bfe5076a2687d207d2068c0027bdf8cb938b82a38002a380036b6aa39152988b6c02bcd0ed44d0fa40fa40d122c700925f06e003d0d3030171b0925f06e0fa403002d31f7022c000228b1778c705b022d74ac000b08e136c21830bc85376a182103b9aca00a1fa02c9d09430d33f12e25343c7059133e30d5235c705925f06e30d0b0604f22382104e73744bba8fe102fa4430f828fa443081200302c0ff12f2f4830c01c0fff2f481200122f2f481200524821047868c00bef2f4fa0020db3c300581200405a182103b9aca00a15210bb14f2f4db3c82104e73744bc8cb1f5220cb3f5005cf16c9443080188040db3c9410356c41e201821047657424ba0a080c0702368f16821047657424c8cb1fcb3fc9db3c705880188040db3c9130e2080c011671f833d0d70bff7f01db3c09001674c8cb0212ca07cbffc9d0001cd3ff31d31fd31f31d3ff31d431d101c421830bba8ea0fa005387a182103b9aca00a112b60881200421c200f2f452406d80188040db3cde21811001ba9efa405044c858cf1601cf16c9ed549133e220817702ba9802d307d402fb0002de2082009903ba9d02d4812002226ef2f201fb0402de0c0048226eb32091719170e203c8cb055006cf165004fa02cb6a039358cc019130e201c901fb00fb5470d1 diff --git a/mytoncore/contracts/single-nominator-pool/single-nominator.tlb b/mytoncore/contracts/single-nominator-pool/single-nominator.tlb new file mode 100644 index 00000000..d1470a01 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/single-nominator.tlb @@ -0,0 +1,94 @@ +bit$_ (## 1) = Bit; +bool_false$0 = Bool; +bool_true$1 = Bool; + +left$0 {X:Type} {Y:Type} value:X = Either X Y; +right$1 {X:Type} {Y:Type} value:Y = Either X Y; + +// + +hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) + {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; + +hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; +hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) + right:^(Hashmap n X) = HashmapNode (n + 1) X; + +hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; +hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; +hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; + +unary_zero$0 = Unary ~0; +unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); + +hme_empty$0 {n:#} {X:Type} = HashmapE n X; +hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X; + +// + +nothing$0 {X:Type} = Maybe X; +just$1 {X:Type} value:X = Maybe X; + +anycast_info$_ depth:(#<= 30) { depth >= 1 } + rewrite_pfx:(bits depth) = Anycast; + +addr_std$10 anycast:(Maybe Anycast) + workchain_id:int8 address:bits256 = MsgAddressInt; +_ _:MsgAddressInt = MsgAddress; + +_ address:MsgAddress = Addr; + +// + +var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) + = VarUInteger n; +var_int$_ {n:#} len:(#< n) value:(int (len * 8)) + = VarInteger n; +nanograms$_ amount:(VarUInteger 16) = Grams; + +_ grams:Grams = Coins; + +// + +extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) + = ExtraCurrencyCollection; +currencies$_ grams:Grams other:ExtraCurrencyCollection + = CurrencyCollection; + +// + +tick_tock$_ tick:Bool tock:Bool = TickTock; + +_ split_depth:(Maybe (## 5)) special:(Maybe TickTock) + code:(Maybe ^Cell) data:(Maybe ^Cell) + library:(Maybe ^Cell) = StateInit; + +int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + src:MsgAddressInt dest:MsgAddressInt + value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams + created_lt:uint64 created_at:uint32 = CommonMsgInfo; + +message$_ {X:Type} info:CommonMsgInfo + init:(Maybe (Either StateInit ^StateInit)) + body:(Either X ^X) = Message X; + +_ (Message Any) = MessageAny; + +// + +storage#_ owner_address:MsgAddress validator_address:MsgAddress = Storage; + +// owner ops +withdraw#1000 query_id:uint64 amount:Coins = InternalMsgBody; +change_validator_address#1001 query_id:uint64 new_validator_address:MsgAddress = InternalMsgBody; +send_raw_msg#7702 query_id:uint64 mode:uint8 msg:^MessageAny = InternalMsgBody; +upgrade#9903 query_id:uint64 code:^Cell = InternalMsgBody; + +// elector ops +new_stakedata#_ validator_pubkey:bits256 stake_at:uint32 max_factor:uint32 andl_addr:bits256 signature:^bits512 = NewStakeData; + +// 2 opcodes respond for 2 InternalMsgBody schemes +// so to avoid errors - here is OutMsgBody +new_stake_to_validator#4e73744b query_id:uint64 stake_data:NewStakeData = OutMsgBody; +new_stake#4e73744b query_id:uint64 stake_amount:Coins new_stake_msg:NewStakeData = InternalMsgBody; +recover_stake#47657424 query_id:uint64 = InternalMsgBody; diff --git a/mytoncore/contracts/single-nominator-pool/upgrade.fif b/mytoncore/contracts/single-nominator-pool/upgrade.fif new file mode 100755 index 00000000..cb426327 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/upgrade.fif @@ -0,0 +1,18 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr cr + ."Creates a message body to update the nominator's code." cr + ."Takes - BoC file path as argument." cr cr + ."Saves the result into `upgrade.boc`." cr 1 halt +} : usage +$# 1 = { } { usage } cond + +$1 file>B dup +8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if +B>boc =: new-code-boc + + 2 boc+>B +."Message body is " dup B>base64 type cr + +"upgrade.boc" tuck B>file ."Saved to " type cr diff --git a/mytoncore/contracts/single-nominator-pool/validator-elect-signed.fif b/mytoncore/contracts/single-nominator-pool/validator-elect-signed.fif new file mode 100644 index 00000000..a7c42da5 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/validator-elect-signed.fif @@ -0,0 +1,44 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a message body for participating in validator elections starting at on behalf of smart-contract with address (prefix with '@' to load address from file) and hexadecimal adnl address (empty string or '0' for none)." cr + ." is the main public key of the future validator (as a Base64 string), and is the signature of the previously created validator request by that key (also Base64)" cr + ."The result is saved into (`validator-query.boc` by default) and output in hexadecimal form, to be sent later as the body of a message from to elections smart contract, along with the desired stake" cr 1 halt +} : usage + +$# dup 8 < swap 8 > or ' usage if +$1 true parse-load-address drop swap 1+ abort"only masterchain smartcontracts may participate in validator elections" +constant src_addr +$2 (number) 1 <> { 0 } if dup 0<= abort" must be a positive integer" +constant elect_time +$3 (number) dup 0= abort" must be a real number 1..100" +1 = { 16 << } { 16 < or abort" must be a real number 1..100" +constant max_factor +$4 dup $len 1 > { parse-adnl-address } { drop 0 } cond constant adnl_addr +$5 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" + 32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number" + constant pubkey +$6 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" +constant signature +$7 constant output_fname +$8 $>GR =: amount + +."Creating a request to participate in validator elections at time " elect_time . +."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr +." with maximal stake factor with respect to the minimal stake " max_factor ._ +."/65536 and validator ADNL address " adnl_addr 64x. cr + +B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ +."String to sign is: " dup Bx. cr constant to_sign + +to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid" +."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr +now dup constant query_id ."query_id set to " . cr + + ref, b> +cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr diff --git a/mytoncore/contracts/single-nominator-pool/validator-withdraw.fif b/mytoncore/contracts/single-nominator-pool/validator-withdraw.fif new file mode 100644 index 00000000..387f4bc1 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/validator-withdraw.fif @@ -0,0 +1,18 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." []" cr + ."" cr + ."" cr 1 halt +} : usage + +$# dup 1 < swap 2 > or ' usage if +$1 $>GR =: amount +def? $2 { @' $2 } { "validator-withdraw-query.boc" } cond constant output_fname +now constant query_id +."query_id for stake recovery message is set to " query_id . ."amount=" amount .GR cr + + +cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr diff --git a/mytoncore/contracts/single-nominator-pool/wallet-v3.fif b/mytoncore/contracts/single-nominator-pool/wallet-v3.fif new file mode 100755 index 00000000..f44e0c92 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/wallet-v3.fif @@ -0,0 +1,85 @@ +#!/usr/bin/fift -s +"TonUtil.fif" include +"GetOpt.fif" include + +{ show-options-help 1 halt } : usage + +"" =: comment // comment for simple transfers +true =: allow-bounce +false =: force-bounce +3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors +60 =: timeout // external message expires in 60 seconds +variable extra-currencies +{ extra-currencies @ cc+ extra-currencies ! } : extra-cc+! + +begin-options + " [-x *] [-n|-b] [-t] [-B ] [-C ] [-I ] []" +cr +tab + +"Creates a request to advanced wallet created by new-wallet-v3.fif, with private key loaded from file .pk " + +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" + disable-digit-options generic-help-setopt + "n" "--no-bounce" { false =: allow-bounce } short-long-option + "Clears bounce flag" option-help + "b" "--force-bounce" { true =: force-bounce } short-long-option + "Forces bounce flag" option-help + "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg + "Indicates the amount of extra currencies to be transfered" option-help + "t" "--timeout" { parse-int =: timeout } short-long-option-arg + "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help + "B" "--body" { =: body-boc-file } short-long-option-arg + "Sets the payload of the transfer message" option-help + "C" "--comment" { =: comment } short-long-option-arg + "Sets the comment to be sent in the transfer message" option-help + "I" "--with-init" { =: init-file } short-long-option-arg + "Indicates filename with BoC containing StateInit for internal message" option-help + "m" "--mode" { parse-int =: send-mode } short-long-option-arg + "Sets transfer mode (0..255) for SENDRAWMSG (" send-mode (.) $+ +" by default)" + option-help + "h" "--help" { usage } short-long-option + "Shows a help message" option-help +parse-options + +$# dup 5 < swap 6 > or ' usage if +6 :$1..n + +true constant bounce +$1 =: file-base +$2 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: dest_addr +$3 parse-int =: subwallet_id +$4 parse-int =: seqno +$5 $>cc extra-cc+! extra-currencies @ 2=: amount +$6 "wallet-query" replace-if-null =: savefile +subwallet_id (.) 1 ' $+ does : +subwallet + +file-base +subwallet +".addr" dup file-exists? { drop file-base +".addr" } ifnot +load-address +2dup 2constant wallet_addr +."Source wallet address = " 2dup .addr cr 6 .Addr cr +file-base +".pk" load-keypair nip constant wallet_pk + +def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond +constant body-cell + +def? init-file { @' init-file file>B B>boc + +dup ."signing message: " +dup ."resulting external message: " B dup Bx. cr +savefile +".boc" tuck B>file +."Query expires in " timeout . ."seconds" cr +."(Saved to file " type .")" cr diff --git a/mytoncore/contracts/single-nominator-pool/withdraw.fif b/mytoncore/contracts/single-nominator-pool/withdraw.fif new file mode 100755 index 00000000..fa8a0976 --- /dev/null +++ b/mytoncore/contracts/single-nominator-pool/withdraw.fif @@ -0,0 +1,16 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include +"Asm.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a message body to withdraw from a single-nominator pool." cr 1 halt +} : usage +$# 1 = { } { usage } cond + +$1 $>GR =: amount + + =: body_boc +body_boc B +"withdraw.boc" tuck B>file +."(Saved witdhraw query to file to file " type .")" cr diff --git a/mytoncore/fift.py b/mytoncore/fift.py new file mode 100644 index 00000000..291b3707 --- /dev/null +++ b/mytoncore/fift.py @@ -0,0 +1,27 @@ +import subprocess + + +class Fift: + def __init__(self, local): + self.local = local + self.appPath = None + self.libsPath = None + self.smartcontsPath = None + #end define + + def Run(self, args, **kwargs): + fift_timeout = self.local.db.fift_timeout if self.local.db.fift_timeout else 3 + timeout = kwargs.get("timeout", fift_timeout) + for i in range(len(args)): + args[i] = str(args[i]) + includePath = self.libsPath + ':' + self.smartcontsPath + args = [self.appPath, "-I", includePath, "-s"] + args + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) + output = process.stdout.decode("utf-8") + err = process.stderr.decode("utf-8") + if len(err) > 0: + self.local.add_log("args: {args}".format(args=args), "error") + raise Exception("Fift error: {err}".format(err=err)) + return output + #end define +#end class diff --git a/mytoncore/functions.py b/mytoncore/functions.py new file mode 100755 index 00000000..0cca873a --- /dev/null +++ b/mytoncore/functions.py @@ -0,0 +1,586 @@ +#!/usr/bin/env python3 +# -*- coding: utf_8 -*-l +import os +import sys +import psutil +import time +import json +import base64 +import requests +import subprocess + +from mytoncore.mytoncore import MyTonCore +from mytonctrl.utils import fix_git_config +from mytoninstaller.config import GetConfig +from mypylib.mypylib import ( + b2mb, + get_timestamp, + get_internet_interface_name, + get_git_hash, + get_load_avg, + thr_sleep, +) +from mytoncore.telemetry import * +from mytoninstaller.node_args import get_node_args + + +def Init(local): + # Event reaction + if ("-e" in sys.argv): + x = sys.argv.index("-e") + event_name = sys.argv[x+1] + Event(local, event_name) + # end if + + local.run() + + # statistics + local.buffer.blocksData = dict() + local.buffer.transData = dict() + local.buffer.network = [None]*15*6 + local.buffer.diskio = [None]*15*6 + + # scan blocks + local.buffer.masterBlocksList = list() + local.buffer.prevShardsBlock = dict() + local.buffer.blocksNum = 0 + local.buffer.transNum = 0 +# end define + + +def Event(local, event_name): + if event_name == "enableVC": + EnableVcEvent(local) + elif event_name == "validator down": + 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) + local.exit() +# end define + + +def EnableVcEvent(local): + local.add_log("start EnableVcEvent function", "debug") + # Создать новый кошелек для валидатора + ton = MyTonCore(local) + wallet = ton.CreateWallet("validator_wallet_001", -1) + local.db["validatorWalletName"] = wallet.name + + # Создать новый ADNL адрес для валидатора + adnlAddr = ton.CreateNewKey() + ton.AddAdnlAddrToValidator(adnlAddr) + local.db["adnlAddr"] = adnlAddr + + # Сохранить + local.save() +# end define + + +def ValidatorDownEvent(local): + local.add_log("start ValidatorDownEvent function", "debug") + local.add_log("Validator is down", "error") +# end define + + +def enable_ton_storage_provider_event(local): + config_path = local.db.ton_storage.provider.config_path + config = GetConfig(path=config_path) + key_bytes = base64.b64decode(config.ProviderKey) + ton = MyTonCore(local) + ton.import_wallet_with_version(key_bytes[:32], version="v3r2", wallet_name="provider_wallet_001") +#end define + + +def enable_liteserver_mode(local): + ton = MyTonCore(local) + ton.disable_mode('validator') + ton.enable_mode('liteserver') +#end define + + +def Elections(local, ton): + use_pool = ton.using_pool() + use_liquid_staking = ton.using_liquid_staking() + if use_pool: + ton.PoolsUpdateValidatorSet() + if use_liquid_staking: + ton.ControllersUpdateValidatorSet() + ton.RecoverStake() + if ton.using_validator(): + ton.ElectionEntry() + + +def Statistics(local): + ReadNetworkData(local) + SaveNetworkStatistics(local) + # ReadTransData(local, scanner) + SaveTransStatistics(local) + ReadDiskData(local) + SaveDiskStatistics(local) +# end define + + +def ReadDiskData(local): + timestamp = get_timestamp() + disks = GetDisksList() + buff = psutil.disk_io_counters(perdisk=True) + data = dict() + for name in disks: + data[name] = dict() + data[name]["timestamp"] = timestamp + data[name]["busyTime"] = buff[name].busy_time + data[name]["readBytes"] = buff[name].read_bytes + data[name]["writeBytes"] = buff[name].write_bytes + data[name]["readCount"] = buff[name].read_count + data[name]["writeCount"] = buff[name].write_count + # end for + + local.buffer.diskio.pop(0) + local.buffer.diskio.append(data) +# end define + + +def SaveDiskStatistics(local): + data = local.buffer.diskio + data = data[::-1] + zerodata = data[0] + buff1 = data[1*6-1] + buff5 = data[5*6-1] + buff15 = data[15*6-1] + if buff5 is None: + buff5 = buff1 + if buff15 is None: + buff15 = buff5 + # end if + + disksLoadAvg = dict() + disksLoadPercentAvg = dict() + iopsAvg = dict() + disks = GetDisksList() + for name in disks: + if zerodata[name]["busyTime"] == 0: + continue + diskLoad1, diskLoadPercent1, iops1 = CalculateDiskStatistics( + zerodata, buff1, name) + diskLoad5, diskLoadPercent5, iops5 = CalculateDiskStatistics( + zerodata, buff5, name) + diskLoad15, diskLoadPercent15, iops15 = CalculateDiskStatistics( + zerodata, buff15, name) + disksLoadAvg[name] = [diskLoad1, diskLoad5, diskLoad15] + disksLoadPercentAvg[name] = [diskLoadPercent1, + diskLoadPercent5, diskLoadPercent15] + iopsAvg[name] = [iops1, iops5, iops15] + # end fore + + # save statistics + statistics = local.db.get("statistics", dict()) + statistics["disksLoadAvg"] = disksLoadAvg + statistics["disksLoadPercentAvg"] = disksLoadPercentAvg + statistics["iopsAvg"] = iopsAvg + local.db["statistics"] = statistics +# end define + + +def CalculateDiskStatistics(zerodata, data, name): + if data is None: + return None, None, None + data = data[name] + zerodata = zerodata[name] + timeDiff = zerodata["timestamp"] - data["timestamp"] + busyTimeDiff = zerodata["busyTime"] - data["busyTime"] + diskReadDiff = zerodata["readBytes"] - data["readBytes"] + diskWriteDiff = zerodata["writeBytes"] - data["writeBytes"] + diskReadCountDiff = zerodata["readCount"] - data["readCount"] + diskWriteCountDiff = zerodata["writeCount"] - data["writeCount"] + diskLoadPercent = busyTimeDiff / 1000 / timeDiff * \ + 100 # /1000 - to second, *100 - to percent + diskLoadPercent = round(diskLoadPercent, 2) + diskRead = diskReadDiff / timeDiff + diskWrite = diskWriteDiff / timeDiff + diskReadCount = diskReadCountDiff / timeDiff + diskWriteCount = diskWriteCountDiff / timeDiff + diskLoad = b2mb(diskRead + diskWrite) + iops = round(diskReadCount + diskWriteCount, 2) + return diskLoad, diskLoadPercent, iops +# end define + + +def GetDisksList(): + data = list() + buff = os.listdir("/sys/block/") + for item in buff: + if "loop" in item: + continue + data.append(item) + # end for + data.sort() + return data +# end define + + +def ReadNetworkData(local): + timestamp = get_timestamp() + interfaceName = get_internet_interface_name() + buff = psutil.net_io_counters(pernic=True) + buff = buff[interfaceName] + data = dict() + data["timestamp"] = timestamp + data["bytesRecv"] = buff.bytes_recv + data["bytesSent"] = buff.bytes_sent + data["packetsSent"] = buff.packets_sent + data["packetsRecv"] = buff.packets_recv + + local.buffer.network.pop(0) + local.buffer.network.append(data) +# end define + + +def SaveNetworkStatistics(local): + data = local.buffer.network + data = data[::-1] + zerodata = data[0] + buff1 = data[1*6-1] + buff5 = data[5*6-1] + buff15 = data[15*6-1] + if buff5 is None: + buff5 = buff1 + if buff15 is None: + buff15 = buff5 + # end if + + netLoadAvg = dict() + ppsAvg = dict() + networkLoadAvg1, ppsAvg1 = CalculateNetworkStatistics(zerodata, buff1) + networkLoadAvg5, ppsAvg5 = CalculateNetworkStatistics(zerodata, buff5) + networkLoadAvg15, ppsAvg15 = CalculateNetworkStatistics(zerodata, buff15) + netLoadAvg = [networkLoadAvg1, networkLoadAvg5, networkLoadAvg15] + ppsAvg = [ppsAvg1, ppsAvg5, ppsAvg15] + + # save statistics + statistics = local.db.get("statistics", dict()) + statistics["netLoadAvg"] = netLoadAvg + statistics["ppsAvg"] = ppsAvg + local.db["statistics"] = statistics +# end define + + +def CalculateNetworkStatistics(zerodata, data): + if data is None: + return None, None + timeDiff = zerodata["timestamp"] - data["timestamp"] + bytesRecvDiff = zerodata["bytesRecv"] - data["bytesRecv"] + bytesSentDiff = zerodata["bytesSent"] - data["bytesSent"] + packetsRecvDiff = zerodata["packetsRecv"] - data["packetsRecv"] + packetsSentDiff = zerodata["packetsSent"] - data["packetsSent"] + bitesRecvAvg = bytesRecvDiff / timeDiff * 8 + bitesSentAvg = bytesSentDiff / timeDiff * 8 + packetsRecvAvg = packetsRecvDiff / timeDiff + packetsSentAvg = packetsSentDiff / timeDiff + netLoadAvg = b2mb(bitesRecvAvg + bitesSentAvg) + ppsAvg = round(packetsRecvAvg + packetsSentAvg, 2) + return netLoadAvg, ppsAvg +# end define + + +def ReadTransData(local, scanner): + transData = local.buffer.transData + SetToTimeData(transData, scanner.transNum) + ShortTimeData(transData) +# end define + + +def SetToTimeData(timeDataList, data): + timenow = int(time.time()) + timeDataList[timenow] = data +# end define + + +def ShortTimeData(data, max=120, diff=20): + if len(data) < max: + return + buff = data.copy() + data.clear() + keys = sorted(buff.keys(), reverse=True) + for item in keys[:max-diff]: + data[item] = buff[item] +# end define + + +def SaveTransStatistics(local): + tps1 = GetTps(local, 60) + tps5 = GetTps(local, 60*5) + tps15 = GetTps(local, 60*15) + + # save statistics + statistics = local.db.get("statistics", dict()) + statistics["tpsAvg"] = [tps1, tps5, tps15] + local.db["statistics"] = statistics +# end define + + +def GetDataPerSecond(data, timediff): + if len(data) == 0: + return + timenow = sorted(data.keys())[-1] + now = data.get(timenow) + prev = GetItemFromTimeData(data, timenow-timediff) + if prev is None: + return + diff = now - prev + result = diff / timediff + result = round(result, 2) + return result +# end define + + +def GetItemFromTimeData(data, timeneed): + if timeneed in data: + result = data.get(timeneed) + else: + result = data[min(data.keys(), key=lambda k: abs(k-timeneed))] + return result +# end define + + +def GetTps(local, timediff): + data = local.buffer.transData + tps = GetDataPerSecond(data, timediff) + return tps +# end define + + +def GetBps(local, timediff): + data = local.buffer.blocksData + bps = GetDataPerSecond(data, timediff) + return bps +# end define + + +def GetBlockTimeAvg(local, timediff): + bps = GetBps(local, timediff) + if bps is None or bps == 0: + return + result = 1/bps + result = round(result, 2) + return result +# end define + + +def Offers(local, ton): + save_offers = ton.GetSaveOffers() + offers = ton.GetOffers() + for offer in offers: + offer_hash = offer.get("hash") + if offer_hash in save_offers: + offer_pseudohash = offer.get("pseudohash") + save_offer = save_offers.get(offer_hash) + if isinstance(save_offer, list): # new version of save offers {"hash": ["pseudohash", param_id]} + save_offer_pseudohash = save_offer[0] + else: # old version of save offers {"hash": "pseudohash"} + save_offer_pseudohash = save_offer + if offer_pseudohash == save_offer_pseudohash and offer_pseudohash is not None: + 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: + return + # end if + + # Get validator status + data = dict() + data["adnlAddr"] = ton.GetAdnlAddr() + data["validatorStatus"] = ton.GetValidatorStatus() + data["cpuNumber"] = psutil.cpu_count() + data["cpuLoad"] = get_load_avg() + data["netLoad"] = ton.GetStatistics("netLoadAvg") + data["tps"] = ton.GetStatistics("tpsAvg") + data["disksLoad"] = ton.GetStatistics("disksLoadAvg") + data["disksLoadPercent"] = ton.GetStatistics("disksLoadPercentAvg") + data["iops"] = ton.GetStatistics("iopsAvg") + data["pps"] = ton.GetStatistics("ppsAvg") + data["dbUsage"] = ton.GetDbUsage() + data["memory"] = GetMemoryInfo() + data["swap"] = GetSwapInfo() + data["uname"] = GetUname() + data["vprocess"] = GetValidatorProcessInfo() + data["dbStats"] = local.try_function(get_db_stats) + data["nodeArgs"] = local.try_function(get_node_args) + data["cpuInfo"] = {'cpuName': local.try_function(get_cpu_name), 'virtual': local.try_function(is_host_virtual)} + data["validatorDiskName"] = local.try_function(get_validator_disk_name) + data["pings"] = local.try_function(get_pings_values) + + # Get git hashes + gitHashes = dict() + mtc_path = "/usr/src/mytonctrl" + local.try_function(fix_git_config, args=[mtc_path]) + gitHashes["mytonctrl"] = get_git_hash(mtc_path) + gitHashes["validator"] = GetBinGitHash( + "/usr/bin/ton/validator-engine/validator-engine") + data["gitHashes"] = gitHashes + data["stake"] = local.db.get("stake") + + # Get validator config + vconfig = ton.GetValidatorConfig() + data["fullnode_adnl"] = vconfig.fullnode + + # Send data to toncenter server + liteUrl_default = "https://telemetry.toncenter.com/report_status" + liteUrl = local.db.get("telemetryLiteUrl", liteUrl_default) + output = json.dumps(data) + resp = requests.post(liteUrl, data=output, timeout=3) +# end define + + +def GetBinGitHash(path, short=False): + if not os.path.isfile(path): + return + args = [path, "--version"] + process = subprocess.run(args, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) + output = process.stdout.decode("utf-8") + if "build information" not in output: + return + buff = output.split(' ') + start = buff.index("Commit:") + 1 + result = buff[start].replace(',', '') + if short is True: + result = result[:7] + return result +# end define + + +def OverlayTelemetry(local, ton): + sendTelemetry = local.db.get("sendTelemetry") + if sendTelemetry is not True: + return + # end if + + # Get validator status + data = dict() + data["adnlAddr"] = ton.GetAdnlAddr() + data["overlaysStats"] = ton.GetOverlaysStats() + + # Send data to toncenter server + overlayUrl_default = "https://telemetry.toncenter.com/report_overlays" + overlayUrl = local.db.get("overlayTelemetryUrl", overlayUrl_default) + output = json.dumps(data) + resp = requests.post(overlayUrl, data=output, timeout=3) +# end define + + +def Complaints(local, ton): + validatorIndex = ton.GetValidatorIndex() + if validatorIndex < 0: + return + # end if + + # Voting for complaints + config32 = ton.GetConfig32() + election_id = config32.get("startWorkTime") + complaints = ton.GetComplaints(election_id) # get complaints from Elector + valid_complaints = ton.get_valid_complaints(complaints, election_id) + for c in valid_complaints.values(): + complaint_hash = c.get("hash") + ton.VoteComplaint(election_id, complaint_hash) +# end define + + +def Slashing(local, ton): + is_slashing = local.db.get("isSlashing") + is_validator = ton.using_validator() + if is_slashing is not True or not is_validator: + return + + # Creating complaints + slash_time = local.buffer.slash_time + config32 = ton.GetConfig32() + start = config32.get("startWorkTime") + end = config32.get("endWorkTime") + config15 = ton.GetConfig15() + ts = get_timestamp() + if not(end < ts < end + config15['stakeHeldFor']): # check that currently is freeze time + return + local.add_log("slash_time {}, start {}, end {}".format(slash_time, start, end), "debug") + if slash_time != start: + end -= 60 + ton.CheckValidators(start, end) + local.buffer.slash_time = start +# end define + + +def save_past_events(local, ton): + local.try_function(ton.GetElectionEntries) + local.try_function(ton.GetComplaints) + + +def ScanLiteServers(local, ton): + # Считать список серверов + filePath = ton.liteClient.configPath + file = open(filePath, 'rt') + text = file.read() + file.close() + data = json.loads(text) + + # Пройтись по серверам + result = list() + liteservers = data.get("liteservers") + for index in range(len(liteservers)): + try: + ton.liteClient.Run("last", index=index) + result.append(index) + except: + pass + # end for + + # Записать данные в базу + local.db["liteServers"] = result +# end define + + +def General(local): + local.add_log("start General function", "debug") + ton = MyTonCore(local) + # scanner = Dict() + # scanner.Run() + + # Start threads + local.start_cycle(Elections, sec=600, args=(local, ton, )) + local.start_cycle(Statistics, sec=10, args=(local, )) + local.start_cycle(Offers, sec=600, args=(local, ton, )) + local.start_cycle(save_past_events, sec=300, args=(local, ton, )) + + t = 600 + if ton.GetNetworkName() != 'mainnet': + t = 60 + 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,)) + + from modules.custom_overlays import CustomOverlayModule + local.start_cycle(CustomOverlayModule(ton, local).custom_overlays, sec=60, args=()) + + thr_sleep() +# end define + + +def mytoncore(): + from mypylib.mypylib import MyPyClass + + local = MyPyClass('mytoncore.py') + print('Local DB path:', local.buffer.db_path) + Init(local) + General(local) diff --git a/mytoncore/liteclient.py b/mytoncore/liteclient.py new file mode 100644 index 00000000..67f4ed9f --- /dev/null +++ b/mytoncore/liteclient.py @@ -0,0 +1,43 @@ +import random +import subprocess + + +class LiteClient: + def __init__(self, local): + self.local = local + self.appPath = None + self.configPath = None + self.pubkeyPath = None + self.addr = None + self.ton = None # magic + #end define + + def Run(self, cmd, **kwargs): + index = kwargs.get("index") + liteclient_timeout = self.local.db.liteclient_timeout if self.local.db.liteclient_timeout else 3 + timeout = kwargs.get("timeout", liteclient_timeout) + useLocalLiteServer = kwargs.get("useLocalLiteServer", True) + validator_status = self.ton.GetValidatorStatus() + args = [self.appPath, "--global-config", self.configPath, "--verbosity", "0", "--cmd", cmd] + if index is not None: + index = str(index) + args += ["-i", index] + elif useLocalLiteServer and self.pubkeyPath and validator_status.out_of_sync and validator_status.out_of_sync < 20: + args = [self.appPath, "--addr", self.addr, "--pub", self.pubkeyPath, "--verbosity", "0", "--cmd", cmd] + else: + liteServers = self.local.db.get("liteServers") + if liteServers is not None and len(liteServers): + index = random.choice(liteServers) + index = str(index) + args += ["-i", index] + #end if + + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) + output = process.stdout.decode("utf-8") + err = process.stderr.decode("utf-8") + if len(err) > 0: + self.local.add_log("args: {args}".format(args=args), "error") + raise Exception("LiteClient error: {err}".format(err=err)) + return output + #end define +#end class diff --git a/mytoncore/models.py b/mytoncore/models.py new file mode 100644 index 00000000..474fa276 --- /dev/null +++ b/mytoncore/models.py @@ -0,0 +1,177 @@ +import os + + +class Wallet: + def __init__(self, name, path, version): + self.name = name + self.path = path + self.addrFilePath = f"{path}.addr" + self.privFilePath = f"{path}.pk" + self.bocFilePath = f"{path}-query.boc" + self.addrFull = None + self.workchain = None + self.addr = None + self.addrB64 = None + self.addrB64_init = None + self.oldseqno = None + self.account = None + self.subwallet = None + self.version = version + #end define + + def Delete(self): + os.remove(self.addrFilePath) + os.remove(self.privFilePath) + #end define +#end class + + +class Account: + def __init__(self, workchain, addr): + self.workchain = workchain + self.addr = addr + self.addrB64 = None + self.addrFull = None + self.status = "empty" + self.balance = 0 + self.lt = None + self.hash = None + self.codeHash = None + #end define +#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 + self.shardchain = None + self.seqno = None + self.rootHash = None + self.fileHash = None + self.ParsBlock(str) + #end define + + def ParsBlock(self, str): + if str is None: + return + buff = str.split(':') + self.rootHash = buff[1] + self.fileHash = buff[2] + buff = buff[0] + buff = buff.replace('(', '') + buff = buff.replace(')', '') + buff = buff.split(',') + self.workchain = int(buff[0]) + self.shardchain = buff[1] + self.seqno = int(buff[2]) + #end define + + def __str__(self): + result = f"({self.workchain},{self.shardchain},{self.seqno}):{self.rootHash}:{self.fileHash}" + return result + #end define + + def __repr__(self): + return self.__str__() + #end define + + def __eq__(self, other): + if other is None: + return False + return self.rootHash == other.rootHash and self.fileHash == other.fileHash + #end define +#end class + + +class Trans(): + def __init__(self, block, addr=None, lt=None, hash=None): + self.block = block + self.addr = addr + self.lt = lt + self.hash = hash + #end define + + def __str__(self): + return str(self.__dict__) + #end define + + def __repr__(self): + return self.__str__() + #end define + + def __eq__(self, other): + if other is None: + return False + return self.hash == other.hash + #end define +#end class + + +class Message(): + def __init__(self): + self.trans = None + self.type = None + self.time = None + self.srcWorkchain = None + self.destWorkchain = None + self.srcAddr = None + self.destAddr = None + self.value = None + self.body = None + self.comment = None + self.ihr_fee = None + self.fwd_fee = None + self.total_fees = None + self.ihr_disabled = None + self.hash = None + #end define + + def GetFullAddr(self, workchain, addr): + if addr is None: + return + return f"{workchain}:{addr}" + #end define + + def __str__(self): + return str(self.__dict__) + #end define + + def __repr__(self): + return self.__str__() + #end define + + def __eq__(self, other): + if other is None: + return False + return self.hash == other.hash + #end define +#end class + + +class Pool: + def __init__(self, name, path): + self.name = name + self.path = path + self.addrFilePath = f"{path}.addr" + self.bocFilePath = f"{path}-query.boc" + self.addrFull = None + self.workchain = None + self.addr = None + self.addrB64 = None + self.addrB64_init = None + self.account = None + #end define + + def Delete(self): + os.remove(self.addrFilePath) + #end define +#end class diff --git a/mytoncore.py b/mytoncore/mytoncore.py old mode 100755 new mode 100644 similarity index 66% rename from mytoncore.py rename to mytoncore/mytoncore.py index ed132f58..ed643083 --- a/mytoncore.py +++ b/mytoncore/mytoncore.py @@ -1,275 +1,43 @@ -#!/usr/bin/env python3 -# -*- coding: utf_8 -*-l - -from fastcrc import crc16 -import struct -import random +import os +import base64 +import time +import re +import json import hashlib +import struct +import psutil +import subprocess +import pkg_resources import requests -import re -from mypylib.mypylib import * -from custom_overlays import custom_overlays - -local = MyPyClass(__file__) - -class LiteClient: - def __init__(self): - self.appPath = None - self.configPath = None - self.pubkeyPath = None - self.addr = None - self.ton = None # magic - #end define - - def Run(self, cmd, **kwargs): - index = kwargs.get("index") - liteclient_timeout = local.db.liteclient_timeout if local.db.liteclient_timeout else 3 - timeout = kwargs.get("timeout", liteclient_timeout) - useLocalLiteServer = kwargs.get("useLocalLiteServer", True) - validatorStatus = self.ton.GetValidatorStatus() - validatorOutOfSync = validatorStatus.get("outOfSync") - args = [self.appPath, "--global-config", self.configPath, "--verbosity", "0", "--cmd", cmd] - if index is not None: - index = str(index) - args += ["-i", index] - elif useLocalLiteServer and self.pubkeyPath and validatorOutOfSync < 20: - args = [self.appPath, "--addr", self.addr, "--pub", self.pubkeyPath, "--verbosity", "0", "--cmd", cmd] - else: - liteServers = local.db.get("liteServers") - if liteServers is not None and len(liteServers): - index = random.choice(liteServers) - index = str(index) - args += ["-i", index] - #end if - - process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) - output = process.stdout.decode("utf-8") - err = process.stderr.decode("utf-8") - if len(err) > 0: - local.add_log("args: {args}".format(args=args), "error") - raise Exception("LiteClient error: {err}".format(err=err)) - return output - #end define -#end class - -class ValidatorConsole: - def __init__(self): - self.appPath = None - self.privKeyPath = None - self.pubKeyPath = None - self.addr = None - #end define - - def Run(self, cmd, **kwargs): - console_timeout = local.db.console_timeout if local.db.console_timeout else 3 - timeout = kwargs.get("timeout", console_timeout) - if self.appPath is None or self.privKeyPath is None or self.pubKeyPath is None: - raise Exception("ValidatorConsole error: Validator console is not settings") - args = [self.appPath, "-k", self.privKeyPath, "-p", self.pubKeyPath, "-a", self.addr, "-v", "0", "--cmd", cmd] - process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) - output = process.stdout.decode("utf-8") - err = process.stderr.decode("utf-8") - if len(err) > 0: - local.add_log("args: {args}".format(args=args), "error") - raise Exception("ValidatorConsole error: {err}".format(err=err)) - return output - #end define -#end class - -class Fift: - def __init__(self): - self.appPath = None - self.libsPath = None - self.smartcontsPath = None - #end define - - def Run(self, args, **kwargs): - fift_timeout = local.db.fift_timeout if local.db.fift_timeout else 3 - timeout = kwargs.get("timeout", fift_timeout) - for i in range(len(args)): - args[i] = str(args[i]) - includePath = self.libsPath + ':' + self.smartcontsPath - args = [self.appPath, "-I", includePath, "-s"] + args - process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) - output = process.stdout.decode("utf-8") - err = process.stderr.decode("utf-8") - if len(err) > 0: - local.add_log("args: {args}".format(args=args), "error") - raise Exception("Fift error: {err}".format(err=err)) - return output - #end define -#end class - -class Wallet: - def __init__(self, name, path, version): - self.name = name - self.path = path - self.addrFilePath = f"{path}.addr" - self.privFilePath = f"{path}.pk" - self.bocFilePath = f"{path}-query.boc" - self.addrFull = None - self.workchain = None - self.addr = None - self.addrB64 = None - self.addrB64_init = None - self.oldseqno = None - self.account = None - self.subwallet = None - self.version = version - #end define - - def Delete(self): - os.remove(self.addrFilePath) - os.remove(self.privFilePath) - #end define -#end class - -class Account: - def __init__(self, workchain, addr): - self.workchain = workchain - self.addr = addr - self.addrB64 = None - self.addrFull = None - self.status = "empty" - self.balance = 0 - self.lt = None - self.hash = None - self.codeHash = None - #end define -#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 - self.shardchain = None - self.seqno = None - self.rootHash = None - self.fileHash = None - self.ParsBlock(str) - #end define - - def ParsBlock(self, str): - if str is None: - return - buff = str.split(':') - self.rootHash = buff[1] - self.fileHash = buff[2] - buff = buff[0] - buff = buff.replace('(', '') - buff = buff.replace(')', '') - buff = buff.split(',') - self.workchain = int(buff[0]) - self.shardchain = buff[1] - self.seqno = int(buff[2]) - #end define - - def __str__(self): - result = f"({self.workchain},{self.shardchain},{self.seqno}):{self.rootHash}:{self.fileHash}" - return result - #end define - - def __repr__(self): - return self.__str__() - #end define - - def __eq__(self, other): - if other is None: - return False - return self.rootHash == other.rootHash and self.fileHash == other.fileHash - #end define -#end class - -class Trans(): - def __init__(self, block, addr=None, lt=None, hash=None): - self.block = block - self.addr = addr - self.lt = lt - self.hash = hash - #end define - - def __str__(self): - return str(self.__dict__) - #end define - - def __repr__(self): - return self.__str__() - #end define - - def __eq__(self, other): - if other is None: - return False - return self.hash == other.hash - #end define -#end class - -class Message(): - def __init__(self): - self.trans = None - self.type = None - self.time = None - self.srcWorkchain = None - self.destWorkchain = None - self.srcAddr = None - self.destAddr = None - self.value = None - self.body = None - self.comment = None - self.ihr_fee = None - self.fwd_fee = None - self.total_fees = None - self.ihr_disabled = None - #end define - - def GetFullAddr(self, workchain, addr): - if addr is None: - return - return f"{workchain}:{addr}" - #end define - - def __str__(self): - return str(self.__dict__) - #end define - - def __repr__(self): - return self.__str__() - #end define +from fastcrc import crc16 - def __eq__(self, other): - if other is None: - return False - return self.hash == other.hash - #end define -#end class +from modules import MODES +from mytoncore.utils import xhex2hex, ng2g +from mytoncore.liteclient import LiteClient +from mytoncore.validator_console import ValidatorConsole +from mytoncore.fift import Fift +from mytoncore.models import ( + Wallet, + Account, + Domain, + Block, + Trans, + Message, + Pool, +) + +from mypylib.mypylib import ( + parse, + get_timestamp, + timestamp2datetime, + dec2hex, + Dict +) -class Pool: - def __init__(self, name, path): - self.name = name - self.path = path - self.addrFilePath = f"{path}.addr" - self.bocFilePath = f"{path}-query.boc" - self.addrFull = None - self.workchain = None - self.addr = None - self.addrB64 = None - self.addrB64_init = None - self.account = None - #end define - - def Delete(self): - os.remove(self.addrFilePath) - #end define -#end class class MyTonCore(): - def __init__(self): + def __init__(self, local): + self.local = local self.walletsDir = None self.dbFile = None self.contractsDir = None @@ -277,9 +45,9 @@ def __init__(self): self.tempDir = None self.nodeName = None - self.liteClient = LiteClient() - self.validatorConsole = ValidatorConsole() - self.fift = Fift() + self.liteClient = LiteClient(self.local) + self.validatorConsole = ValidatorConsole(self.local) + self.fift = Fift(self.local) self.Refresh() self.Init() @@ -294,22 +62,21 @@ def Init(self): def Refresh(self): if self.dbFile: - local.load_db(self.dbFile) - #end if + self.local.load_db(self.dbFile) if not self.walletsDir: - self.walletsDir = local.buffer.my_work_dir + "wallets/" - self.contractsDir = local.buffer.my_work_dir + "contracts/" - self.poolsDir = local.buffer.my_work_dir + "pools/" - self.tempDir = local.buffer.my_temp_dir + self.walletsDir = self.local.buffer.my_work_dir + "wallets/" + self.contractsDir = self.local.buffer.my_work_dir + "contracts/" + self.poolsDir = self.local.buffer.my_work_dir + "pools/" + self.tempDir = self.local.buffer.my_temp_dir - self.nodeName = local.db.get("nodeName") + self.nodeName = self.local.db.get("nodeName") if self.nodeName is None: self.nodeName="" else: self.nodeName = self.nodeName + "_" - liteClient = local.db.get("liteClient") + liteClient = self.local.db.get("liteClient") if liteClient is not None: self.liteClient.ton = self # magic self.liteClient.appPath = liteClient["appPath"] @@ -320,7 +87,7 @@ def Refresh(self): self.liteClient.addr = "{0}:{1}".format(liteServer["ip"], liteServer["port"]) #end if - validatorConsole = local.db.get("validatorConsole") + validatorConsole = self.local.db.get("validatorConsole") if validatorConsole is not None: self.validatorConsole.appPath = validatorConsole["appPath"] self.validatorConsole.privKeyPath = validatorConsole["privKeyPath"] @@ -328,7 +95,7 @@ def Refresh(self): self.validatorConsole.addr = validatorConsole["addr"] #end if - fift = local.db.get("fift") + fift = self.local.db.get("fift") if fift is not None: self.fift.appPath = fift["appPath"] self.fift.libsPath = fift["libsPath"] @@ -340,18 +107,18 @@ def Refresh(self): #end define def CheckConfigFile(self, fift, liteClient): - mconfig_path = local.buffer.db_path + mconfig_path = self.local.buffer.db_path backup_path = mconfig_path + ".backup" if fift is None or liteClient is None: - local.add_log("The config file is broken", "warning") - print(f"local.db: {local.db}") + self.local.add_log("The config file is broken", "warning") + print(f"self.local.db: {self.local.db}") if os.path.isfile(backup_path): - local.add_log("Restoring the configuration file", "info") + self.local.add_log("Restoring the configuration file", "info") args = ["cp", backup_path, mconfig_path] subprocess.run(args) self.Refresh() elif os.path.isfile(backup_path) == False: - local.add_log("Create backup config file", "info") + self.local.add_log("Create backup config file", "info") args = ["cp", mconfig_path, backup_path] subprocess.run(args) #end define @@ -388,7 +155,7 @@ def GetVarFromWorkerOutput(self, text, search): #end define def GetSeqno(self, wallet): - local.add_log("start GetSeqno function", "debug") + self.local.add_log("start GetSeqno function", "debug") cmd = "runmethodfull {addr} seqno".format(addr=wallet.addrB64) result = self.liteClient.Run(cmd) if "cannot run any methods" in result: @@ -403,7 +170,7 @@ def GetSeqno(self, wallet): #end define def GetAccount(self, inputAddr): - #local.add_log("start GetAccount function", "debug") + #self.local.add_log("start GetAccount function", "debug") workchain, addr = self.ParseInputAddr(inputAddr) account = Account(workchain, addr) cmd = "getaccount {inputAddr}".format(inputAddr=inputAddr) @@ -448,7 +215,7 @@ def GetCodeHash(self, code): #end define def GetAccountHistory(self, account, limit): - local.add_log("start GetAccountHistory function", "debug") + self.local.add_log("start GetAccountHistory function", "debug") addr = f"{account.workchain}:{account.addr}" lt = account.lt transHash = account.hash @@ -637,7 +404,7 @@ def GetDomainAddr(self, domainName): #end define def GetDomainEndTime(self, domainName): - local.add_log("start GetDomainEndTime function", "debug") + self.local.add_log("start GetDomainEndTime function", "debug") buff = domainName.split('.') subdomain = buff.pop(0) dnsDomain = ".".join(buff) @@ -653,7 +420,7 @@ def GetDomainEndTime(self, domainName): #end define def GetDomainAdnlAddr(self, domainName): - local.add_log("start GetDomainAdnlAddr function", "debug") + self.local.add_log("start GetDomainAdnlAddr function", "debug") cmd = "dnsresolve {domainName} 1".format(domainName=domainName) result = self.liteClient.Run(cmd) lines = result.split('\n') @@ -666,7 +433,7 @@ def GetDomainAdnlAddr(self, domainName): #end define def GetLocalWallet(self, walletName, version=None, subwallet=None): - local.add_log("start GetLocalWallet function", "debug") + self.local.add_log("start GetLocalWallet function", "debug") if walletName is None: return None walletPath = self.walletsDir + walletName @@ -678,7 +445,7 @@ def GetLocalWallet(self, walletName, version=None, subwallet=None): #end define def GetWalletFromFile(self, filePath, version): - local.add_log("start GetWalletFromFile function", "debug") + self.local.add_log("start GetWalletFromFile function", "debug") # Check input args if (".addr" in filePath): filePath = filePath.replace(".addr", '') @@ -697,7 +464,7 @@ def GetWalletFromFile(self, filePath, version): #end define def GetHighWalletFromFile(self, filePath, subwallet, version): - local.add_log("start GetHighWalletFromFile function", "debug") + self.local.add_log("start GetHighWalletFromFile function", "debug") # Check input args if (".addr" in filePath): filePath = filePath.replace(".addr", '') @@ -726,34 +493,33 @@ def AddrFile2Object(self, object): object.addrFull = f"{object.workchain}:{object.addr}" object.addrB64 = self.AddrFull2AddrB64(object.addrFull) object.addrB64_init = self.AddrFull2AddrB64(object.addrFull, bounceable=False) + file.close() #end define def WalletVersion2Wallet(self, wallet): - local.add_log("start WalletVersion2Wallet function", "debug") if wallet.version is not None: return + self.local.add_log("start WalletVersion2Wallet function", "debug") walletsVersionList = self.GetWalletsVersionList() - account = self.GetAccount(wallet.addrB64) version = walletsVersionList.get(wallet.addrB64) if version is None: - version = self.GetWalletVersionFromHash(account.codeHash) + account = self.GetAccount(wallet.addrB64) + version = self.GetVersionFromCodeHash(account.codeHash) + self.SetWalletVersion(wallet.addrB64, version) if version is None: - local.add_log("Wallet version not found: " + wallet.addrB64, "warning") + self.local.add_log("Wallet version not found: " + wallet.addrB64, "warning") return - #end if - - self.SetWalletVersion(wallet.addrB64, version) wallet.version = version #end define def SetWalletVersion(self, addrB64, version): walletsVersionList = self.GetWalletsVersionList() walletsVersionList[addrB64] = version - local.save() + self.local.save() #end define - def GetWalletVersionFromHash(self, inputHash): - local.add_log("start GetWalletVersionFromHash function", "debug") + def GetVersionFromCodeHash(self, inputHash): + self.local.add_log("start GetVersionFromCodeHash function", "debug") arr = dict() arr["v1r1"] = "d670136510daff4fee1889b8872c4c1e89872ffa1fe58a23a5f5d99cef8edf32" arr["v1r2"] = "2705a31a7ac162295c8aed0761cc6e031ab65521dd7b4a14631099e02de99e18" @@ -764,6 +530,17 @@ def GetWalletVersionFromHash(self, inputHash): arr["v3r2"] = "8a6d73bdd8704894f17d8c76ce6139034b8a51b1802907ca36283417798a219b" arr["v4"] = "7ae380664c513769eaa5c94f9cd5767356e3f7676163baab66a4b73d5edab0e5" arr["hv1"] = "fc8e48ed7f9654ba76757f52cc6031b2214c02fab9e429ffa0340f5575f9f29c" + arr["pool"] = "399838da9489139680e90fd237382e96ba771fdf6ea27eb7d513965b355038b4" + arr["spool"] = "fc2ae44bcaedfa357d0091769aabbac824e1c28f14cc180c0b52a57d83d29054" + arr["spool_r2"] = "42bea8fea43bf803c652411976eb2981b9bdb10da84eb788a63ea7a01f2a044d" + arr["liquid_pool_r1"] = "82bc5760719c34395f80df76c42dc5d287f08f6562c643601ebed6944302dcc2" + arr["liquid_pool_r2"] = "95abec0a66ac63b0fbcf28466eb8240ddcd88f97300691511d9c9975d5521e4a" + arr["liquid_pool_r3"] = "22a023bc75b649ff2b5b183cd0d34cd413e6e27ee6d6ad0787f75ad39787ed4e" + arr["liquid_pool_r4"] = "77282b45fd7cfc72ca68fe97af33ad10078730ceaf55e20534c9526c48d602d2" + arr["controller_r1"] = "0949cf92963dd27bb1e6bf76487807f20409131b6110acbc18b7fbb90280ccf0" + arr["controller_r2"] = "01118b9553151fb9bc81704a4b3e0fc7b899871a527d44435a51574806863e2c" + arr["controller_r3"] = "e4d8ce8ff7b4b60c76b135eb8702ce3c86dc133fcee7d19c7aa18f71d9d91438" + arr["controller_r4"] = "dec125a4850c4ba24668d84252b04c6ad40abf5c9d413a429b56bfff09ea25d4" for version, hash in arr.items(): if hash == inputHash: return version @@ -772,10 +549,10 @@ def GetWalletVersionFromHash(self, inputHash): def GetWalletsVersionList(self): bname = "walletsVersionList" - walletsVersionList = local.db.get(bname) + walletsVersionList = self.local.db.get(bname) if walletsVersionList is None: walletsVersionList = dict() - local.db[bname] = walletsVersionList + self.local.db[bname] = walletsVersionList return walletsVersionList #end define @@ -787,11 +564,11 @@ def GetFullConfigAddr(self): return buff #end if - local.add_log("start GetFullConfigAddr function", "debug") + self.local.add_log("start GetFullConfigAddr function", "debug") result = self.liteClient.Run("getconfig 0") configAddr_hex = self.GetVarFromWorkerOutput(result, "config_addr:x") fullConfigAddr = "-1:{configAddr_hex}".format(configAddr_hex=configAddr_hex) - + # Set buffer self.SetFunctionBuffer(bname, fullConfigAddr) return fullConfigAddr @@ -806,7 +583,7 @@ def GetFullElectorAddr(self): #end if # Get data - local.add_log("start GetFullElectorAddr function", "debug") + self.local.add_log("start GetFullElectorAddr function", "debug") result = self.liteClient.Run("getconfig 1") electorAddr_hex = self.GetVarFromWorkerOutput(result, "elector_addr:x") fullElectorAddr = "-1:{electorAddr_hex}".format(electorAddr_hex=electorAddr_hex) @@ -824,7 +601,7 @@ def GetFullMinterAddr(self): return buff #end if - local.add_log("start GetFullMinterAddr function", "debug") + self.local.add_log("start GetFullMinterAddr function", "debug") result = self.liteClient.Run("getconfig 2") minterAddr_hex = self.GetVarFromWorkerOutput(result, "minter_addr:x") fullMinterAddr = "-1:{minterAddr_hex}".format(minterAddr_hex=minterAddr_hex) @@ -842,11 +619,11 @@ def GetFullDnsRootAddr(self): return buff #end if - local.add_log("start GetFullDnsRootAddr function", "debug") + self.local.add_log("start GetFullDnsRootAddr function", "debug") result = self.liteClient.Run("getconfig 4") dnsRootAddr_hex = self.GetVarFromWorkerOutput(result, "dns_root_addr:x") fullDnsRootAddr = "-1:{dnsRootAddr_hex}".format(dnsRootAddr_hex=dnsRootAddr_hex) - + # Set buffer self.SetFunctionBuffer(bname, fullDnsRootAddr) return fullDnsRootAddr @@ -860,40 +637,40 @@ def GetActiveElectionId(self, fullElectorAddr): return buff #end if - local.add_log("start GetActiveElectionId function", "debug") + self.local.add_log("start GetActiveElectionId function", "debug") cmd = "runmethodfull {fullElectorAddr} active_election_id".format(fullElectorAddr=fullElectorAddr) result = self.liteClient.Run(cmd) activeElectionId = self.GetVarFromWorkerOutput(result, "result") activeElectionId = activeElectionId.replace(' ', '') activeElectionId = parse(activeElectionId, '[', ']') activeElectionId = int(activeElectionId) - + # Set buffer self.SetFunctionBuffer(bname, activeElectionId) return activeElectionId #end define def GetValidatorsElectedFor(self): - local.add_log("start GetValidatorsElectedFor function", "debug") + self.local.add_log("start GetValidatorsElectedFor function", "debug") config15 = self.GetConfig15() return config15["validatorsElectedFor"] #end define def GetMinStake(self): - local.add_log("start GetMinStake function", "debug") + self.local.add_log("start GetMinStake function", "debug") config17 = self.GetConfig17() return config17["minStake"] #end define def GetRootWorkchainEnabledTime(self): - local.add_log("start GetRootWorkchainEnabledTime function", "debug") + self.local.add_log("start GetRootWorkchainEnabledTime function", "debug") config12 = self.GetConfig(12) enabledTime = config12["workchains"]["root"]["node"]["value"]["enabled_since"] return enabledTime #end define def GetTotalValidators(self): - local.add_log("start GetTotalValidators function", "debug") + self.local.add_log("start GetTotalValidators function", "debug") config34 = self.GetConfig34() result = config34["totalValidators"] return result @@ -1031,42 +808,48 @@ def GetShardsNumber(self, block=None): def GetValidatorStatus(self): # Get buffer - bname = "validatorStatus" + bname = "validator_status" buff = self.GetFunctionBuffer(bname) if buff: return buff #end if - # local.add_log("start GetValidatorStatus function", "debug") - validatorStatus = dict() + self.local.add_log("start GetValidatorStatus function", "debug") + status = Dict() try: - validatorStatus["isWorking"] = True + # Parse + status.is_working = True result = self.validatorConsole.Run("getstats") - validatorStatus["unixtime"] = int(parse(result, "unixtime", '\n')) - validatorStatus["masterchainblocktime"] = int(parse(result, "masterchainblocktime", '\n')) - validatorStatus["stateserializermasterchainseqno"] = int(parse(result, "stateserializermasterchainseqno", '\n')) - validatorStatus["shardclientmasterchainseqno"] = int(parse(result, "shardclientmasterchainseqno", '\n')) + status.unixtime = int(parse(result, "unixtime", '\n')) + status.masterchainblocktime = int(parse(result, "masterchainblocktime", '\n')) + status.stateserializermasterchainseqno = int(parse(result, "stateserializermasterchainseqno", '\n')) + status.shardclientmasterchainseqno = int(parse(result, "shardclientmasterchainseqno", '\n')) buff = parse(result, "masterchainblock", '\n') - validatorStatus["masterchainblock"] = self.GVS_GetItemFromBuff(buff) + status.masterchainblock = self.GVS_GetItemFromBuff(buff) buff = parse(result, "gcmasterchainblock", '\n') - validatorStatus["gcmasterchainblock"] = self.GVS_GetItemFromBuff(buff) + status.gcmasterchainblock = self.GVS_GetItemFromBuff(buff) buff = parse(result, "keymasterchainblock", '\n') - validatorStatus["keymasterchainblock"] = self.GVS_GetItemFromBuff(buff) + status.keymasterchainblock = self.GVS_GetItemFromBuff(buff) buff = parse(result, "rotatemasterchainblock", '\n') - validatorStatus["rotatemasterchainblock"] = self.GVS_GetItemFromBuff(buff) - validatorStatus["transNum"] = local.buffer.get("transNum", -1) - validatorStatus["blocksNum"] = local.buffer.get("blocksNum", -1) - validatorStatus["masterBlocksNum"] = local.buffer.get("masterBlocksNum", -1) + status.rotatemasterchainblock = self.GVS_GetItemFromBuff(buff) + # Calculate + status.masterchain_out_of_sync = status.unixtime - status.masterchainblocktime + status.shardchain_out_of_sync = status.masterchainblock - status.shardclientmasterchainseqno + 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 except Exception as ex: - local.add_log(f"GetValidatorStatus warning: {ex}", "warning") - validatorStatus["isWorking"] = False - validatorStatus["unixtime"] = get_timestamp() - validatorStatus["masterchainblocktime"] = 0 - validatorStatus["outOfSync"] = validatorStatus["unixtime"] - validatorStatus["masterchainblocktime"] + self.local.add_log(f"GetValidatorStatus warning: {ex}", "warning") + status.is_working = False + #end try + + # old vars + status.outOfSync = status.out_of_sync + status.isWorking = status.is_working # Set buffer - self.SetFunctionBuffer(bname, validatorStatus) - return validatorStatus + self.SetFunctionBuffer(bname, status) + return status #end define def GVS_GetItemFromBuff(self, buff): @@ -1090,13 +873,13 @@ def GetConfig(self, configId): #end if text = "start GetConfig function ({})".format(configId) - local.add_log(text, "debug") + self.local.add_log(text, "debug") cmd = "getconfig {configId}".format(configId=configId) result = self.liteClient.Run(cmd) start = result.find("ConfigParam") text = result[start:] data = self.Tlb2Json(text) - + # Set buffer self.SetFunctionBuffer(bname, data) return data @@ -1129,7 +912,7 @@ def GetConfig32(self): return buff #end if - local.add_log("start GetConfig32 function", "debug") + self.local.add_log("start GetConfig32 function", "debug") config32 = dict() result = self.liteClient.Run("getconfig 32") config32["totalValidators"] = int(parse(result, "total:", ' ')) @@ -1141,9 +924,9 @@ def GetConfig32(self): if "public_key:" in line: validatorAdnlAddr = parse(line, "adnl_addr:x", ')') pubkey = parse(line, "pubkey:x", ')') - if config32["totalValidators"] > 1: + try: validatorWeight = int(parse(line, "weight:", ' ')) - else: + except ValueError: validatorWeight = int(parse(line, "weight:", ')')) buff = dict() buff["adnlAddr"] = validatorAdnlAddr @@ -1151,7 +934,7 @@ def GetConfig32(self): buff["weight"] = validatorWeight validators.append(buff) config32["validators"] = validators - + # Set buffer self.SetFunctionBuffer(bname, config32) return config32 @@ -1165,7 +948,7 @@ def GetConfig34(self): return buff #end if - local.add_log("start GetConfig34 function", "debug") + self.local.add_log("start GetConfig34 function", "debug") config34 = dict() result = self.liteClient.Run("getconfig 34") config34["totalValidators"] = int(parse(result, "total:", ' ')) @@ -1178,9 +961,9 @@ def GetConfig34(self): if "public_key:" in line: validatorAdnlAddr = parse(line, "adnl_addr:x", ')') pubkey = parse(line, "pubkey:x", ')') - if config34["totalValidators"] > 1: + try: validatorWeight = int(parse(line, "weight:", ' ')) - else: + except ValueError: validatorWeight = int(parse(line, "weight:", ')')) buff = dict() buff["adnlAddr"] = validatorAdnlAddr @@ -1188,7 +971,7 @@ def GetConfig34(self): buff["weight"] = validatorWeight validators.append(buff) config34["validators"] = validators - + # Set buffer self.SetFunctionBuffer(bname, config34) return config34 @@ -1202,7 +985,7 @@ def GetConfig36(self): return buff #end if - local.add_log("start GetConfig36 function", "debug") + self.local.add_log("start GetConfig36 function", "debug") config36 = dict() try: result = self.liteClient.Run("getconfig 36") @@ -1225,28 +1008,28 @@ def GetConfig36(self): except: config36["validators"] = list() #end try - + # Set buffer self.SetFunctionBuffer(bname, config36) return config36 #end define def CreateNewKey(self): - local.add_log("start CreateNewKey function", "debug") + self.local.add_log("start CreateNewKey function", "debug") result = self.validatorConsole.Run("newkey") key = parse(result, "created new key ", '\n') return key #end define def GetPubKeyBase64(self, key): - local.add_log("start GetPubKeyBase64 function", "debug") + self.local.add_log("start GetPubKeyBase64 function", "debug") result = self.validatorConsole.Run("exportpub " + key) validatorPubkey_b64 = parse(result, "got public key: ", '\n') return validatorPubkey_b64 #end define def GetPubKey(self, key): - local.add_log("start GetPubKey function", "debug") + self.local.add_log("start GetPubKey function", "debug") pubkey_b64 = self.GetPubKeyBase64(key) buff = pubkey_b64.encode("utf-8") buff = base64.b64decode(buff) @@ -1257,7 +1040,7 @@ def GetPubKey(self, key): #end define def AddKeyToValidator(self, key, startWorkTime, endWorkTime): - local.add_log("start AddKeyToValidator function", "debug") + self.local.add_log("start AddKeyToValidator function", "debug") output = False cmd = "addpermkey {key} {startWorkTime} {endWorkTime}".format(key=key, startWorkTime=startWorkTime, endWorkTime=endWorkTime) result = self.validatorConsole.Run(cmd) @@ -1267,7 +1050,7 @@ def AddKeyToValidator(self, key, startWorkTime, endWorkTime): #end define def AddKeyToTemp(self, key, endWorkTime): - local.add_log("start AddKeyToTemp function", "debug") + self.local.add_log("start AddKeyToTemp function", "debug") output = False result = self.validatorConsole.Run("addtempkey {key} {key} {endWorkTime}".format(key=key, endWorkTime=endWorkTime)) if ("success" in result): @@ -1276,7 +1059,7 @@ def AddKeyToTemp(self, key, endWorkTime): #end define def AddAdnlAddrToValidator(self, adnlAddr): - local.add_log("start AddAdnlAddrToValidator function", "debug") + self.local.add_log("start AddAdnlAddrToValidator function", "debug") output = False result = self.validatorConsole.Run("addadnl {adnlAddr} 0".format(adnlAddr=adnlAddr)) if ("success" in result): @@ -1285,12 +1068,12 @@ def AddAdnlAddrToValidator(self, adnlAddr): #end define def GetAdnlAddr(self): - adnlAddr = local.db.get("adnlAddr") + adnlAddr = self.local.db.get("adnlAddr") return adnlAddr #end define def AttachAdnlAddrToValidator(self, adnlAddr, key, endWorkTime): - local.add_log("start AttachAdnlAddrToValidator function", "debug") + self.local.add_log("start AttachAdnlAddrToValidator function", "debug") output = False result = self.validatorConsole.Run("addvalidatoraddr {key} {adnlAddr} {endWorkTime}".format(adnlAddr=adnlAddr, key=key, endWorkTime=endWorkTime)) if ("success" in result): @@ -1299,7 +1082,7 @@ def AttachAdnlAddrToValidator(self, adnlAddr, key, endWorkTime): #end define def CreateConfigProposalRequest(self, offerHash, validatorIndex): - local.add_log("start CreateConfigProposalRequest function", "debug") + self.local.add_log("start CreateConfigProposalRequest function", "debug") fileName = self.tempDir + self.nodeName + "proposal_validator-to-sign.req" args = ["config-proposal-vote-req.fif", "-i", validatorIndex, offerHash, fileName] result = self.fift.Run(args) @@ -1316,8 +1099,8 @@ def CreateConfigProposalRequest(self, offerHash, validatorIndex): return var1 #end define - def CreateComplaintRequest(self, electionId , complaintHash, validatorIndex): - local.add_log("start CreateComplaintRequest function", "debug") + def CreateComplaintRequest(self, electionId, complaintHash, validatorIndex): + self.local.add_log("start CreateComplaintRequest function", "debug") fileName = self.tempDir + "complaint_validator-to-sign.req" args = ["complaint-vote-req.fif", validatorIndex, electionId, complaintHash, fileName] result = self.fift.Run(args) @@ -1334,8 +1117,17 @@ def CreateComplaintRequest(self, electionId , complaintHash, validatorIndex): return var1 #end define + def remove_proofs_from_complaint(self, input_file_name: str): + self.local.add_log("start remove_proofs_from_complaint function", "debug") + output_file_name = self.tempDir + "complaint-new.boc" + fift_script = pkg_resources.resource_filename('mytoncore', 'complaints/remove-proofs-v2.fif') + args = [fift_script, input_file_name, output_file_name] + result = self.fift.Run(args) + return output_file_name + + def PrepareComplaint(self, electionId, inputFileName): - local.add_log("start PrepareComplaint function", "debug") + self.local.add_log("start PrepareComplaint function", "debug") fileName = self.tempDir + "complaint-msg-body.boc" args = ["envelope-complaint.fif", electionId, inputFileName, fileName] result = self.fift.Run(args) @@ -1343,10 +1135,10 @@ def PrepareComplaint(self, electionId, inputFileName): return fileName #end define - def CreateElectionRequest(self, wallet, startWorkTime, adnlAddr, maxFactor): - local.add_log("start CreateElectionRequest function", "debug") + def CreateElectionRequest(self, addrB64, startWorkTime, adnlAddr, maxFactor): + self.local.add_log("start CreateElectionRequest function", "debug") fileName = self.tempDir + self.nodeName + str(startWorkTime) + "_validator-to-sign.bin" - args = ["validator-elect-req.fif", wallet.addrB64, startWorkTime, maxFactor, adnlAddr, fileName] + args = ["validator-elect-req.fif", addrB64, startWorkTime, maxFactor, adnlAddr, fileName] result = self.fift.Run(args) fileName = parse(result, "Saved to file ", '\n') resultList = result.split('\n') @@ -1362,7 +1154,7 @@ def CreateElectionRequest(self, wallet, startWorkTime, adnlAddr, maxFactor): #end define def GetValidatorSignature(self, validatorKey, var1): - local.add_log("start GetValidatorSignature function", "debug") + self.local.add_log("start GetValidatorSignature function", "debug") cmd = "sign {validatorKey} {var1}".format(validatorKey=validatorKey, var1=var1) result = self.validatorConsole.Run(cmd) validatorSignature = parse(result, "got signature ", '\n') @@ -1370,7 +1162,7 @@ def GetValidatorSignature(self, validatorKey, var1): #end define def SignElectionRequestWithValidator(self, wallet, startWorkTime, adnlAddr, validatorPubkey_b64, validatorSignature, maxFactor): - local.add_log("start SignElectionRequestWithValidator function", "debug") + self.local.add_log("start SignElectionRequestWithValidator function", "debug") fileName = self.tempDir + self.nodeName + str(startWorkTime) + "_validator-query.boc" args = ["validator-elect-signed.fif", wallet.addrB64, startWorkTime, maxFactor, adnlAddr, validatorPubkey_b64, validatorSignature, fileName] result = self.fift.Run(args) @@ -1379,52 +1171,55 @@ def SignElectionRequestWithValidator(self, wallet, startWorkTime, adnlAddr, vali return pubkey, fileName #end define - def SignBocWithWallet(self, wallet, bocPath, dest, coins, **kwargs): - local.add_log("start SignBocWithWallet function", "debug") + def SignBocWithWallet(self, wallet, boc_path, dest, coins, **kwargs): + self.local.add_log("start SignBocWithWallet function", "debug") flags = kwargs.get("flags", list()) subwalletDefault = 698983191 + wallet.workchain # 0x29A9A317 + workchain subwallet = kwargs.get("subwallet", subwalletDefault) + boc_mode = kwargs.get("boc_mode", "--body") # Balance checking account = self.GetAccount(wallet.addrB64) - if account.balance < coins + 0.1: - raise Exception("Wallet balance is less than requested coins") - #end if - + self.check_account_balance(account, coins + 0.1) + # Bounceable checking destAccount = self.GetAccount(dest) bounceable = self.IsBounceableAddrB64(dest) if bounceable == False and destAccount.status == "active": - flags += ["-b"] + flags += ["--force-bounce"] text = "Find non-bounceable flag, but destination account already active. Using bounceable flag" - local.add_log(text, "warning") + self.local.AddLog(text, "warning") elif "-n" not in flags and bounceable == True and destAccount.status != "active": raise Exception("Find bounceable flag, but destination account is not active. Use non-bounceable address or flag -n") #end if seqno = self.GetSeqno(wallet) - resultFilePath = self.tempDir + self.nodeName + wallet.name + "_wallet-query" + result_file_path = self.tempDir + self.nodeName + wallet.name + "_wallet-query" if "v1" in wallet.version: - fiftScript = "wallet.fif" - args = [fiftScript, wallet.path, dest, seqno, coins, "-B", bocPath, resultFilePath] + fift_script = "wallet.fif" + args = [fift_script, wallet.path, dest, seqno, coins, boc_mode, boc_path, result_file_path] elif "v2" in wallet.version: - fiftScript = "wallet-v2.fif" - args = [fiftScript, wallet.path, dest, seqno, coins, "-B", bocPath, resultFilePath] + fift_script = "wallet-v2.fif" + args = [fift_script, wallet.path, dest, seqno, coins, boc_mode, boc_path, result_file_path] elif "v3" in wallet.version: - fiftScript = "wallet-v3.fif" - args = [fiftScript, wallet.path, dest, subwallet, seqno, coins, "-B", bocPath, resultFilePath] + fift_script = "wallet-v3.fif" + args = [fift_script, wallet.path, dest, subwallet, seqno, coins, boc_mode, boc_path, result_file_path] + else: + raise Exception(f"SignBocWithWallet error: Wallet version '{wallet.version}' is not supported") if flags: args += flags result = self.fift.Run(args) - resultFilePath = parse(result, "Saved to file ", ")") - return resultFilePath + result_file_path = parse(result, "Saved to file ", ")") + return result_file_path #end define def SendFile(self, filePath, wallet=None, **kwargs): - local.add_log("start SendFile function: " + filePath, "debug") + self.local.add_log("start SendFile function: " + filePath, "debug") timeout = kwargs.get("timeout", 30) remove = kwargs.get("remove", True) - duplicateSendfile = local.db.get("duplicateSendfile", True) + duplicateSendfile = self.local.db.get("duplicateSendfile", True) + telemetry = self.local.db.get("sendTelemetry", False) + duplicateApi = self.local.db.get("duplicateApi", telemetry) if not os.path.isfile(filePath): raise Exception("SendFile error: no such file '{filePath}'".format(filePath=filePath)) if timeout and wallet: @@ -1434,8 +1229,8 @@ def SendFile(self, filePath, wallet=None, **kwargs): try: self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) self.liteClient.Run("sendfile " + filePath, useLocalLiteServer=False) - except Exception as e: - local.add_log('failed to send file via liteclient: ' + str(e), 'info') + except: pass + if duplicateApi: self.send_boc_toncenter(filePath) if timeout and wallet: self.WaitTransaction(wallet, timeout) @@ -1444,24 +1239,30 @@ def SendFile(self, filePath, wallet=None, **kwargs): #end define def send_boc_toncenter(self, file_path: str): - local.add_log('Start send_boc_toncenter function: ' + file_path, 'debug') + self.local.add_log('Start send_boc_toncenter function: ' + file_path, 'debug') with open(file_path, "rb") as f: boc = f.read() boc_b64 = base64.b64encode(boc).decode("utf-8") data = {"boc": boc_b64} - if self.GetNetworkName() == 'testnet': - url = 'https://testnet.toncenter.com/api/v2/sendBoc' + network_name = self.GetNetworkName() + if network_name == 'testnet': + default_url = 'https://testnet.toncenter.com/api/v2/sendBoc' + elif network_name == 'mainnet': + default_url = 'https://toncenter.com/api/v2/sendBoc' else: - url = 'https://toncenter.com/api/v2/sendBoc' + default_url = None + url = self.local.db.get("duplicateApiUrl", default_url) + if url == None: + return False result = requests.post(url=url, json=data) if result.status_code != 200: - local.add_log(f'Failed to send boc to toncenter: {result.content}', 'info') + self.local.add_log(f'Failed to send boc to toncenter: {result.content}', 'info') return False - local.add_log('Sent boc to toncenter', 'info') + self.local.add_log('Sent boc to toncenter', 'info') return True def WaitTransaction(self, wallet, timeout=30): - local.add_log("start WaitTransaction function", "debug") + self.local.add_log("start WaitTransaction function", "debug") timesleep = 3 steps = timeout // timesleep for i in range(steps): @@ -1473,7 +1274,7 @@ def WaitTransaction(self, wallet, timeout=30): #end define def GetReturnedStake(self, fullElectorAddr, inputAddr): - local.add_log("start GetReturnedStake function", "debug") + self.local.add_log("start GetReturnedStake function", "debug") workchain, addr = self.ParseInputAddr(inputAddr) cmd = f"runmethodfull {fullElectorAddr} compute_returned_stake 0x{addr}" result = self.liteClient.Run(cmd) @@ -1485,7 +1286,7 @@ def GetReturnedStake(self, fullElectorAddr, inputAddr): #end define def ProcessRecoverStake(self): - local.add_log("start ProcessRecoverStake function", "debug") + self.local.add_log("start ProcessRecoverStake function", "debug") resultFilePath = self.tempDir + self.nodeName + "recover-query" args = ["recover-stake.fif", resultFilePath] result = self.fift.Run(args) @@ -1494,10 +1295,12 @@ def ProcessRecoverStake(self): #end define def GetStake(self, account, args=None): - stake = local.db.get("stake") - usePool = local.db.get("usePool") - stakePercent = local.db.get("stakePercent", 99) + stake = self.local.db.get("stake") + usePool = self.using_pool() + useController = self.using_liquid_staking() + stakePercent = self.local.db.get("stakePercent", 99) vconfig = self.GetValidatorConfig() + validators = vconfig.get("validators") config17 = self.GetConfig17() # Check if optional arguments have been passed to us @@ -1511,47 +1314,49 @@ def GetStake(self, account, args=None): # Stake was a number stake = int(desiredStake) else: - local.add_log("Specified stake must be a percentage or whole number", "error") + self.local.add_log("Specified stake must be a percentage or whole number", "error") return # Limit stake to maximum available amount minus 10 (for transaction fees) if stake > account.balance - 10: stake = account.balance - 10 - #end if - if stake is None and usePool: + is_single_nominator = self.is_account_single_nominator(account) + + if stake is None and usePool and not is_single_nominator: stake = account.balance - 20 + if stake is None and useController: + stake = account.balance - 50 if stake is None: sp = stakePercent / 100 if sp > 1 or sp < 0: - local.add_log("Wrong stakePercent value. Using default stake.", "warning") + self.local.add_log("Wrong stakePercent value. Using default stake.", "warning") elif len(vconfig.validators) == 0: stake = int(account.balance*sp/2) + if stake < config17["minStake"]: # not enough funds to divide them by 2 + stake = int(account.balance*sp) elif len(vconfig.validators) > 0: stake = int(account.balance*sp) - #end if # Check if we have enough coins if stake > config17["maxStake"]: text = "Stake is greater than the maximum value. Will be used the maximum stake." - local.add_log(text, "warning") + self.local.add_log(text, "warning") stake = config17["maxStake"] if config17["minStake"] > stake: text = "Stake less than the minimum stake. Minimum stake: {minStake}".format(minStake=config17["minStake"]) - #local.add_log(text, "error") + # self.local.add_log(text, "error") raise Exception(text) if stake > account.balance: text = "Don't have enough coins. stake: {stake}, account balance: {balance}".format(stake=stake, balance=account.balance) - #local.add_log(text, "error") + # self.local.add_log(text, "error") raise Exception(text) - #end if return stake - #end define def GetMaxFactor(self): # Either use defined maxFactor, or set maximal allowed by config17 - maxFactor = local.db.get("maxFactor") + maxFactor = self.local.db.get("maxFactor") if maxFactor is None: config17 = self.GetConfig17() maxFactor = config17["maxStakeFactor"] / 65536 @@ -1560,31 +1365,27 @@ def GetMaxFactor(self): #end define def GetValidatorWallet(self, mode="stake"): - local.add_log("start GetValidatorWallet function", "debug") - walletName = local.db.get("validatorWalletName") + self.local.add_log("start GetValidatorWallet function", "debug") + walletName = self.local.db.get("validatorWalletName") wallet = self.GetLocalWallet(walletName) return wallet #end define def ElectionEntry(self, args=None): - usePool = local.db.get("usePool") + usePool = self.using_pool() + useController = self.using_liquid_staking() wallet = self.GetValidatorWallet() addrB64 = wallet.addrB64 if wallet is None: raise Exception("Validator wallet not found") #end if - if usePool: - pool = self.GetPool(mode="stake") - addrB64 = pool.addrB64 - #end if - - local.add_log("start ElectionEntry function", "debug") + self.local.add_log("start ElectionEntry function", "debug") # Check if validator is not synchronized validatorStatus = self.GetValidatorStatus() validatorOutOfSync = validatorStatus.get("outOfSync") if validatorOutOfSync > 60: - local.add_log("Validator is not synchronized", "error") + self.local.add_log("Validator is not synchronized", "error") return #end if @@ -1594,26 +1395,51 @@ def ElectionEntry(self, args=None): # Check if elections started if (startWorkTime == 0): - local.add_log("Elections have not yet begun", "info") + self.local.add_log("Elections have not yet begun", "info") return #end if # Get ADNL address - adnlAddr = self.GetAdnlAddr() + adnl_addr = self.GetAdnlAddr() + adnl_addr_bytes = bytes.fromhex(adnl_addr) # Check wether it is too early to participate - if "participateBeforeEnd" in local.db: + if "participateBeforeEnd" in self.local.db: now = time.time() - if (startWorkTime - now) > local.db["participateBeforeEnd"] and \ - (now + local.db["periods"]["elections"]) < startWorkTime: + if (startWorkTime - now) > self.local.db["participateBeforeEnd"] and \ + (now + self.local.db["periods"]["elections"]) < startWorkTime: return + #end if + + vconfig = self.GetValidatorConfig() + + have_adnl = False + # Check if ADNL address is in the list + for a in vconfig.adnl: + if base64.b64decode(a.id) == adnl_addr_bytes: + have_adnl = True + break + #end for + if not have_adnl: + raise Exception('ADNL address is not found') + #end if + # Check if election entry already completed entries = self.GetElectionEntries() - if adnlAddr in entries: - local.add_log("Elections entry already completed", "info") + if adnl_addr in entries: + self.local.add_log("Elections entry already completed", "info") return #end if + if usePool: + pool = self.get_pool() + addrB64 = pool.addrB64 + elif useController: + controllerAddr = self.GetController(mode="stake") + self.CheckController(controllerAddr) + self.CreateLoanRequest(controllerAddr) + addrB64 = controllerAddr + # Calculate stake account = self.GetAccount(addrB64) stake = self.GetStake(account, args) @@ -1627,24 +1453,32 @@ def ElectionEntry(self, args=None): validatorPubkey_b64 = self.GetPubKeyBase64(validatorKey) # Attach ADNL addr to validator - self.AttachAdnlAddrToValidator(adnlAddr, validatorKey, endWorkTime) + self.AttachAdnlAddrToValidator(adnl_addr, validatorKey, endWorkTime) # Get max factor maxFactor = self.GetMaxFactor() # Create fift's. Continue with pool or walet if usePool: - var1 = self.CreateElectionRequest(pool, startWorkTime, adnlAddr, maxFactor) + var1 = self.CreateElectionRequest(pool.addrB64, startWorkTime, adnl_addr, maxFactor) validatorSignature = self.GetValidatorSignature(validatorKey, var1) - validatorPubkey, resultFilePath = self.SignElectionRequestWithPoolWithValidator(pool, startWorkTime, adnlAddr, validatorPubkey_b64, validatorSignature, maxFactor, stake) + validatorPubkey, resultFilePath = self.SignElectionRequestWithPoolWithValidator(pool, startWorkTime, adnl_addr, validatorPubkey_b64, validatorSignature, maxFactor, stake) # Send boc file to TON resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, pool.addrB64, 1.3) self.SendFile(resultFilePath, wallet) + elif useController: + var1 = self.CreateElectionRequest(controllerAddr, startWorkTime, adnl_addr, maxFactor) + validatorSignature = self.GetValidatorSignature(validatorKey, var1) + validatorPubkey, resultFilePath = self.SignElectionRequestWithController(controllerAddr, startWorkTime, adnl_addr, validatorPubkey_b64, validatorSignature, maxFactor, stake) + + # Send boc file to TON + resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, controllerAddr, 1.03) + self.SendFile(resultFilePath, wallet) else: - var1 = self.CreateElectionRequest(wallet, startWorkTime, adnlAddr, maxFactor) + var1 = self.CreateElectionRequest(wallet.addrB64, startWorkTime, adnl_addr, maxFactor) validatorSignature = self.GetValidatorSignature(validatorKey, var1) - validatorPubkey, resultFilePath = self.SignElectionRequestWithValidator(wallet, startWorkTime, adnlAddr, validatorPubkey_b64, validatorSignature, maxFactor) + validatorPubkey, resultFilePath = self.SignElectionRequestWithValidator(wallet, startWorkTime, adnl_addr, validatorPubkey_b64, validatorSignature, maxFactor) # Send boc file to TON resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, fullElectorAddr, stake) @@ -1652,13 +1486,28 @@ def ElectionEntry(self, args=None): #end if # Save vars to json file - self.SaveElectionVarsToJsonFile(wallet=wallet, account=account, stake=stake, maxFactor=maxFactor, fullElectorAddr=fullElectorAddr, startWorkTime=startWorkTime, validatorsElectedFor=validatorsElectedFor, endWorkTime=endWorkTime, validatorKey=validatorKey, validatorPubkey_b64=validatorPubkey_b64, adnlAddr=adnlAddr, var1=var1, validatorSignature=validatorSignature, validatorPubkey=validatorPubkey) + self.SaveElectionVarsToJsonFile(wallet=wallet, account=account, stake=stake, maxFactor=maxFactor, fullElectorAddr=fullElectorAddr, startWorkTime=startWorkTime, validatorsElectedFor=validatorsElectedFor, endWorkTime=endWorkTime, validatorKey=validatorKey, validatorPubkey_b64=validatorPubkey_b64, adnlAddr=adnl_addr, var1=var1, validatorSignature=validatorSignature, validatorPubkey=validatorPubkey) + self.local.add_log("ElectionEntry completed. Start work time: " + str(startWorkTime)) + + self.clear_tmp() - local.add_log("ElectionEntry completed. Start work time: " + str(startWorkTime)) #end define + def clear_tmp(self): + 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)) + if ts < time.time() - week_ago: + count += 1 + os.remove(os.path.join(dir, f)) + + self.local.add_log(f"Removed {count} old files from tmp dir for {int(time.time() - start)} seconds", "info") + def GetValidatorKeyByTime(self, startWorkTime, endWorkTime): - local.add_log("start GetValidatorKeyByTime function", "debug") + self.local.add_log("start GetValidatorKeyByTime function", "debug") # Check temp key vconfig = self.GetValidatorConfig() for item in vconfig.validators: @@ -1682,18 +1531,18 @@ def RecoverStake(self): raise Exception("Validator wallet not found") #end if - local.add_log("start RecoverStake function", "debug") + self.local.add_log("start RecoverStake function", "debug") fullElectorAddr = self.GetFullElectorAddr() returnedStake = self.GetReturnedStake(fullElectorAddr, wallet.addrB64) if returnedStake == 0: - local.add_log("You have nothing on the return stake", "debug") + self.local.add_log("You have nothing on the return stake", "debug") return #end if resultFilePath = self.ProcessRecoverStake() resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, fullElectorAddr, 1) self.SendFile(resultFilePath, wallet) - local.add_log("RecoverStake completed") + self.local.add_log("RecoverStake completed") #end define def PoolRecoverStake(self, poolAddr): @@ -1702,28 +1551,27 @@ def PoolRecoverStake(self, poolAddr): raise Exception("Validator wallet not found") #end if - local.add_log("start PoolRecoverStake function", "debug") + self.local.add_log("start PoolRecoverStake function", "debug") resultFilePath = self.PoolProcessRecoverStake() resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, poolAddr, 1.2) self.SendFile(resultFilePath, wallet) - local.add_log("PoolRecoverStake completed") + self.local.add_log("PoolRecoverStake completed") #end define def PoolsUpdateValidatorSet(self): - local.add_log("start PoolsUpdateValidatorSet function", "debug") + self.local.add_log("start PoolsUpdateValidatorSet function", "debug") wallet = self.GetValidatorWallet() pools = self.GetPools() for pool in pools: - self.PoolUpdateValidatorSet(pool, wallet) + self.PoolUpdateValidatorSet(pool.addrB64, wallet) #end define - def PoolUpdateValidatorSet(self, pool, wallet): - local.add_log("start PoolUpdateValidatorSet function", "debug") - poolAddr = pool.addrB64 + def PoolUpdateValidatorSet(self, poolAddr, wallet): + self.local.add_log("start PoolUpdateValidatorSet function", "debug") poolData = self.GetPoolData(poolAddr) if poolData is None: return - #en if + #end if timeNow = int(time.time()) config34 = self.GetConfig34() @@ -1741,15 +1589,15 @@ def PoolUpdateValidatorSet(self, pool, wallet): timeNow - poolData["validatorSetChangeTime"] > poolData["stakeHeldFor"] + 60): self.PoolRecoverStake(poolAddr) poolData = self.GetPoolData(poolAddr) - if (poolData["state"] == 0 and self.HasPoolWithdrawRequests(pool)): - self.PoolWithdrawRequests(pool, wallet) + if (poolData["state"] == 0 and self.HasPoolWithdrawRequests(poolAddr)): + self.PoolWithdrawRequests(poolAddr, wallet) poolData = self.GetPoolData(poolAddr) if (poolData["state"] == 0 and poolAddr in pendingWithdraws): self.HandlePendingWithdraw(pendingWithdraws, poolAddr) #end define def PoolProcessUpdateValidatorSet(self, poolAddr, wallet): - local.add_log("start PoolProcessUpdateValidatorSet function", "debug") + self.local.add_log("start PoolProcessUpdateValidatorSet function", "debug") resultFilePath = self.tempDir + "pool-update-validator-set-query.boc" fiftScript = self.contractsDir + "nominator-pool/func/update-validator-set.fif" args = [fiftScript, resultFilePath] @@ -1757,19 +1605,19 @@ def PoolProcessUpdateValidatorSet(self, poolAddr, wallet): resultFilePath = parse(result, "Saved to file ", '\n') resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, poolAddr, 1.1) self.SendFile(resultFilePath, wallet) - local.add_log("PoolProcessUpdateValidatorSet completed") + self.local.add_log("PoolProcessUpdateValidatorSet completed") #end define - def PoolWithdrawRequests(self, pool, wallet): - local.add_log("start PoolWithdrawRequests function", "debug") + def PoolWithdrawRequests(self, poolAddr, wallet): + self.local.add_log("start PoolWithdrawRequests function", "debug") resultFilePath = self.PoolProcessWihtdrawRequests() - resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, pool.addrB64, 10) + resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, poolAddr, 10) self.SendFile(resultFilePath, wallet) - local.add_log("PoolWithdrawRequests completed") + self.local.add_log("PoolWithdrawRequests completed") #end define def PoolProcessWihtdrawRequests(self): - local.add_log("start PoolProcessWihtdrawRequests function", "debug") + self.local.add_log("start PoolProcessWihtdrawRequests function", "debug") resultFilePath = self.tempDir + "pool-withdraw-requests-query.boc" fiftScript = self.contractsDir + "nominator-pool/func/process-withdraw-requests.fif" args = [fiftScript, resultFilePath] @@ -1778,8 +1626,8 @@ def PoolProcessWihtdrawRequests(self): return resultFilePath #end define - def HasPoolWithdrawRequests(self, pool): - cmd = f"runmethodfull {pool.addrB64} has_withdraw_requests" + def HasPoolWithdrawRequests(self, poolAddr): + cmd = f"runmethodfull {poolAddr} has_withdraw_requests" result = self.liteClient.Run(cmd) buff = self.Result2List(result) data = int(buff[0]) @@ -1790,7 +1638,7 @@ def HasPoolWithdrawRequests(self, pool): #end define def SaveElectionVarsToJsonFile(self, **kwargs): - local.add_log("start SaveElectionVarsToJsonFile function", "debug") + self.local.add_log("start SaveElectionVarsToJsonFile function", "debug") fileName = self.tempDir + self.nodeName + str(kwargs.get("startWorkTime")) + "_ElectionEntry.json" wallet = kwargs.get("wallet") account = kwargs.get("account") @@ -1805,24 +1653,18 @@ def SaveElectionVarsToJsonFile(self, **kwargs): #ned define def CreateWallet(self, name, workchain=0, version="v1", **kwargs): - local.add_log("start CreateWallet function", "debug") - subwalletDefault = 698983191 + workchain # 0x29A9A317 + workchain - subwallet = kwargs.get("subwallet", subwalletDefault) - walletPath = self.walletsDir + name - if os.path.isfile(walletPath + ".pk") and "v3" not in version: - local.add_log("CreateWallet error: Wallet already exists: " + name, "warning") + self.local.add_log("start CreateWallet function", "debug") + subwallet_default = 698983191 + workchain # 0x29A9A317 + workchain + subwallet = kwargs.get("subwallet", subwallet_default) + wallet_path = self.walletsDir + name + if os.path.isfile(wallet_path + ".pk") and "v3" not in version: + self.local.add_log("CreateWallet error: Wallet already exists: " + name, "warning") else: - if "v1" in version: - fiftScript = "new-wallet.fif" - args = [fiftScript, workchain, walletPath] - if "v2" in version: - fiftScript = "new-wallet-v2.fif" - args = [fiftScript, workchain, walletPath] - if "v3" in version: - fiftScript = "new-wallet-v3.fif" - args = [fiftScript, workchain, subwallet, walletPath] - result = self.fift.Run(args) + 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: + print(result) raise Exception("CreateWallet error") #end if wallet = self.GetLocalWallet(name, version) @@ -1832,15 +1674,15 @@ def CreateWallet(self, name, workchain=0, version="v1", **kwargs): def CreateHighWallet(self, name, **kwargs): workchain = kwargs.get("workchain", 0) - subwalletDefault = 698983191 + workchain # 0x29A9A317 + workchain - subwallet = kwargs.get("subwallet", subwalletDefault) + subwallet_default = 698983191 + workchain # 0x29A9A317 + workchain + subwallet = kwargs.get("subwallet", subwallet_default) version = kwargs.get("version", "hv1") - local.add_log("start CreateHighWallet function", "debug") - walletPath = self.walletsDir + name - if os.path.isfile(walletPath + ".pk") and os.path.isfile(walletPath + str(subwallet) + ".addr"): - local.add_log("CreateHighWallet error: Wallet already exists: " + name + str(subwallet), "warning") + self.local.AddLog("start CreateHighWallet function", "debug") + wallet_path = self.walletsDir + name + if os.path.isfile(wallet_path + ".pk") and os.path.isfile(wallet_path + str(subwallet) + ".addr"): + self.local.AddLog("CreateHighWallet error: Wallet already exists: " + name + str(subwallet), "warning") else: - args = ["new-highload-wallet.fif", workchain, subwallet, walletPath] + args = ["new-highload-wallet.fif", workchain, subwallet, wallet_path] result = self.fift.Run(args) if "Creating new high-load wallet" not in result: raise Exception("CreateHighWallet error") @@ -1851,48 +1693,89 @@ def CreateHighWallet(self, name, **kwargs): #end define def ActivateWallet(self, wallet): - local.add_log("start ActivateWallet function", "debug") + self.local.add_log("start ActivateWallet function", "debug") account = self.GetAccount(wallet.addrB64) if account.status == "empty": raise Exception("ActivateWallet error: account status is empty") elif account.status == "active": - local.add_log("ActivateWallet warning: account status is active", "warning") + self.local.add_log("ActivateWallet warning: account status is active", "warning") else: self.SendFile(wallet.bocFilePath, wallet, remove=False) #end define - def ImportWallet(self, addrB64, key): - workchain, addr, bounceable = self.ParseAddrB64(addrB64) - workchain_bytes = int.to_bytes(workchain, 4, "big", signed=True) - addr_bytes = bytes.fromhex(addr) - key_bytes = base64.b64decode(key) + 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 - walletName = self.GenerateWalletName() - walletPath = self.walletsDir + walletName - file = open(walletPath + ".addr", 'wb') - file.write(addr_bytes + workchain_bytes) - file.close() + def import_wallet_with_version(self, key, version, **kwargs): + wallet_name = kwargs.get("wallet_name") + workchain = kwargs.get("workchain", 0) + subwallet_default = 698983191 + workchain # 0x29A9A317 + workchain + subwallet = kwargs.get("subwallet", subwallet_default) + if type(key) == bytes: + pk_bytes = key + else: + pk_bytes = base64.b64decode(key) + if wallet_name == None: + wallet_name = self.GenerateWalletName() + 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, + wallet_path=wallet_path, subwallet=subwallet) + result = self.fift.Run(fift_args) + if "Creating new" not in result: + print(result) + raise Exception("import_wallet_with_version error") + wallet = self.GetLocalWallet(wallet_name, version) + self.SetWalletVersion(wallet.addrB64, version) + return wallet + #end define - file = open(walletPath + ".pk", 'wb') - file.write(key_bytes) - file.close() + def get_new_wallet_fift_args(self, version, **kwargs): + workchain = kwargs.get("workchain") + wallet_path = kwargs.get("wallet_path") + subwallet = kwargs.get("subwallet") + if "v1" in version: + fift_script = "new-wallet.fif" + args = [fift_script, workchain, wallet_path] + elif "v2" in version: + fift_script = "new-wallet-v2.fif" + args = [fift_script, workchain, wallet_path] + elif "v3" in version: + fift_script = "new-wallet-v3.fif" + args = [fift_script, workchain, subwallet, wallet_path] + else: + raise Exception(f"get_wallet_fift error: fift script for `{version}` not found") + return args + #end define - return walletName + def addr_b64_to_bytes(self, addr_b64): + workchain, addr, bounceable = self.ParseAddrB64(addr_b64) + workchain_bytes = int.to_bytes(workchain, 4, "big", signed=True) + addr_bytes = bytes.fromhex(addr) + result = addr_bytes + workchain_bytes + return result #end define def ExportWallet(self, walletName): wallet = self.GetLocalWallet(walletName) - - file = open(wallet.privFilePath, 'rb') - data = file.read() - file.close() + 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): - local.add_log("start GetWalletsNameList function", "debug") + self.local.add_log("start GetWalletsNameList function", "debug") walletsNameList = list() for fileName in os.listdir(self.walletsDir): if fileName.endswith(".addr"): @@ -1905,7 +1788,7 @@ def GetWalletsNameList(self): #end define def GetWallets(self): - local.add_log("start GetWallets function", "debug") + self.local.add_log("start GetWallets function", "debug") wallets = list() walletsNameList = self.GetWalletsNameList() for walletName in walletsNameList: @@ -1915,7 +1798,7 @@ def GetWallets(self): #end define def GenerateWalletName(self): - local.add_log("start GenerateWalletName function", "debug") + self.local.add_log("start GenerateWalletName function", "debug") index = 1 index_str = str(index).rjust(3, '0') walletPrefix = "wallet_" @@ -1937,7 +1820,7 @@ def GenerateWalletName(self): #end define def WalletsCheck(self): - local.add_log("start WalletsCheck function", "debug") + self.local.add_log("start WalletsCheck function", "debug") wallets = self.GetWallets() for wallet in wallets: if os.path.isfile(wallet.bocFilePath): @@ -1947,16 +1830,16 @@ def WalletsCheck(self): #end define def GetValidatorConfig(self): - #local.add_log("start GetValidatorConfig function", "debug") + #self.local.add_log("start GetValidatorConfig function", "debug") result = self.validatorConsole.Run("getconfig") text = parse(result, "---------", "--------") vconfig = json.loads(text) return Dict(vconfig) #end define - + def GetOverlaysStats(self): - local.add_log("start GetOverlaysStats function", "debug") - resultFilePath = local.buffer.my_temp_dir + "getoverlaysstats.json" + self.local.add_log("start GetOverlaysStats function", "debug") + resultFilePath = self.local.buffer.my_temp_dir + "getoverlaysstats.json" result = self.validatorConsole.Run(f"getoverlaysstatsjson {resultFilePath}") if "wrote stats" not in result: raise Exception(f"GetOverlaysStats error: {result}") @@ -1979,8 +1862,27 @@ def GetWalletId(self, wallet): return subwallet #end define + def check_account_balance(self, account, coins): + if not isinstance(account, Account): + account = self.GetAccount(account) + if account.balance < coins: + raise Exception(f"Account {account.addrB64} balance is less than requested coins. Balance: {account.balance}, requested amount: {coins} (need {coins - account.balance} more)") + # end if + # end define + + def check_account_active(self, account): + if not isinstance(account, Account): + address = account + account = self.GetAccount(account) + else: + address = account.addrB64 + if account.status != "active": + raise Exception(f"Account {address} account is uninitialized") + # end if + # end define + def MoveCoins(self, wallet, dest, coins, **kwargs): - local.add_log("start MoveCoins function", "debug") + self.local.add_log("start MoveCoins function", "debug") flags = kwargs.get("flags", list()) timeout = kwargs.get("timeout", 30) subwallet = kwargs.get("subwallet") @@ -1999,25 +1901,22 @@ def MoveCoins(self, wallet, dest, coins, **kwargs): # Balance checking account = self.GetAccount(wallet.addrB64) - if account.balance < coins + 0.1: - raise Exception("Wallet balance is less than requested coins") - if account.status != "active": - raise Exception("Wallet account is uninitialized") - #end if - + self.check_account_balance(account, coins + 0.1) + self.check_account_active(account) + # Bounceable checking destAccount = self.GetAccount(dest) bounceable = self.IsBounceableAddrB64(dest) if bounceable == False and destAccount.status == "active": flags += ["-b"] text = "Find non-bounceable flag, but destination account already active. Using bounceable flag" - local.add_log(text, "warning") + self.local.add_log(text, "warning") elif "-n" not in flags and bounceable == True and destAccount.status != "active": raise Exception("Find bounceable flag, but destination account is not active. Use non-bounceable address or flag -n") #end if seqno = self.GetSeqno(wallet) - resultFilePath = local.buffer.my_temp_dir + wallet.name + "_wallet-query" + resultFilePath = self.local.buffer.my_temp_dir + wallet.name + "_wallet-query" if "v1" in wallet.version: fiftScript = "wallet.fif" args = [fiftScript, wallet.path, dest, seqno, coins, "-m", mode, resultFilePath] @@ -2027,6 +1926,8 @@ def MoveCoins(self, wallet, dest, coins, **kwargs): elif "v3" in wallet.version: fiftScript = "wallet-v3.fif" args = [fiftScript, wallet.path, dest, subwallet, seqno, coins, "-m", mode, resultFilePath] + else: + raise Exception(f"MoveCoins error: Wallet version '{wallet.version}' is not supported") if flags: args += flags result = self.fift.Run(args) @@ -2035,7 +1936,7 @@ def MoveCoins(self, wallet, dest, coins, **kwargs): #end define def MoveCoinsThroughProxy(self, wallet, dest, coins): - local.add_log("start MoveCoinsThroughProxy function", "debug") + 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) @@ -2048,16 +1949,16 @@ def MoveCoinsThroughProxy(self, wallet, dest, coins): #end define def MoveCoinsFromHW(self, wallet, destList, **kwargs): - local.add_log("start MoveCoinsFromHW function", "debug") + self.local.add_log("start MoveCoinsFromHW function", "debug") flags = kwargs.get("flags") timeout = kwargs.get("timeout", 30) if len(destList) == 0: - local.add_log("MoveCoinsFromHW warning: destList is empty, break function", "warning") + self.local.add_log("MoveCoinsFromHW warning: destList is empty, break function", "warning") return #end if - orderFilePath = local.buffer.my_temp_dir + wallet.name + "_order.txt" + orderFilePath = self.local.buffer.my_temp_dir + wallet.name + "_order.txt" lines = list() for dest, coins in destList: lines.append("SEND {dest} {coins}".format(dest=dest, coins=coins)) @@ -2071,7 +1972,7 @@ def MoveCoinsFromHW(self, wallet, destList, **kwargs): elif "v2" in wallet.version: fiftScript = "highload-wallet-v2.fif" seqno = self.GetSeqno(wallet) - resultFilePath = local.buffer.my_temp_dir + wallet.name + "_wallet-query" + resultFilePath = self.local.buffer.my_temp_dir + wallet.name + "_wallet-query" args = [fiftScript, wallet.path, wallet.subwallet, seqno, orderFilePath, resultFilePath] if flags: args += flags @@ -2121,7 +2022,7 @@ def GetElectionEntries(self, past=False): #end if # Get raw data - local.add_log("start GetElectionEntries function", "debug") + self.local.add_log("start GetElectionEntries function", "debug") cmd = "runmethodfull {fullElectorAddr} participant_list_extended".format(fullElectorAddr=fullElectorAddr) result = self.liteClient.Run(cmd) rawElectionEntries = self.Result2List(result) @@ -2163,10 +2064,10 @@ def GetElectionEntries(self, past=False): def GetSaveElections(self): timestamp = get_timestamp() - saveElections = local.db.get("saveElections") + saveElections = self.local.db.get("saveElections") if saveElections is None: saveElections = dict() - local.db["saveElections"] = saveElections + self.local.db["saveElections"] = saveElections buff = saveElections.copy() for key, item in buff.items(): diffTime = timestamp - int(key) @@ -2182,8 +2083,13 @@ def GetSaveElectionEntries(self, electionId): return result #end define + def calculate_offer_pseudohash(self, offer_hash: str, param_id: int): + config_val = self.GetConfig(param_id) + pseudohash_bytes = offer_hash.encode() + json.dumps(config_val, sort_keys=True).encode() + return hashlib.sha256(pseudohash_bytes).hexdigest() + def GetOffers(self): - local.add_log("start GetOffers function", "debug") + self.local.add_log("start GetOffers function", "debug") fullConfigAddr = self.GetFullConfigAddr() # Get raw data cmd = "runmethodfull {fullConfigAddr} list_proposals".format(fullConfigAddr=fullConfigAddr) @@ -2203,8 +2109,8 @@ def GetOffers(self): # Create dict # parser from: https://github.com/ton-blockchain/ton/blob/dab7ee3f9794db5a6d32c895dbc2564f681d9126/crypto/smartcont/config-code.fc#L607 - item = dict() - item["config"] = dict() + item = Dict() + item["config"] = Dict() item["hash"] = hash item["endTime"] = subdata[0] # *expires* item["critFlag"] = subdata[1] # *critical* @@ -2226,16 +2132,14 @@ def GetOffers(self): item["approvedPercent"] = round(availableWeight / totalWeight * 100, 3) item["isPassed"] = (weightRemaining < 0) #item["pseudohash"] = hashlib.sha256(param_val.encode()).hexdigest() - config_val = self.GetConfig(param_id) - pseudohash_bytes = hash.encode() + json.dumps(config_val, sort_keys=True).encode() - item["pseudohash"] = hashlib.sha256(pseudohash_bytes).hexdigest() + item['pseudohash'] = self.calculate_offer_pseudohash(hash, param_id) offers.append(item) #end for return offers #end define def GetOfferDiff(self, offerHash): - local.add_log("start GetOfferDiff function", "debug") + self.local.add_log("start GetOfferDiff function", "debug") offer = self.GetOffer(offerHash) configId = offer["config"]["id"] configValue = offer["config"]["value"] @@ -2332,7 +2236,7 @@ def GetComplaints(self, electionId=None, past=False): #end if # Get raw data - local.add_log("start GetComplaints function", "debug") + self.local.add_log("start GetComplaints function", "debug") cmd = "runmethodfull {fullElectorAddr} list_complaints {electionId}".format(fullElectorAddr=fullElectorAddr, electionId=electionId) result = self.liteClient.Run(cmd) rawComplaints = self.Result2List(result) @@ -2355,6 +2259,7 @@ def GetComplaints(self, electionId=None, past=False): buff = subdata[0] # *complaint* item["electionId"] = electionId item["hash"] = chash + item["hash_hex"] = dec2hex(chash) pubkey = Dec2HexAddr(buff[0]) # *validator_pubkey* adnl = self.GetAdnlFromPubkey(pubkey) item["pubkey"] = pubkey @@ -2384,9 +2289,12 @@ def GetComplaints(self, electionId=None, past=False): item["isPassed"] = (weightRemaining < 0) pseudohash = pubkey + str(electionId) item["pseudohash"] = pseudohash - complaints[pseudohash] = item + complaints[chash] = item #end for + # sort complaints by their creation time and hash + complaints = dict(sorted(complaints.items(), key=lambda item: (item[1]["createdTime"], item[0]))) + # Set buffer self.SetFunctionBuffer(bname, complaints) @@ -2400,10 +2308,10 @@ def GetComplaints(self, electionId=None, past=False): def GetSaveComplaints(self): timestamp = get_timestamp() - saveComplaints = local.db.get("saveComplaints") + saveComplaints = self.local.db.get("saveComplaints") if saveComplaints is None: saveComplaints = dict() - local.db["saveComplaints"] = saveComplaints + self.local.db["saveComplaints"] = saveComplaints buff = saveComplaints.copy() for key, item in buff.items(): diffTime = timestamp - int(key) @@ -2423,16 +2331,13 @@ def GetAdnlFromPubkey(self, inputPubkey): #end define def GetComplaintsNumber(self): - local.add_log("start GetComplaintsNumber function", "debug") + self.local.add_log("start GetComplaintsNumber function", "debug") result = dict() complaints = self.GetComplaints() - votedComplaints = self.GetVotedComplaints() + voted_complaints = self.GetVotedComplaints(complaints) buff = 0 - for key, item in complaints.items(): - pubkey = item.get("pubkey") - electionId = item.get("electionId") - pseudohash = pubkey + str(electionId) - if pseudohash in votedComplaints: + for chash in complaints: + if chash in voted_complaints: continue buff += 1 result["all"] = len(complaints) @@ -2440,17 +2345,8 @@ def GetComplaintsNumber(self): return result #end define - def GetComplaint(self, electionId, complaintHash): - local.add_log("start GetComplaint function", "debug") - complaints = self.GetComplaints(electionId) - for key, item in complaints.items(): - if complaintHash == item.get("hash"): - return item - raise Exception("GetComplaint error: complaint not found.") - #end define - def SignProposalVoteRequestWithValidator(self, offerHash, validatorIndex, validatorPubkey_b64, validatorSignature): - local.add_log("start SignProposalVoteRequestWithValidator function", "debug") + self.local.add_log("start SignProposalVoteRequestWithValidator function", "debug") fileName = self.tempDir + self.nodeName + "proposal_vote-msg-body.boc" args = ["config-proposal-vote-signed.fif", "-i", validatorIndex, offerHash, validatorPubkey_b64, validatorSignature, fileName] result = self.fift.Run(args) @@ -2459,7 +2355,7 @@ def SignProposalVoteRequestWithValidator(self, offerHash, validatorIndex, valida #end define def SignComplaintVoteRequestWithValidator(self, complaintHash, electionId, validatorIndex, validatorPubkey_b64, validatorSignature): - local.add_log("start SignComplaintRequestWithValidator function", "debug") + self.local.add_log("start SignComplaintRequestWithValidator function", "debug") fileName = self.tempDir + "complaint_vote-msg-body.boc" args = ["complaint-vote-signed.fif", validatorIndex, electionId, complaintHash, validatorPubkey_b64, validatorSignature, fileName] result = self.fift.Run(args) @@ -2468,7 +2364,7 @@ def SignComplaintVoteRequestWithValidator(self, complaintHash, electionId, valid #end define def VoteOffer(self, offerHash): - local.add_log("start VoteOffer function", "debug") + self.local.add_log("start VoteOffer function", "debug") fullConfigAddr = self.GetFullConfigAddr() wallet = self.GetValidatorWallet(mode="vote") validatorKey = self.GetValidatorKey() @@ -2476,9 +2372,9 @@ def VoteOffer(self, offerHash): validatorIndex = self.GetValidatorIndex() offer = self.GetOffer(offerHash) if validatorIndex in offer.get("votedValidators"): - local.add_log("Proposal already has been voted", "debug") + self.local.add_log("Proposal already has been voted", "debug") return - self.AddSaveOffer(offer) + self.add_save_offer(offer) var1 = self.CreateConfigProposalRequest(offerHash, validatorIndex) validatorSignature = self.GetValidatorSignature(validatorKey, var1) resultFilePath = self.SignProposalVoteRequestWithValidator(offerHash, validatorIndex, validatorPubkey_b64, validatorSignature) @@ -2487,29 +2383,22 @@ def VoteOffer(self, offerHash): #end define def VoteComplaint(self, electionId, complaintHash): - local.add_log("start VoteComplaint function", "debug") + self.local.add_log("start VoteComplaint function", "debug") complaintHash = int(complaintHash) fullElectorAddr = self.GetFullElectorAddr() wallet = self.GetValidatorWallet(mode="vote") validatorKey = self.GetValidatorKey() validatorPubkey_b64 = self.GetPubKeyBase64(validatorKey) validatorIndex = self.GetValidatorIndex() - complaint = self.GetComplaint(electionId, complaintHash) - votedValidators = complaint.get("votedValidators") - pubkey = complaint.get("pubkey") - if validatorIndex in votedValidators: - local.add_log("Complaint already has been voted", "info") - return var1 = self.CreateComplaintRequest(electionId, complaintHash, validatorIndex) validatorSignature = self.GetValidatorSignature(validatorKey, var1) resultFilePath = self.SignComplaintVoteRequestWithValidator(complaintHash, electionId, validatorIndex, validatorPubkey_b64, validatorSignature) resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, fullElectorAddr, 1.5) self.SendFile(resultFilePath, wallet) - self.AddVotedComplaints(complaint) #end define def SaveComplaints(self, electionId): - local.add_log("start SaveComplaints function", "debug") + self.local.add_log("start SaveComplaints function", "debug") filePrefix = self.tempDir + "scheck_" cmd = "savecomplaints {electionId} {filePrefix}".format(electionId=electionId, filePrefix=filePrefix) result = self.liteClient.Run(cmd) @@ -2528,9 +2417,9 @@ def SaveComplaints(self, electionId): return complaintsHashes #end define - def CheckComplaint(self, filePath): - local.add_log("start CheckComplaint function", "debug") - cmd = "loadproofcheck {filePath}".format(filePath=filePath) + def CheckComplaint(self, file_path: str): + self.local.add_log("start CheckComplaint function", "debug") + cmd = "loadproofcheck {filePath}".format(filePath=file_path) result = self.liteClient.Run(cmd, timeout=30) lines = result.split('\n') ok = False @@ -2544,6 +2433,57 @@ def CheckComplaint(self, filePath): return ok #end define + def get_valid_complaints(self, complaints: dict, election_id: int): + self.local.add_log("start get_valid_complaints function", "debug") + config32 = self.GetConfig32() + start = config32.get("startWorkTime") + assert start == election_id, 'provided election_id != election_id from config32' + end = config32.get("endWorkTime") + validators_load = self.GetValidatorsLoad(start, end - 60, saveCompFiles=True) + voted_complaints = self.GetVotedComplaints(complaints) + voted_complaints_pseudohashes = [complaint['pseudohash'] for complaint in voted_complaints.values()] + result = {} + for complaint in complaints.values(): + if complaint['pseudohash'] in voted_complaints_pseudohashes or complaint['pseudohash'] in result: + self.local.add_log(f"skip checking complaint {complaint['hash_hex']}: " + f"complaint with this pseudohash ({complaint['pseudohash']})" + f" has already been voted", "debug") + continue + # check that complaint is valid + + if complaint['electionId'] != start: + self.local.add_log(f"skip checking complaint {complaint['hash_hex']}: " + f"election_id ({election_id}) doesn't match with " + f"start work time ({config32.get('startWorkTime')})", "info") + continue + + exists = False + for item in validators_load.values(): + if 'fileName' not in item: + continue + pubkey = item.get("pubkey") + if pubkey is None: + continue + pseudohash = pubkey + str(election_id) + if pseudohash == complaint['pseudohash']: + exists = True + 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 + + # check complaint fine value + if complaint['suggestedFine'] != 101: # https://github.com/ton-blockchain/ton/blob/5847897b3758bc9ea85af38e7be8fc867e4c133a/lite-client/lite-client.cpp#L3708 + self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint fine value is {complaint['suggestedFine']} ton", "info") + continue + if complaint['suggestedFinePart'] != 0: # https://github.com/ton-blockchain/ton/blob/5847897b3758bc9ea85af38e7be8fc867e4c133a/lite-client/lite-client.cpp#L3709 + self.local.add_log(f"complaint {complaint['hash_hex']} declined: complaint fine part value is {complaint['suggestedFinePart']} ton", "info") + continue + + result[complaint['pseudohash']] = complaint + return result + def GetOnlineValidators(self): onlineValidators = list() validators = self.GetValidatorsList() @@ -2556,7 +2496,7 @@ def GetOnlineValidators(self): return onlineValidators #end define - def GetValidatorsLoad(self, start, end, saveCompFiles=False): + def GetValidatorsLoad(self, start, end, saveCompFiles=False) -> dict: # Get buffer bname = f"validatorsLoad{start}{end}" buff = self.GetFunctionBuffer(bname, timeout=60) @@ -2565,7 +2505,7 @@ def GetValidatorsLoad(self, start, end, saveCompFiles=False): #end if text = "start GetValidatorsLoad function ({}, {})".format(start, end) - local.add_log(text, "debug") + self.local.add_log(text, "debug") if saveCompFiles is True: filePrefix = self.tempDir + f"checkload_{start}_{end}" else: @@ -2646,7 +2586,7 @@ def GetValidatorsList(self, past=False): if buff: return buff #end if - + timestamp = get_timestamp() end = timestamp - 60 start = end - 2000 @@ -2671,26 +2611,17 @@ def GetValidatorsList(self, past=False): if saveElectionEntries and adnlAddr in saveElectionEntries: validator["walletAddr"] = saveElectionEntries[adnlAddr]["walletAddr"] #end for - + # Set buffer self.SetFunctionBuffer(bname, validators) return validators #end define - def find_myself_in_el(self): - save_elections = self.GetSaveElections() - my_adnl = self.GetAdnlAddr() - for election_id, election in save_elections.items(): - for adnl in election: - if adnl == my_adnl: - return True - return False - #end define - def CheckValidators(self, start, end): - local.add_log("start CheckValidators function", "debug") + self.local.add_log("start CheckValidators function", "debug") electionId = start complaints = self.GetComplaints(electionId) + valid_complaints = self.get_valid_complaints(complaints, electionId) data = self.GetValidatorsLoad(start, end, saveCompFiles=True) fullElectorAddr = self.GetFullElectorAddr() wallet = self.GetValidatorWallet(mode="vote") @@ -2709,17 +2640,18 @@ def CheckValidators(self, start, end): var2 = item.get("var2") pubkey = item.get("pubkey") pseudohash = pubkey + str(electionId) - if pseudohash in complaints: + if pseudohash in valid_complaints: continue # Create complaint + fileName = self.remove_proofs_from_complaint(fileName) fileName = self.PrepareComplaint(electionId, fileName) fileName = self.SignBocWithWallet(wallet, fileName, fullElectorAddr, 300) self.SendFile(fileName, wallet) - local.add_log("var1: {}, var2: {}, pubkey: {}, election_id: {}".format(var1, var2, pubkey, electionId), "debug") + self.local.add_log("var1: {}, var2: {}, pubkey: {}, election_id: {}".format(var1, var2, pubkey, electionId), "debug") #end define def GetOffer(self, offerHash): - local.add_log("start GetOffer function", "debug") + self.local.add_log("start GetOffer function", "debug") offers = self.GetOffers() for offer in offers: if offerHash == offer.get("hash"): @@ -2728,7 +2660,7 @@ def GetOffer(self, offerHash): #end define def GetOffersNumber(self): - local.add_log("start GetOffersNumber function", "debug") + self.local.add_log("start GetOffersNumber function", "debug") result = dict() offers = self.GetOffers() saveOffers = self.GetSaveOffers() @@ -2754,12 +2686,12 @@ def GetValidatorIndex(self, adnlAddr=None): if adnlAddr == searchAdnlAddr: return index index += 1 - local.add_log("GetValidatorIndex warning: index not found.", "warning") + self.local.add_log("GetValidatorIndex warning: index not found.", "warning") return -1 #end define def GetValidatorEfficiency(self, adnlAddr=None): - local.add_log("start GetValidatorEfficiency function", "debug") + self.local.add_log("start GetValidatorEfficiency function", "debug") validators = self.GetValidatorsList() if adnlAddr is None: adnlAddr = self.GetAdnlAddr() @@ -2768,7 +2700,7 @@ def GetValidatorEfficiency(self, adnlAddr=None): if adnlAddr == searchAdnlAddr: efficiency = validator.get("efficiency") return efficiency - local.add_log("GetValidatorEfficiency warning: efficiency not found.", "warning") + self.local.add_log("GetValidatorEfficiency warning: efficiency not found.", "warning") #end define def GetDbUsage(self): @@ -2778,7 +2710,7 @@ def GetDbUsage(self): #end define def GetDbSize(self, exceptions="log"): - local.add_log("start GetDbSize function", "debug") + self.local.add_log("start GetDbSize function", "debug") exceptions = exceptions.split() totalSize = 0 path = "/var/ton-work/" @@ -2794,6 +2726,43 @@ 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): + import socket + return socket.inet_ntoa(struct.pack("!i", dec)) + + vconfig = self.GetValidatorConfig() + + data = {"host": int2ip(vconfig["addrs"][0]["ip"]), "port": vconfig["addrs"][0]["port"]} + + dht_id = vconfig["dht"][0]["id"] + dht_id_hex = base64.b64decode(dht_id).hex().upper() + + result = self.validatorConsole.Run(f"exportpub {dht_id_hex}") + pubkey = parse(result, "got public key: ", "\n") + data["pubkey"] = base64.b64encode(base64.b64decode(pubkey)[4:]).decode() + return data + #end define + def Result2List(self, text): buff = parse(text, "result:", "\n") if buff is None or "error" in buff: @@ -2928,10 +2897,10 @@ def GetItemFromDict(self, data, search): #end for return None #end define - + def GetDomainFromAuction(self, walletName, addr): wallet = self.GetLocalWallet(walletName) - bocPath = local.buffer.my_temp_dir + "get_dns_data.boc" + bocPath = self.local.buffer.my_temp_dir + "get_dns_data.boc" bocData = bytes.fromhex("b5ee9c7241010101000e0000182fcb26a20000000000000000f36cae4d") with open(bocPath, 'wb') as file: file.write(bocData) @@ -2940,7 +2909,7 @@ def GetDomainFromAuction(self, walletName, addr): #end define def NewDomain(self, domain): - local.add_log("start NewDomain function", "debug") + self.local.add_log("start NewDomain function", "debug") domainName = domain["name"] buff = domainName.split('.') subdomain = buff.pop(0) @@ -2966,15 +2935,15 @@ def NewDomain(self, domain): #end define def AddDomain(self, domain): - if "domains" not in local.db: - local.db["domains"] = list() + if "domains" not in self.local.db: + self.local.db["domains"] = list() #end if - local.db["domains"].append(domain) - local.save() + self.local.db["domains"].append(domain) + self.local.save() #end define def GetDomains(self): - domains = local.db.get("domains", list()) + domains = self.local.db.get("domains", list()) for domain in domains: domainName = domain.get("name") domain["endTime"] = self.GetDomainEndTime(domainName) @@ -2990,39 +2959,39 @@ def GetDomain(self, domainName): #end define def DeleteDomain(self, domainName): - domains = local.db.get("domains") + domains = self.local.db.get("domains") for domain in domains: if (domainName == domain.get("name")): domains.remove(domain) - local.save() + self.local.save() return raise Exception("DeleteDomain error: Domain not found") #end define def GetAutoTransferRules(self): - autoTransferRules = local.db.get("autoTransferRules") + autoTransferRules = self.local.db.get("autoTransferRules") if autoTransferRules is None: autoTransferRules = list() - local.db["autoTransferRules"] = autoTransferRules + self.local.db["autoTransferRules"] = autoTransferRules return autoTransferRules #end define def AddAutoTransferRule(self, rule): autoTransferRules = self.GetAutoTransferRules() autoTransferRules.append(rule) - local.save() + self.local.save() #end define def AddBookmark(self, bookmark): - if "bookmarks" not in local.db: - local.db["bookmarks"] = list() + if "bookmarks" not in self.local.db: + self.local.db["bookmarks"] = list() #end if - local.db["bookmarks"].append(bookmark) - local.save() + self.local.db["bookmarks"].append(bookmark) + self.local.save() #end define def GetBookmarks(self): - bookmarks = local.db.get("bookmarks") + bookmarks = self.local.db.get("bookmarks") if bookmarks is not None: for bookmark in bookmarks: self.WriteBookmarkData(bookmark) @@ -3030,7 +2999,7 @@ def GetBookmarks(self): #end define def GetBookmarkAddr(self, type, name): - bookmarks = local.db.get("bookmarks", list()) + bookmarks = self.local.db.get("bookmarks", list()) for bookmark in bookmarks: bookmarkType = bookmark.get("type") bookmarkName = bookmark.get("name") @@ -3041,13 +3010,13 @@ def GetBookmarkAddr(self, type, name): #end define def DeleteBookmark(self, name, type): - bookmarks = local.db.get("bookmarks") + bookmarks = self.local.db.get("bookmarks") for bookmark in bookmarks: bookmarkType = bookmark.get("type") bookmarkName = bookmark.get("name") if (type == bookmarkType and name == bookmarkName): bookmarks.remove(bookmark) - local.save() + self.local.save() return raise Exception("DeleteBookmark error: Bookmark not found") #end define @@ -3073,39 +3042,47 @@ def WriteBookmarkData(self, bookmark): bookmark["data"] = data #end define + def offers_gc(self, save_offers): + current_offers = self.GetOffers() + current_offers_hashes = [offer.get("hash") for offer in current_offers] + for offer_hash, offer in list(save_offers.items()): + if offer_hash not in current_offers_hashes: + if isinstance(offer, list): + param_id = offer[1] + if param_id is not None and offer[0] != self.calculate_offer_pseudohash(offer_hash, param_id): + # param has been changed so no need to keep anymore + save_offers.pop(offer_hash) + else: # old version of offer in db + save_offers.pop(offer_hash) + return save_offers + def GetSaveOffers(self): bname = "saveOffers" - saveOffers = local.db.get(bname) - if saveOffers is None or isinstance(saveOffers, list): - saveOffers = dict() - local.db[bname] = saveOffers - return saveOffers - #end define - - def AddSaveOffer(self, offer): - offerHash = offer.get("hash") - offerPseudohash = offer.get("pseudohash") - saveOffers = self.GetSaveOffers() - if offerHash not in saveOffers: - saveOffers[offerHash] = offerPseudohash - local.save() - #end define - - def GetVotedComplaints(self): - bname = "votedComplaints" - votedComplaints = local.db.get(bname) - if votedComplaints is None: - votedComplaints = dict() - local.db[bname] = votedComplaints - return votedComplaints - #end define - - def AddVotedComplaints(self, complaint): - pseudohash = complaint.get("pseudohash") - votedComplaints = self.GetVotedComplaints() - if pseudohash not in votedComplaints: - votedComplaints[pseudohash] = complaint - local.save() + save_offers = self.local.db.get(bname) + if save_offers is None or isinstance(save_offers, list): + save_offers = dict() + self.local.db[bname] = save_offers + self.offers_gc(save_offers) + return save_offers + #end define + + def add_save_offer(self, offer): + offer_hash = offer.get("hash") + offer_pseudohash = offer.get("pseudohash") + save_offers = self.GetSaveOffers() + if offer_hash not in save_offers: + save_offers[offer_hash] = [offer_pseudohash, offer.get('config', {}).get("id")] + self.local.save() + #end define + + def GetVotedComplaints(self, complaints: dict): + result = {} + validator_index = self.GetValidatorIndex() + for chash, complaint in complaints.items(): + voted_validators = complaint.get("votedValidators") + if validator_index in voted_validators: + result[chash] = complaint + return result #end define def GetDestinationAddr(self, destination): @@ -3122,6 +3099,8 @@ def GetDestinationAddr(self, destination): #end define def AddrFull2AddrB64(self, addrFull, bounceable=True): + if addrFull is None or "None" in addrFull: + return testnet = self.IsTestnet() buff = addrFull.split(':') workchain = int(buff[0]) @@ -3153,7 +3132,7 @@ def ParseAddrB64(self, addrB64): if buff: return buff #end if - + buff = addrB64.replace('-', '+') buff = buff.replace('_', '/') buff = buff.encode() @@ -3171,7 +3150,7 @@ def ParseAddrB64(self, addrB64): networkTestnet = self.IsTestnet() if testnet != networkTestnet: text = f"ParseAddrB64 warning: testnet flag do not match. Addr: {testnet}, Network: {networkTestnet}" - local.add_log(text, "warning") + self.local.add_log(text, "warning") #end if # get wc and addr @@ -3187,7 +3166,7 @@ def ParseAddrB64(self, addrB64): workchain = int.from_bytes(workchain_bytes, "big", signed=True) addr = addr_bytes.hex() - + # Set buffer data = (workchain, addr, bounceable) self.SetFunctionBuffer(fname, data) @@ -3214,7 +3193,7 @@ def ParseInputAddr(self, inputAddr): else: raise Exception(f"ParseInputAddr error: input address is not a adress: {inputAddr}") #end define - + def IsBounceableAddrB64(self, inputAddr): bounceable = None try: @@ -3225,7 +3204,7 @@ def IsBounceableAddrB64(self, inputAddr): def GetNetLoadAvg(self, statistics=None): if statistics is None: - statistics = local.db.get("statistics") + statistics = self.local.db.get("statistics") if statistics: netLoadAvg = statistics.get("netLoadAvg") else: @@ -3235,7 +3214,7 @@ def GetNetLoadAvg(self, statistics=None): def GetTpsAvg(self, statistics=None): if statistics is None: - statistics = local.db.get("statistics") + statistics = self.local.db.get("statistics") if statistics: tpsAvg = statistics.get("tpsAvg") else: @@ -3245,7 +3224,7 @@ def GetTpsAvg(self, statistics=None): def GetStatistics(self, name, statistics=None): if statistics is None: - statistics = local.db.get("statistics") + statistics = self.local.db.get("statistics") if statistics: data = statistics.get(name) else: @@ -3254,8 +3233,8 @@ def GetStatistics(self, name, statistics=None): #end define def GetSettings(self, name): - #local.load_db() - result = local.db.get(name) + # self.local.load_db() + result = self.local.db.get(name) return result #end define @@ -3263,9 +3242,88 @@ def SetSettings(self, name, data): try: data = json.loads(data) except: pass - local.db[name] = data - local.save() - #end define + self.local.db[name] = data + self.local.save() + #end define + + def migrate_to_modes(self): + usePool = self.local.db.get('usePool') + if usePool is not None: + if usePool: + self.enable_mode('nominator-pool') + self.local.db.pop('usePool') + + useController = self.local.db.get('useController') + if useController is not None: + if useController: + self.enable_mode('liquid-staking') + self.local.db.pop('useController') + self.local.save() + + def rollback_modes(self): + self.local.db['usePool'] = self.get_mode_value('nominator-pool') + self.local.db['useController'] = self.get_mode_value('liquid-staking') + self.local.db.pop('modes') + self.local.save() + + def get_modes(self): + current_modes = self.local.db.get('modes', {}) + if 'modes' not in self.local.db: + self.local.db['modes'] = current_modes + self.migrate_to_modes() + for name, mode in MODES.items(): + if name not in current_modes: + current_modes[name] = mode.default_value # assign default mode value + return current_modes + + def check_enable_mode(self, name): + if name == 'liteserver': + if self.using_validator(): + raise Exception(f'Cannot enable liteserver mode while validator mode is enabled. ' + f'Use `disable_mode validator` first.') + if name == 'validator': + if self.using_liteserver(): + raise Exception(f'Cannot enable validator mode while liteserver mode is enabled. ' + f'Use `disable_mode liteserver` first.') + + def enable_mode(self, name): + if name not in MODES: + raise Exception(f'Unknown module name: {name}. Available modes: {", ".join(MODES)}') + self.check_enable_mode(name) + current_modes = self.get_modes() + current_modes[name] = True + self.local.save() + + def disable_mode(self, name): + current_modes = self.get_modes() + if name not in current_modes: + raise Exception(f'Unknown module name: {name}. Available modes: {", ".join(MODES)}') + current_modes[name] = False + self.local.save() + + def get_mode_value(self, name): + current_modes = self.get_modes() + if name not in current_modes: + raise Exception(f'No mode named {name} found in current modes: {current_modes}') + return current_modes[name] + + def using_nominator_pool(self): + return self.get_mode_value('nominator-pool') + + def using_single_nominator(self): + return self.get_mode_value('single-nominator') + + def using_liquid_staking(self): + return self.get_mode_value('liquid-staking') + + def using_pool(self) -> bool: + return self.using_nominator_pool() or self.using_single_nominator() + + def using_validator(self): + return self.get_mode_value('validator') + + def using_liteserver(self): + return self.get_mode_value('liteserver') def Tlb2Json(self, text): # Заменить скобки @@ -3338,7 +3396,7 @@ def Tlb2Json(self, text): #end define def SignShardOverlayCert(self, adnl, pubkey): - local.add_log("start SignShardOverlayCert function", "debug") + self.local.add_log("start SignShardOverlayCert function", "debug") fileName = self.tempDir + pubkey + ".cert" cmd = "signshardoverlaycert {workchain} {shardprefix} {pubkey} {expireat} {maxsize} {outfile}" cmd = cmd.format(workchain=-1, shardprefix=-9223372036854775808, pubkey=pubkey, expireat=172800, maxsize=8192, outfile=fileName) @@ -3360,7 +3418,7 @@ def SignShardOverlayCert(self, adnl, pubkey): #end define def ImportShardOverlayCert(self): - local.add_log("start ImportShardOverlayCert function", "debug") + self.local.add_log("start ImportShardOverlayCert function", "debug") adnlAddr = self.GetAdnlAddr() pubkey = self.GetPubKey(adnlAddr) adnl = pubkey # adnl = adnlAddr @@ -3385,7 +3443,7 @@ def ImportShardOverlayCert(self): # Check certificate if cert is None: - local.add_log("ImportShardOverlayCert warning: certificate not found", "warning") + self.local.add_log("ImportShardOverlayCert warning: certificate not found", "warning") return #end if @@ -3397,7 +3455,7 @@ def ImportShardOverlayCert(self): #end define def ImportCertificate(self, pubkey, fileName): - local.add_log("start ImportCertificate function", "debug") + self.local.add_log("start ImportCertificate function", "debug") cmd = "importshardoverlaycert {workchain} {shardprefix} {pubkey} {certfile}" cmd = cmd.format(workchain=-1, shardprefix=-9223372036854775808, pubkey=pubkey, certfile=fileName) result = self.validatorConsole.Run(cmd) @@ -3413,7 +3471,7 @@ def GetValidatorsWalletsList(self): #end define def DownloadContract(self, url, branch=None): - local.add_log("start DownloadContract function", "debug") + self.local.add_log("start DownloadContract function", "debug") buff = url.split('/') gitPath = self.contractsDir + buff[-1] + '/' @@ -3444,102 +3502,41 @@ def DownloadContract(self, url, branch=None): #end if #end define - def CreatePool(self, poolName, validatorRewardSharePercent, maxNominatorsCount, minValidatorStake, minNominatorStake): - local.add_log("start CreatePool function", "debug") - validatorRewardShare = int(validatorRewardSharePercent * 100) - contractPath = self.contractsDir + "nominator-pool/" - if not os.path.isdir(contractPath): - self.DownloadContract("https://github.com/ton-blockchain/nominator-pool") - #end if - - filePath = self.poolsDir + poolName - if os.path.isfile(filePath + ".addr"): - local.add_log("CreatePool warning: Pool already exists: " + filePath, "warning") - return - #end if - - fiftScript = self.contractsDir + "nominator-pool/func/new-pool.fif" - wallet = self.GetValidatorWallet() - args = [fiftScript, wallet.addrB64, validatorRewardShare, maxNominatorsCount, minValidatorStake, minNominatorStake, filePath] - result = self.fift.Run(args) - if "Saved pool" not in result: - raise Exception("CreatePool error: " + result) - #end if - - pools = self.GetPools() - newPool = self.GetLocalPool(poolName) - for pool in pools: - if pool.name != newPool.name and pool.addrB64 == newPool.addrB64: - newPool.Delete() - raise Exception("CreatePool error: Pool with the same parameters already exists.") - #end for - #end define - - def ActivatePool(self, pool, ex=True): - local.add_log("start ActivatePool function", "debug") - for i in range(10): - time.sleep(3) - account = self.GetAccount(pool.addrB64) - if account.balance > 0: - self.SendFile(pool.bocFilePath, pool, timeout=False) - return - if ex: - raise Exception("ActivatePool error: time out") - #end define - - def DepositToPool(self, walletName, poolAddr, amount): - wallet = self.GetLocalWallet(walletName) - bocPath = local.buffer.my_temp_dir + wallet.name + "validator-deposit-query.boc" - fiftScript = self.contractsDir + "nominator-pool/func/validator-deposit.fif" - args = [fiftScript, bocPath] - result = self.fift.Run(args) - resultFilePath = self.SignBocWithWallet(wallet, bocPath, poolAddr, amount) - self.SendFile(resultFilePath, wallet) - #end define - - def WithdrawFromPool(self, poolAddr, amount): - poolData = self.GetPoolData(poolAddr) - if poolData["state"] == 0: - self.WithdrawFromPoolProcess(poolAddr, amount) - else: - self.PendWithdrawFromPool(poolAddr, amount) - #end define - def WithdrawFromPoolProcess(self, poolAddr, amount): - local.add_log("start WithdrawFromPoolProcess function", "debug") + self.local.add_log("start WithdrawFromPoolProcess function", "debug") wallet = self.GetValidatorWallet() - bocPath = local.buffer.my_temp_dir + wallet.name + "validator-withdraw-query.boc" + bocPath = self.local.buffer.my_temp_dir + wallet.name + "validator-withdraw-query.boc" fiftScript = self.contractsDir + "nominator-pool/func/validator-withdraw.fif" args = [fiftScript, amount, bocPath] result = self.fift.Run(args) resultFilePath = self.SignBocWithWallet(wallet, bocPath, poolAddr, 1.35) self.SendFile(resultFilePath, wallet) #end define - + def PendWithdrawFromPool(self, poolAddr, amount): - local.add_log("start PendWithdrawFromPool function", "debug") + self.local.add_log("start PendWithdrawFromPool function", "debug") pendingWithdraws = self.GetPendingWithdraws() pendingWithdraws[poolAddr] = amount - local.save() + self.local.save() #end define - + def HandlePendingWithdraw(self, pendingWithdraws, poolAddr): amount = pendingWithdraws.get(poolAddr) self.WithdrawFromPoolProcess(poolAddr, amount) pendingWithdraws.pop(poolAddr) #end define - + def GetPendingWithdraws(self): bname = "pendingWithdraws" - pendingWithdraws = local.db.get(bname) + pendingWithdraws = self.local.db.get(bname) if pendingWithdraws is None: pendingWithdraws = dict() - local.db[bname] = pendingWithdraws + self.local.db[bname] = pendingWithdraws return pendingWithdraws #end define def SignElectionRequestWithPoolWithValidator(self, pool, startWorkTime, adnlAddr, validatorPubkey_b64, validatorSignature, maxFactor, stake): - local.add_log("start SignElectionRequestWithPoolWithValidator function", "debug") + self.local.add_log("start SignElectionRequestWithPoolWithValidator function", "debug") fileName = self.tempDir + str(startWorkTime) + "_validator-query.boc" fiftScript = self.contractsDir + "nominator-pool/func/validator-elect-signed.fif" args = [fiftScript, pool.addrB64, startWorkTime, maxFactor, adnlAddr, validatorPubkey_b64, validatorSignature, fileName, stake] @@ -3550,7 +3547,7 @@ def SignElectionRequestWithPoolWithValidator(self, pool, startWorkTime, adnlAddr #end define def PoolProcessRecoverStake(self): - local.add_log("start PoolProcessRecoverStake function", "debug") + self.local.add_log("start PoolProcessRecoverStake function", "debug") resultFilePath = self.tempDir + "recover-query.boc" fiftScript = self.contractsDir + "nominator-pool/func/recover-stake.fif" args = [fiftScript, resultFilePath] @@ -3559,8 +3556,38 @@ def PoolProcessRecoverStake(self): return resultFilePath #end define + def GetControllerData(self, addrB64): + self.local.add_log("start GetControllerData function", "debug") + account = self.GetAccount(addrB64) + if account.status != "active": + return + cmd = "runmethodfull {addrB64} all_data".format(addrB64=addrB64) + result = self.liteClient.Run(cmd) + data = self.Result2List(result) + controllerData = dict() + wallet_data = dict() + wallet_data["seqno"] = data[0][0] + wallet_data["subwallet_id"] = data[0][1] + wallet_data["controller_pubkey"] = data[0][2] + wallet_data["last_used"] = data[0][3] + static_data = dict() + static_data["nominator_address"] = data[1][0] + static_data["controller_reward_share"] = data[1][1] + static_data["controller_cover_ability"] = data[1][2] + balances = dict() + balances["nominator_total_balance"] = data[2][0] + balances["nominator_elector_balance"] = data[2][1] + balances["nominator_withdrawal_request"] = data[2][2] + balances["total_stake_on_elector"] = data[2][3] + controllerData["wallet_data"] = wallet_data + controllerData["static_data"] = static_data + controllerData["balances"] = balances + controllerData["last_sent_stake_time"] = data[3] + return controllerData + #end define + def GetLocalPool(self, poolName): - local.add_log("start GetLocalPool function", "debug") + self.local.add_log("start GetLocalPool function", "debug") if poolName is None: return None filePath = self.poolsDir + poolName @@ -3576,7 +3603,7 @@ def GetLocalPool(self, poolName): #end define def GetPoolsNameList(self): - local.add_log("start GetPoolsNameList function", "debug") + self.local.add_log("start GetPoolsNameList function", "debug") poolsNameList = list() for fileName in os.listdir(self.poolsDir): if fileName.endswith(".addr"): @@ -3587,7 +3614,7 @@ def GetPoolsNameList(self): #end define def GetPools(self): - local.add_log("start GetPools function", "debug") + self.local.add_log("start GetPools function", "debug") pools = list() poolsNameList = self.GetPoolsNameList() for poolName in poolsNameList: @@ -3596,39 +3623,48 @@ def GetPools(self): return pools #end define - def GetPool(self, mode): + def get_pool(self): pools = self.GetPools() for pool in pools: - if mode == "stake" and self.IsPoolReadyToStake(pool.addrB64): - return pool - if mode == "vote" and self.IsPoolReadyToVote(pool.addrB64): + if self.is_pool_ready_to_stake(pool): return pool raise Exception("Validator pool not found or not ready") #end define - def GetPoolLastSentStakeTime(self, addrB64): - poolData = self.GetPoolData(addrB64) - return poolData["stakeAt"] + def get_pool_last_sent_stake_time(self, addrB64): + pool_data = self.GetPoolData(addrB64) + return pool_data["stakeAt"] #end define - def IsPoolReadyToStake(self, addrB64): + def is_pool_ready_to_stake(self, pool: Pool): + addr = pool.addrB64 + account = self.GetAccount(addr) + is_single_nominator = self.is_account_single_nominator(account) + if self.using_single_nominator() and not is_single_nominator: + return False + try: # check that account balance is enough for stake + stake = self.GetStake(account) + if not stake: + raise Exception(f'Stake is {stake}') + except Exception as e: + self.local.add_log(f"Failed to get stake for pool {addr}: {e}", "debug") + return False now = get_timestamp() config15 = self.GetConfig15() - lastSentStakeTime = self.GetPoolLastSentStakeTime(addrB64) - stakeFreezeDelay = config15["validatorsElectedFor"] + config15["stakeHeldFor"] - result = lastSentStakeTime + stakeFreezeDelay < now - print(f"{addrB64}: {result}. {lastSentStakeTime}, {stakeFreezeDelay}, {now}") + last_sent_stake_time = self.get_pool_last_sent_stake_time(addr) + stake_freeze_delay = config15["validatorsElectedFor"] + config15["stakeHeldFor"] + result = last_sent_stake_time + stake_freeze_delay < now + print(f"{addr}: {result}. {last_sent_stake_time}, {stake_freeze_delay}, {now}") return result #end define - def IsPoolReadyToVote(self, addrB64): - vwl = self.GetValidatorsWalletsList() - result = addrB64 in vwl - return result + def is_account_single_nominator(self, account: Account): + account_version = self.GetVersionFromCodeHash(account.codeHash) + return account_version is not None and 'spool' in account_version #end define def GetPoolData(self, addrB64): - local.add_log("start GetPoolData function", "debug") + self.local.add_log("start GetPoolData function", "debug") cmd = f"runmethodfull {addrB64} get_pool_data" result = self.liteClient.Run(cmd) data = self.Result2List(result) @@ -3657,37 +3693,339 @@ def GetPoolData(self, addrB64): return poolData #end define + def GetLiquidPoolAddr(self): + liquid_pool_addr = self.local.db.get("liquid_pool_addr") + if liquid_pool_addr is None: + raise Exception("GetLiquidPoolAddr error: liquid_pool_addr not set") + return liquid_pool_addr + #end define + + def GetControllerAddress(self, controller_id): + wallet = self.GetValidatorWallet() + addr_hash = HexAddr2Dec(wallet.addr) + liquid_pool_addr = self.GetLiquidPoolAddr() + cmd = f"runmethodfull {liquid_pool_addr} get_controller_address_legacy {controller_id} {wallet.workchain} {addr_hash}" + result = self.liteClient.Run(cmd) + buff = self.Result2List(result) + wc = buff[0] + addr_hash = Dec2HexAddr(buff[1]) + addrFull = f"{wc}:{addr_hash}" + controllerAddr = self.AddrFull2AddrB64(addrFull) + return controllerAddr + #end define + + def CheckController(self, controllerAddr): + self.local.add_log("start CheckController function", "debug") + controllerData = self.GetControllerData(controllerAddr) + using_controllers = self.local.db.get("using_controllers", list()) + if controllerData is None: + raise Exception(f"CheckController error: controller not initialized. Use new_controllers") + if controllerData["approved"] != -1: + raise Exception(f"CheckController error: controller not approved: {controllerAddr}") + if controllerAddr not in using_controllers: + raise Exception("CheckController error: controller is not up to date. Use new_controllers") + #end define + + def GetControllers(self): + self.local.add_log("start GetControllers function", "debug") + controller0 = self.GetControllerAddress(controller_id=0) + controller1 = self.GetControllerAddress(controller_id=1) + controllers = [controller0, controller1] + return controllers + #end define + + def GetController(self, mode): + controllers = self.GetControllers() + for controllerAddr in controllers: + if mode == "stake" and self.IsControllerReadyToStake(controllerAddr): + return controllerAddr + if mode == "vote" and self.IsControllerReadyToVote(controllerAddr): + return controllerAddr + raise Exception("Validator controller not found or not ready") + #end define + + def GetControllerRequiredBalanceForLoan(self, controllerAddr, credit, interest): + cmd = f"runmethodfull {controllerAddr} required_balance_for_loan {credit} {interest}" + result = self.liteClient.Run(cmd) + data = self.Result2List(result) + if data is None: + return + min_amount = data[0] + validator_amount = data[1] + return min_amount, validator_amount + #end define + + def IsControllerReadyToStake(self, addrB64): + stop_controllers_list = self.local.db.get("stop_controllers_list") + if stop_controllers_list is not None and addrB64 in stop_controllers_list: + return False + now = get_timestamp() + config15 = self.GetConfig15() + controllerData = self.GetControllerData(addrB64) + if controllerData is None: + raise Exception(f"IsControllerReadyToStake error: controller not initialized. Use new_controllers") + lastSentStakeTime = controllerData["stake_at"] + stakeFreezeDelay = config15["validatorsElectedFor"] + config15["stakeHeldFor"] + result = lastSentStakeTime + stakeFreezeDelay < now + print(f"{addrB64}: {result}. {lastSentStakeTime}, {stakeFreezeDelay}, {now}") + return result + #end define + + def IsControllerReadyToVote(self, addrB64): + vwl = self.GetValidatorsWalletsList() + result = addrB64 in vwl + return result + #end define + + def GetControllerData(self, controllerAddr): + cmd = f"runmethodfull {controllerAddr} get_validator_controller_data" + result = self.liteClient.Run(cmd) + data = self.Result2List(result) + if data is None: + return + result_vars = ["state", "halted", "approved", "stake_amount_sent", "stake_at", "saved_validator_set_hash", "validator_set_changes_count", "validator_set_change_time", "stake_held_for", "borrowed_amount", "borrowing_time"] + controllerData = dict() + for name in result_vars: + controllerData[name] = data.pop(0) + return controllerData + #end define + + def CreateLoanRequest(self, controllerAddr): + self.local.add_log("start CreateLoanRequest function", "debug") + min_loan = self.local.db.get("min_loan", 41000) + max_loan = self.local.db.get("max_loan", 43000) + max_interest_percent = self.local.db.get("max_interest_percent", 1.5) + max_interest = int(max_interest_percent/100*16777216) + + # Проверить наличие действующего кредита + controllerData = self.GetControllerData(controllerAddr) + if controllerData["borrowed_amount"] > 0: + self.local.add_log("CreateLoanRequest warning: past loan found", "warning") + return + #end define + + # Проверить наличие средств у ликвидного пула + if self.CalculateLoanAmount(min_loan, max_loan, max_interest) == '-0x1': + raise Exception("CreateLoanRequest error: The liquid pool cannot issue the required amount of credit") + #end if + + # Проверить хватает ли ставки валидатора + min_amount, validator_amount = self.GetControllerRequiredBalanceForLoan(controllerAddr, max_loan, max_interest) + if min_amount > validator_amount: + raise Exception("CreateLoanRequest error: Validator stake is too low. Use deposit_to_controller") + #end if + + wallet = self.GetValidatorWallet() + fiftScript = self.contractsDir + "jetton_pool/fift-scripts/generate-loan-request.fif" + resultFilePath = self.tempDir + self.nodeName + wallet.name + "_loan_request.boc" + args = [fiftScript, min_loan, max_loan, max_interest, resultFilePath] + result = self.fift.Run(args) + resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, controllerAddr, 1.01) + self.SendFile(resultFilePath, wallet) + self.WaitLoan(controllerAddr) + #end define + + def CalculateLoanAmount(self, min_loan, max_loan, max_interest): + data = dict() + data["address"] = self.GetLiquidPoolAddr() + data["method"] = "calculate_loan_amount" + data["stack"] = [ + ["num", min_loan*10**9], + ["num", max_loan*10**9], + ["num", max_interest], + ] + print(f"CalculateLoanAmount data: {data}") + + url = "http://127.0.0.1:8801/runGetMethod" + res = requests.post(url, json=data) + res_data = res.json() + if res_data.get("ok") is False: + error = res_data.get("error") + raise Exception(error) + result = res_data.get("result").get("stack").pop().pop() + return result + #end define + + def WaitLoan(self, controllerAddr): + self.local.add_log("start WaitLoan function", "debug") + for i in range(10): + time.sleep(3) + controllerData = self.GetControllerData(controllerAddr) + if controllerData["borrowed_amount"] != 0: + return + raise Exception("WaitLoan error: time out") + #end define + + def ReturnUnusedLoan(self, controllerAddr): + self.local.add_log("start ReturnUnusedLoan function", "debug") + wallet = self.GetValidatorWallet() + fileName = self.contractsDir + "jetton_pool/fift-scripts/return_unused_loan.boc" + resultFilePath = self.SignBocWithWallet(wallet, fileName, controllerAddr, 1.05) + self.SendFile(resultFilePath, wallet) + #end define + + def WithdrawFromController(self, controllerAddr, amount=None): + controllerData = self.GetControllerData(controllerAddr) + if controllerData["state"] == 0: + self.WithdrawFromControllerProcess(controllerAddr, amount) + else: + self.PendWithdrawFromController(controllerAddr, amount) + #end define + + def WithdrawFromControllerProcess(self, controllerAddr, amount): + if amount is None: + account = self.GetAccount(controllerAddr) + amount = account.balance-10.1 + if int(amount) < 3: + return + #end if + + self.local.add_log("start WithdrawFromControllerProcess function", "debug") + wallet = self.GetValidatorWallet() + fiftScript = self.contractsDir + "jetton_pool/fift-scripts/withdraw-controller.fif" + resultFilePath = self.tempDir + self.nodeName + wallet.name + "_withdraw_request.boc" + args = [fiftScript, amount, resultFilePath] + result = self.fift.Run(args) + resultFilePath = self.SignBocWithWallet(wallet, resultFilePath, controllerAddr, 1.06) + self.SendFile(resultFilePath, wallet) + #end define + + def PendWithdrawFromController(self, controllerAddr, amount): + self.local.add_log("start PendWithdrawFromController function", "debug") + controllerPendingWithdraws = self.GetControllerPendingWithdraws() + controllerPendingWithdraws[controllerAddr] = amount + self.local.save() + #end define + + def HandleControllerPendingWithdraw(self, controllerPendingWithdraws, controllerAddr): + amount = controllerPendingWithdraws.get(controllerAddr) + self.WithdrawFromControllerProcess(controllerAddr, amount) + controllerPendingWithdraws.pop(controllerAddr) + #end define + + def GetControllerPendingWithdraws(self): + bname = "controllerPendingWithdraws" + controllerPendingWithdraws = self.local.db.get(bname) + if controllerPendingWithdraws is None: + controllerPendingWithdraws = dict() + self.local.db[bname] = controllerPendingWithdraws + return controllerPendingWithdraws + #end define + + def SignElectionRequestWithController(self, controllerAddr, startWorkTime, adnlAddr, validatorPubkey_b64, validatorSignature, maxFactor, stake): + self.local.add_log("start SignElectionRequestWithController function", "debug") + fileName = self.tempDir + str(startWorkTime) + "_validator-query.boc" + fiftScript = self.contractsDir + "jetton_pool/fift-scripts/controller-elect-signed.fif" + args = [fiftScript, controllerAddr, startWorkTime, maxFactor, adnlAddr, validatorPubkey_b64, validatorSignature, fileName, stake] + self.local.add_log(f"SignElectionRequestWithController args: {args}", "debug") + result = self.fift.Run(args) + self.local.add_log(f"SignElectionRequestWithController result: {result}", "debug") + pubkey = parse(result, "validator public key ", '\n') + fileName = parse(result, "Saved to file ", '\n') + return pubkey, fileName + #end define + + def ControllersUpdateValidatorSet(self): + self.local.add_log("start ControllersUpdateValidatorSet function", "debug") + using_controllers = self.local.db.get("using_controllers") + user_controllers = self.local.db.get("user_controllers", list()) + old_controllers = self.local.db.get("old_controllers", list()) + for controller in using_controllers + user_controllers + old_controllers: + self.ControllerUpdateValidatorSet(controller) + #end define + + def ControllerUpdateValidatorSet(self, controllerAddr): + self.local.add_log("start ControllerUpdateValidatorSet function", "debug") + wallet = self.GetValidatorWallet() + controllers = self.GetControllers() + controllerData = self.GetControllerData(controllerAddr) + if controllerData is None: + return + #end if + + timeNow = int(time.time()) + config34 = self.GetConfig34() + fullElectorAddr = self.GetFullElectorAddr() + returnedStake = self.GetReturnedStake(fullElectorAddr, controllerAddr) + controllerPendingWithdraws = self.GetControllerPendingWithdraws() + if (controllerData["state"] == 3 and + controllerData["validator_set_changes_count"] < 2 and + controllerData["validator_set_change_time"] < config34["startWorkTime"]): + self.ControllerUpdateValidatorSetProcess(controllerAddr, wallet) + controllerData = self.GetControllerData(controllerAddr) + if (returnedStake > 0 and + controllerData["state"] == 3 and + controllerData["validator_set_changes_count"] >= 2 and + timeNow - controllerData["validator_set_change_time"] > controllerData["stake_held_for"] + 60): + self.ControllerRecoverStake(controllerAddr) + controllerData = self.GetControllerData(controllerAddr) + if (controllerData["state"] == 0 and + controllerData["borrowed_amount"] > 0 and + config34["startWorkTime"] > controllerData["borrowing_time"]): + self.ReturnUnusedLoan(controllerAddr) + if (controllerData["state"] == 0 and controllerAddr in controllerPendingWithdraws): + self.HandleControllerPendingWithdraw(controllerPendingWithdraws, controllerAddr) + if controllerAddr not in controllers: + self.WithdrawFromController(controllerAddr) + #end define + + def ControllerUpdateValidatorSetProcess(self, controllerAddr, wallet): + self.local.add_log("start ControllerUpdateValidatorSetProcess function", "debug") + fileName = self.contractsDir + "jetton_pool/fift-scripts/update_validator_hash.boc" + resultFilePath = self.SignBocWithWallet(wallet, fileName, controllerAddr, 1.07) + self.SendFile(resultFilePath, wallet) + self.local.add_log("ControllerUpdateValidatorSetProcess completed") + #end define + + def ControllerRecoverStake(self, controllerAddr): + wallet = self.GetValidatorWallet() + self.local.add_log("start ControllerRecoverStake function", "debug") + fileName = self.contractsDir + "jetton_pool/fift-scripts/recover_stake.boc" + resultFilePath = self.SignBocWithWallet(wallet, fileName, controllerAddr, 1.04) + self.SendFile(resultFilePath, wallet) + self.local.add_log("ControllerRecoverStake completed") + #end define + def get_custom_overlays(self): - if 'custom_overlays' not in local.db: - local.db['custom_overlays'] = {} - return local.db['custom_overlays'] + if 'custom_overlays' not in self.local.db: + self.local.db['custom_overlays'] = {} + return self.local.db['custom_overlays'] def set_custom_overlay(self, name: str, config: dict): overlays = self.get_custom_overlays() overlays[name] = config - local.save() + self.local.save() def delete_custom_overlay(self, name: str): - del local.db['custom_overlays'][name] - local.save() + del self.local.db['custom_overlays'][name] + self.local.save() + + def set_collator_config(self, location: str): + self.local.db['collator_config'] = location + self.local.save() + + def get_collator_config_location(self): + default = 'https://raw.githubusercontent.com/ton-blockchain/ton-blockchain.github.io/main/default_collator_options.json' + location = self.local.db.get('collator_config', default) + if location is None: + location = default + return location def GetNetworkName(self): - mainnetValidatorsElectedFor = 65536 - mainnetZerostateRootHash = "x55B13F6D0E1D0C34C9C2160F6F918E92D82BF9DDCF8DE2E4C94A3FDF39D15446" - config12 = self.GetConfig(12) - config15 = self.GetConfig15() - validatorsElectedFor = config15["validatorsElectedFor"] - zerostateRootHash = config12["workchains"]["root"]["node"]["value"]["zerostate_root_hash"] - if (zerostateRootHash == mainnetZerostateRootHash and - validatorsElectedFor == mainnetValidatorsElectedFor): + data = self.local.read_db(self.liteClient.configPath) + mainnet_zero_state_root_hash = "F6OpKZKqvqeFp6CQmFomXNMfMj2EnaUSOXN+Mh+wVWk=" + testnet_zero_state_root_hash = "gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg=" + if data.validator.zero_state.root_hash == mainnet_zero_state_root_hash: return "mainnet" - else: + elif data.validator.zero_state.root_hash == testnet_zero_state_root_hash: return "testnet" + else: + return "unknown" #end define - + def GetFunctionBuffer(self, name, timeout=10): timestamp = get_timestamp() - buff = local.buffer.get(name) + buff = self.local.buffer.get(name) if buff is None: return buffTime = buff.get("time") @@ -3697,12 +4035,12 @@ def GetFunctionBuffer(self, name, timeout=10): data = buff.get("data") return data #end define - + def SetFunctionBuffer(self, name, data): buff = dict() buff["time"] = get_timestamp() buff["data"] = data - local.buffer[name] = buff + self.local.buffer[name] = buff #end define def IsTestnet(self): @@ -3745,586 +4083,6 @@ def IsHash(self, inputHash): #end define #end class -def ng2g(ng): - if ng is None: - return - return int(ng)/10**9 -#end define - -def Init(): - # Event reaction - if ("-e" in sys.argv): - x = sys.argv.index("-e") - eventName = sys.argv[x+1] - Event(eventName) - #end if - - local.run() - - # statistics - local.buffer.blocksData = dict() - local.buffer.transData = dict() - local.buffer.network = [None]*15*6 - local.buffer.diskio = [None]*15*6 - - # scan blocks - local.buffer.masterBlocksList = list() - local.buffer.prevShardsBlock = dict() - local.buffer.blocksNum = 0 - local.buffer.transNum = 0 -#end define - -def Event(eventName): - if eventName == "enableVC": - EnableVcEvent() - elif eventName == "validator down": - ValidatorDownEvent() - local.exit() -#end define - -def EnableVcEvent(): - local.add_log("start EnableVcEvent function", "debug") - # Создать новый кошелек для валидатора - ton = MyTonCore() - wallet = ton.CreateWallet("validator_wallet_001", -1) - local.db["validatorWalletName"] = wallet.name - - # Создать новый ADNL адрес для валидатора - adnlAddr = ton.CreateNewKey() - ton.AddAdnlAddrToValidator(adnlAddr) - local.db["adnlAddr"] = adnlAddr - - # Сохранить - local.save() -#end define - -def ValidatorDownEvent(): - local.add_log("start ValidatorDownEvent function", "debug") - local.add_log("Validator is down", "error") -#end define - -def Elections(ton): - usePool = local.db.get("usePool") - if usePool == True: - ton.PoolsUpdateValidatorSet() - ton.RecoverStake() - ton.ElectionEntry() - else: - ton.RecoverStake() - ton.ElectionEntry() -#end define - -def Statistics(): - ReadNetworkData() - SaveNetworkStatistics() - #ReadTransData(scanner) - SaveTransStatistics() - ReadDiskData() - SaveDiskStatistics() -#end define - -def ReadDiskData(): - timestamp = get_timestamp() - disks = GetDisksList() - buff = psutil.disk_io_counters(perdisk=True) - data = dict() - for name in disks: - data[name] = dict() - data[name]["timestamp"] = timestamp - data[name]["busyTime"] = buff[name].busy_time - data[name]["readBytes"] = buff[name].read_bytes - data[name]["writeBytes"] = buff[name].write_bytes - data[name]["readCount"] = buff[name].read_count - data[name]["writeCount"] = buff[name].write_count - #end for - - local.buffer.diskio.pop(0) - local.buffer.diskio.append(data) -#end define - -def SaveDiskStatistics(): - data = local.buffer.diskio - data = data[::-1] - zerodata = data[0] - buff1 = data[1*6-1] - buff5 = data[5*6-1] - buff15 = data[15*6-1] - if buff5 is None: - buff5 = buff1 - if buff15 is None: - buff15 = buff5 - #end if - - disksLoadAvg = dict() - disksLoadPercentAvg = dict() - iopsAvg = dict() - disks = GetDisksList() - for name in disks: - if zerodata[name]["busyTime"] == 0: - continue - diskLoad1, diskLoadPercent1, iops1 = CalculateDiskStatistics(zerodata, buff1, name) - diskLoad5, diskLoadPercent5, iops5 = CalculateDiskStatistics(zerodata, buff5, name) - diskLoad15, diskLoadPercent15, iops15 = CalculateDiskStatistics(zerodata, buff15, name) - disksLoadAvg[name] = [diskLoad1, diskLoad5, diskLoad15] - disksLoadPercentAvg[name] = [diskLoadPercent1, diskLoadPercent5, diskLoadPercent15] - iopsAvg[name] = [iops1, iops5, iops15] - #end fore - - # save statistics - statistics = local.db.get("statistics", dict()) - statistics["disksLoadAvg"] = disksLoadAvg - statistics["disksLoadPercentAvg"] = disksLoadPercentAvg - statistics["iopsAvg"] = iopsAvg - local.db["statistics"] = statistics -#end define - -def CalculateDiskStatistics(zerodata, data, name): - if data is None: - return None, None, None - data = data[name] - zerodata = zerodata[name] - timeDiff = zerodata["timestamp"] - data["timestamp"] - busyTimeDiff = zerodata["busyTime"] - data["busyTime"] - diskReadDiff = zerodata["readBytes"] - data["readBytes"] - diskWriteDiff = zerodata["writeBytes"] - data["writeBytes"] - diskReadCountDiff = zerodata["readCount"] - data["readCount"] - diskWriteCountDiff = zerodata["writeCount"] - data["writeCount"] - diskLoadPercent = busyTimeDiff /1000 /timeDiff *100 # /1000 - to second, *100 - to percent - diskLoadPercent = round(diskLoadPercent, 2) - diskRead = diskReadDiff /timeDiff - diskWrite = diskWriteDiff /timeDiff - diskReadCount = diskReadCountDiff /timeDiff - diskWriteCount = diskWriteCountDiff /timeDiff - diskLoad = b2mb(diskRead + diskWrite) - iops = round(diskReadCount + diskWriteCount, 2) - return diskLoad, diskLoadPercent, iops -#end define - -def GetDisksList(): - data = list() - buff = os.listdir("/sys/block/") - for item in buff: - if "loop" in item: - continue - data.append(item) - #end for - data.sort() - return data -#end define - -def ReadNetworkData(): - timestamp = get_timestamp() - interfaceName = get_internet_interface_name() - buff = psutil.net_io_counters(pernic=True) - buff = buff[interfaceName] - data = dict() - data["timestamp"] = timestamp - data["bytesRecv"] = buff.bytes_recv - data["bytesSent"] = buff.bytes_sent - data["packetsSent"] = buff.packets_sent - data["packetsRecv"] = buff.packets_recv - - local.buffer.network.pop(0) - local.buffer.network.append(data) -#end define - -def SaveNetworkStatistics(): - data = local.buffer.network - data = data[::-1] - zerodata = data[0] - buff1 = data[1*6-1] - buff5 = data[5*6-1] - buff15 = data[15*6-1] - if buff5 is None: - buff5 = buff1 - if buff15 is None: - buff15 = buff5 - #end if - - netLoadAvg = dict() - ppsAvg = dict() - networkLoadAvg1, ppsAvg1 = CalculateNetworkStatistics(zerodata, buff1) - networkLoadAvg5, ppsAvg5 = CalculateNetworkStatistics(zerodata, buff5) - networkLoadAvg15, ppsAvg15 = CalculateNetworkStatistics(zerodata, buff15) - netLoadAvg = [networkLoadAvg1, networkLoadAvg5, networkLoadAvg15] - ppsAvg = [ppsAvg1, ppsAvg5, ppsAvg15] - - # save statistics - statistics = local.db.get("statistics", dict()) - statistics["netLoadAvg"] = netLoadAvg - statistics["ppsAvg"] = ppsAvg - local.db["statistics"] = statistics -#end define - -def CalculateNetworkStatistics(zerodata, data): - if data is None: - return None, None - timeDiff = zerodata["timestamp"] - data["timestamp"] - bytesRecvDiff = zerodata["bytesRecv"] - data["bytesRecv"] - bytesSentDiff = zerodata["bytesSent"] - data["bytesSent"] - packetsRecvDiff = zerodata["packetsRecv"] - data["packetsRecv"] - packetsSentDiff = zerodata["packetsSent"] - data["packetsSent"] - bitesRecvAvg = bytesRecvDiff /timeDiff *8 - bitesSentAvg = bytesSentDiff /timeDiff *8 - packetsRecvAvg = packetsRecvDiff /timeDiff - packetsSentAvg = packetsSentDiff /timeDiff - netLoadAvg = b2mb(bitesRecvAvg + bitesSentAvg) - ppsAvg = round(packetsRecvAvg + packetsSentAvg, 2) - return netLoadAvg, ppsAvg -#end define - -def ReadTransData(scanner): - transData = local.buffer.transData - SetToTimeData(transData, scanner.transNum) - ShortTimeData(transData) -#end define - -def SetToTimeData(timeDataList, data): - timenow = int(time.time()) - timeDataList[timenow] = data -#end define - -def ShortTimeData(data, max=120, diff=20): - if len(data) < max: - return - buff = data.copy() - data.clear() - keys = sorted(buff.keys(), reverse=True) - for item in keys[:max-diff]: - data[item] = buff[item] -#end define - -def SaveTransStatistics(): - tps1 = GetTps(60) - tps5 = GetTps(60*5) - tps15 = GetTps(60*15) - - # save statistics - statistics = local.db.get("statistics", dict()) - statistics["tpsAvg"] = [tps1, tps5, tps15] - local.db["statistics"] = statistics -#end define - -def GetDataPerSecond(data, timediff): - if len(data) == 0: - return - timenow = sorted(data.keys())[-1] - now = data.get(timenow) - prev = GetItemFromTimeData(data, timenow-timediff) - if prev is None: - return - diff = now - prev - result = diff / timediff - result = round(result, 2) - return result -#end define - -def GetItemFromTimeData(data, timeneed): - if timeneed in data: - result = data.get(timeneed) - else: - result = data[min(data.keys(), key=lambda k: abs(k-timeneed))] - return result -#end define - - -def GetTps(timediff): - data = local.buffer.transData - tps = GetDataPerSecond(data, timediff) - return tps -#end define - -def GetBps(timediff): - data = local.buffer.blocksData - bps = GetDataPerSecond(data, timediff) - return bps -#end define - -def GetBlockTimeAvg(timediff): - bps = GetBps(timediff) - if bps is None or bps == 0: - return - result = 1/bps - result = round(result, 2) - return result -#end define - -def Offers(ton): - saveOffers = ton.GetSaveOffers() - offers = ton.GetOffers() - for offer in offers: - offerHash = offer.get("hash") - offerPseudohash = offer.get("pseudohash") - saveOfferPseudohash = saveOffers.get(offerHash) - if offerPseudohash == saveOfferPseudohash: - ton.VoteOffer(offerHash) -#end define - -def Domains(ton): - pass -#end define - -def GetUname(): - data = os.uname() - result = dict(zip('sysname nodename release version machine'.split(), data)) - result.pop("nodename") - return result -#end define - -def GetMemoryInfo(): - result = dict() - data = psutil.virtual_memory() - result["total"] = round(data.total / 10**9, 2) - result["usage"] = round(data.used / 10**9, 2) - result["usagePercent"] = data.percent - return result -#end define - -def GetSwapInfo(): - result = dict() - data = psutil.swap_memory() - result["total"] = round(data.total / 10**9, 2) - result["usage"] = round(data.used / 10**9, 2) - result["usagePercent"] = data.percent - return result -#end define - - -def parse_db_stats(path: str): - with open(path) as f: - lines = f.readlines() - result = {} - for line in lines: - s = line.strip().split(maxsplit=1) - items = re.findall(r"(\S+)\s:\s(\S+)", s[1]) - if len(items) == 1: - item = items[0] - if float(item[1]) > 0: - result[s[0]] = float(item[1]) - else: - if any(float(v) > 0 for k, v in items): - result[s[0]] = {} - result[s[0]] = {k: float(v) for k, v in items} - return result -# end define - - -def get_db_stats(): - result = { - 'rocksdb': { - 'ok': True, - 'message': '', - 'data': {} - }, - 'celldb': { - 'ok': True, - 'message': '', - 'data': {} - }, - } - rocksdb_stats_path = '/var/ton-work/db/db_stats.txt' - celldb_stats_path = '/var/ton-work/db/celldb/db_stats.txt' - if os.path.exists(rocksdb_stats_path): - try: - result['rocksdb']['data'] = parse_db_stats(rocksdb_stats_path) - except Exception as e: - result['rocksdb']['ok'] = False - result['rocksdb']['message'] = f'failed to fetch db stats: {e}' - else: - result['rocksdb']['ok'] = False - result['rocksdb']['message'] = 'db stats file is not exists' - # end if - - if os.path.exists(celldb_stats_path): - try: - result['celldb']['data'] = parse_db_stats(celldb_stats_path) - except Exception as e: - result['celldb']['ok'] = False - result['celldb']['message'] = f'failed to fetch db stats: {e}' - else: - result['celldb']['ok'] = False - result['celldb']['message'] = 'db stats file is not exists' - # end if - - return result -# end define - - -def GetValidatorProcessInfo(): - pid = get_service_pid("validator") - if pid == None or pid == 0: - return - p = psutil.Process(pid) - mem = p.memory_info() - result = dict() - result["cpuPercent"] = p.cpu_percent() - memory = dict() - memory["rss"] = mem.rss - memory["vms"] = mem.vms - memory["shared"] = mem.shared - memory["text"] = mem.text - memory["lib"] = mem.lib - memory["data"] = mem.data - memory["dirty"] = mem.dirty - result["memory"] = memory - #io = p.io_counters() # Permission denied: '/proc/{pid}/io' - return result -#end define - -def Telemetry(ton): - sendTelemetry = local.db.get("sendTelemetry") - if sendTelemetry is not True: - return - #end if - - # Get validator status - data = dict() - data["adnlAddr"] = ton.GetAdnlAddr() - data["validatorStatus"] = ton.GetValidatorStatus() - data["cpuNumber"] = psutil.cpu_count() - data["cpuLoad"] = get_load_avg() - data["netLoad"] = ton.GetStatistics("netLoadAvg") - data["tps"] = ton.GetStatistics("tpsAvg") - data["disksLoad"] = ton.GetStatistics("disksLoadAvg") - data["disksLoadPercent"] = ton.GetStatistics("disksLoadPercentAvg") - data["iops"] = ton.GetStatistics("iopsAvg") - data["pps"] = ton.GetStatistics("ppsAvg") - data["dbUsage"] = ton.GetDbUsage() - data["memory"] = GetMemoryInfo() - data["swap"] = GetSwapInfo() - data["uname"] = GetUname() - data["vprocess"] = GetValidatorProcessInfo() - data["dbStats"] = get_db_stats() - elections = local.try_function(ton.GetElectionEntries) - complaints = local.try_function(ton.GetComplaints) - - # Get git hashes - gitHashes = dict() - gitHashes["mytonctrl"] = get_git_hash("/usr/src/mytonctrl") - gitHashes["validator"] = GetBinGitHash("/usr/bin/ton/validator-engine/validator-engine") - data["gitHashes"] = gitHashes - data["stake"] = local.db.get("stake") - - # Get validator config - vconfig = ton.GetValidatorConfig() - data["fullnode_adnl"] = vconfig.fullnode - - # Send data to toncenter server - liteUrl_default = "https://telemetry.toncenter.com/report_status" - liteUrl = local.db.get("telemetryLiteUrl", liteUrl_default) - output = json.dumps(data) - resp = requests.post(liteUrl, data=output, timeout=3) -#end define - -def GetBinGitHash(path, short=False): - if not os.path.isfile(path): - return - args = [path, "--version"] - process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) - output = process.stdout.decode("utf-8") - if "build information" not in output: - return - buff = output.split(' ') - start = buff.index("Commit:") + 1 - result = buff[start].replace(',', '') - if short is True: - result = result[:7] - return result -#end define - -def OverlayTelemetry(ton): - sendTelemetry = local.db.get("sendTelemetry") - if sendTelemetry is not True: - return - #end if - - # Get validator status - data = dict() - data["adnlAddr"] = ton.GetAdnlAddr() - data["overlaysStats"] = ton.GetOverlaysStats() - - # Send data to toncenter server - overlayUrl_default = "https://telemetry.toncenter.com/report_overlays" - overlayUrl = local.db.get("overlayTelemetryUrl", overlayUrl_default) - output = json.dumps(data) - resp = requests.post(overlayUrl, data=output, timeout=3) -#end define - -def Complaints(ton): - validatorIndex = ton.GetValidatorIndex() - if validatorIndex < 0: - return - #end if - - # Voting for complaints - config32 = ton.GetConfig32() - electionId = config32.get("startWorkTime") - complaintsHashes = ton.SaveComplaints(electionId) - complaints = ton.GetComplaints(electionId) - for key, item in complaints.items(): - complaintHash = item.get("hash") - complaintHash_hex = Dec2HexAddr(complaintHash) - if complaintHash_hex in complaintsHashes: - ton.VoteComplaint(electionId, complaintHash) -#end define - -def Slashing(ton): - isSlashing = local.db.get("isSlashing") - if isSlashing is not True: - return - #end if - - # Creating complaints - slash_time = local.buffer.slash_time - config32 = ton.GetConfig32() - start = config32.get("startWorkTime") - end = config32.get("endWorkTime") - local.add_log("slash_time {}, start {}, end {}".format(slash_time, start, end), "debug") - if slash_time != start: - end -= 60 - ton.CheckValidators(start, end) - local.buffer.slash_time = start -#end define - -def ScanLiteServers(ton): - # Считать список серверов - filePath = ton.liteClient.configPath - file = open(filePath, 'rt') - text = file.read() - file.close() - data = json.loads(text) - - # Пройтись по серверам - result = list() - liteservers = data.get("liteservers") - for index in range(len(liteservers)): - try: - ton.liteClient.Run("last", index=index) - result.append(index) - except: pass - #end for - - # Записать данные в базу - local.db["liteServers"] = result -#end define - -def General(): - local.add_log("start General function", "debug") - ton = MyTonCore() - - # Запустить потоки - local.start_cycle(Elections, sec=600, args=(ton, )) - local.start_cycle(Statistics, sec=10) - local.start_cycle(Offers, sec=600, args=(ton, )) - local.start_cycle(Complaints, sec=600, args=(ton, )) - local.start_cycle(Slashing, sec=600, args=(ton, )) - local.start_cycle(Domains, sec=600, args=(ton, )) - local.start_cycle(Telemetry, sec=60, args=(ton, )) - local.start_cycle(OverlayTelemetry, sec=7200, args=(ton, )) - local.start_cycle(ScanLiteServers, sec=60, args=(ton,)) - local.start_cycle(custom_overlays, sec=60, args=(local, ton,)) - thr_sleep() -#end define def Dec2HexAddr(dec): h = dec2hex(dec) @@ -4333,30 +4091,7 @@ def Dec2HexAddr(dec): return h64 #end define -def xhex2hex(x): - try: - b = x[1:] - h = b.lower() - return h - except: - return None -#end define - -def hex2base64(h): - b = bytes.fromhex(h) - b64 = base64.b64encode(b) - s = b64.decode("utf-8") - return s +def HexAddr2Dec(h): + d = int(h, 16) + return d #end define - - - - -### -### Start of the program -### - -if __name__ == "__main__": - Init() - General() -#end if diff --git a/mytoncore/telemetry.py b/mytoncore/telemetry.py new file mode 100644 index 00000000..f02b2eaf --- /dev/null +++ b/mytoncore/telemetry.py @@ -0,0 +1,143 @@ +import os +import subprocess + +import psutil + +from mytoncore.utils import parse_db_stats +from mypylib.mypylib import get_service_pid + + +def GetUname(): + data = os.uname() + result = dict( + zip('sysname nodename release version machine'.split(), data)) + result.pop("nodename") + return result +# end define + + +def GetMemoryInfo(): + result = dict() + data = psutil.virtual_memory() + result["total"] = round(data.total / 10**9, 2) + result["usage"] = round(data.used / 10**9, 2) + result["usagePercent"] = data.percent + return result +# end define + + +def GetSwapInfo(): + result = dict() + data = psutil.swap_memory() + result["total"] = round(data.total / 10**9, 2) + result["usage"] = round(data.used / 10**9, 2) + result["usagePercent"] = data.percent + return result +# end define + + +def GetValidatorProcessInfo(): + pid = get_service_pid("validator") + if pid == None or pid == 0: + return + p = psutil.Process(pid) + mem = p.memory_info() + result = dict() + result["cpuPercent"] = p.cpu_percent() + memory = dict() + memory["rss"] = mem.rss + memory["vms"] = mem.vms + memory["shared"] = mem.shared + memory["text"] = mem.text + memory["lib"] = mem.lib + memory["data"] = mem.data + memory["dirty"] = mem.dirty + result["memory"] = memory + # io = p.io_counters() # Permission denied: '/proc/{pid}/io' + return result +# end define + + +def get_db_stats(): + result = { + 'rocksdb': { + 'ok': True, + 'message': '', + 'data': {} + }, + 'celldb': { + 'ok': True, + 'message': '', + 'data': {} + }, + } + rocksdb_stats_path = '/var/ton-work/db/db_stats.txt' + celldb_stats_path = '/var/ton-work/db/celldb/db_stats.txt' + if os.path.exists(rocksdb_stats_path): + try: + result['rocksdb']['data'] = parse_db_stats(rocksdb_stats_path) + except Exception as e: + result['rocksdb']['ok'] = False + result['rocksdb']['message'] = f'failed to fetch db stats: {e}' + else: + result['rocksdb']['ok'] = False + result['rocksdb']['message'] = 'db stats file is not exists' + # end if + + if os.path.exists(celldb_stats_path): + try: + result['celldb']['data'] = parse_db_stats(celldb_stats_path) + except Exception as e: + result['celldb']['ok'] = False + result['celldb']['message'] = f'failed to fetch db stats: {e}' + else: + result['celldb']['ok'] = False + result['celldb']['message'] = 'db stats file is not exists' + # end if + + return result +# end define + + +def get_cpu_name(): + with open('/proc/cpuinfo') as f: + for line in f: + if line.strip(): + if line.rstrip('\n').startswith('model name'): + return line.rstrip('\n').split(':')[1].strip() + return None + + +def is_host_virtual(): + try: + with open('/sys/class/dmi/id/product_name') as f: + product_name = f.read().strip().lower() + if 'virtual' in product_name or 'kvm' in product_name or 'qemu' in product_name or 'vmware' in product_name: + return {'virtual': True, 'product_name': product_name} + return {'virtual': False, 'product_name': product_name} + except FileNotFoundError: + return {'virtual': None, 'product_name': None} + + +def do_beacon_ping(host, count, timeout): + args = ['ping', '-c', str(count), '-W', str(timeout), host] + process = subprocess.run(args, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) + output = process.stdout.decode("utf-8") + avg = output.split('\n')[-2].split('=')[1].split('/')[1] + return float(avg) + + +def get_pings_values(): + return { + 'beacon-eu-01.toncenter.com': do_beacon_ping('beacon-eu-01.toncenter.com', 5, 10), + 'beacon-apac-01.toncenter.com': do_beacon_ping('beacon-apac-01.toncenter.com', 5, 10) + } + + +def get_validator_disk_name(): + process = subprocess.run("df -h /var/ton-work/ | sed -n '2 p' | awk '{print $1}'", stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3, shell=True) + output = process.stdout.decode("utf-8") + return output.strip() + diff --git a/mytoncore/tonblocksscanner.py b/mytoncore/tonblocksscanner.py new file mode 100644 index 00000000..6f607789 --- /dev/null +++ b/mytoncore/tonblocksscanner.py @@ -0,0 +1,208 @@ +import threading +import time + +from mytoncore.models import Block + + +class TonBlocksScanner(): + def __init__(self, ton, **kwargs): + self.ton = ton + self.prevMasterBlock = None + self.prevShardsBlock = dict() + self.blocksNum = 0 + self.transNum = 0 + self.nbr = kwargs.get("nbr") #NewBlockReaction + self.ntr = kwargs.get("ntr") #NewTransReaction + self.nmr = kwargs.get("nmr") #NewMessageReaction + self.local = kwargs.get("local") + self.sync = kwargs.get("sync", False) + self.delay = 0 + self.working = False + self.closing = False + #end define + + def Run(self): + self.StartThread(self.ScanBlocks, args=()) + self.StartThread(self.ThreadBalancing, args=()) + self.StartThread(self.StatusReading, args=()) + #end define + + def StartThread(self, func, args): + threading.Thread(target=func, args=args, name=func.__name__, daemon=True).start() + #end define + + def StartWithMode(self, func, args): + if self.sync: + func(*args) + else: + self.StartThread(func, args) + #end define + + def AddLog(self, text, type): + if self.local: + self.local.AddLog(text, type) + else: + print(text) + #end define + + def Try(self, func, **kwargs): + args = kwargs.get("args", tuple()) + for step in range(10): + time.sleep(step) + try: + result = func(*args) + return result + except Exception as ex: + err = ex + text = f"{func.__name__} step: {step}, error: {err}" + self.AddLog(text, "error") + raise Exception(err) + #end define + + def SetStartBlock(self, workchain, shardchain, seqno): + workchainType = type(workchain) + shardchainType = type(shardchain) + seqnoType = type(seqno) + if workchainType != int: + raise Exception(f"SetStartBlock error: workchain type mast be int, not {workchainType}") + if shardchainType != str: + raise Exception(f"SetStartBlock error: shardchain type mast be str, not {shardchainType}") + if seqnoType != int: + raise Exception(f"SetStartBlock error: seqno type mast be int, not {seqnoType}") + #end if + + block = Block() + block.workchain = workchain + block.shardchain = shardchain + block.seqno = seqno + if workchain == -1: + self.prevMasterBlock = block + else: + self.SetShardPrevBlock(block) + self.sync = True + #end define + + def ThreadBalancing(self): + while True: + tnum = threading.active_count() + if tnum > 100: + self.delay += 0.1 + elif tnum > 50: + self.delay += 0.01 + elif tnum < 50: + self.delay -= 0.1 + elif tnum < 100: + self.delay -= 0.01 + if self.delay < 0: + self.delay = 0 + if self.closing is True: + exit() + time.sleep(0.1) + #end define + + def StatusReading(self): + while True: + validatorStatus = self.ton.GetValidatorStatus() + validatorOutOfSync = validatorStatus.get("outOfSync") + if self.ton.liteClient.pubkeyPath is None: + self.working = False + self.closing = True + text = "TonBlocksScanner error: local liteserver is not configured, stop thread." + self.AddLog(text, "error") + exit() + if validatorOutOfSync > 20: + self.working = False + text = f"TonBlocksScanner warning: local liteserver is out of sync: {validatorOutOfSync}." + self.AddLog(text, "warning") + else: + self.working = True + time.sleep(10) + #end define + + def ScanBlocks(self): + while True: + if self.working is True: + self.ScanBlock() + if self.closing is True: + exit() + time.sleep(1) + #end define + + def ScanBlock(self): + block = self.Try(self.ton.GetLastBlock) + self.StartThread(self.SearchMissBlocks, args=(block, self.prevMasterBlock)) + if block != self.prevMasterBlock: + self.StartWithMode(self.ReadBlock, args=(block,)) + self.prevMasterBlock = block + #end define + + def ReadBlock(self, block): + self.StartWithMode(self.NewBlockReaction, args=(block,)) + shards = self.Try(self.ton.GetShards, args=(block,)) + for shard in shards: + self.StartThread(self.ReadShard, args=(shard,)) + #end define + + def ReadShard(self, shard): + block = shard.get("block") + prevBlock = self.GetShardPrevBlock(block.shardchain) + self.StartThread(self.SearchMissBlocks, args=(block, prevBlock)) + if block != prevBlock: + self.StartWithMode(self.NewBlockReaction, args=(block,)) + self.SetShardPrevBlock(block) + #end define + + def SearchMissBlocks(self, block, prevBlock): + if prevBlock is None: + return + diff = block.seqno - prevBlock.seqno + #for i in range(1, diff): + for i in range(diff-1, 0, -1): + workchain = block.workchain + shardchain = block.shardchain + seqno = block.seqno - i + self.StartWithMode(self.SearchBlock, args=(workchain, shardchain, seqno)) + #end define + + def SearchBlock(self, workchain, shardchain, seqno): + if self.delay != 0: + time.sleep(self.delay) + block = self.Try(self.ton.GetBlock, args=(workchain, shardchain, seqno)) + self.StartWithMode(self.NewBlockReaction, args=(block,)) + #end define + + def GetShardPrevBlock(self, shardchain): + prevBlock = self.prevShardsBlock.get(shardchain) + return prevBlock + #end define + + def SetShardPrevBlock(self, prevBlock): + self.prevShardsBlock[prevBlock.shardchain] = prevBlock + #end define + + def NewBlockReaction(self, block): + #print(f"{bcolors.green} block: {bcolors.endc} {block}") + self.blocksNum += 1 + if self.nbr: + self.StartThread(self.nbr, args=(block,)) + transactions = self.Try(self.ton.GetTransactions, args=(block,)) + for trans in transactions: + self.StartWithMode(self.NewTransReaction, args=(trans,)) + #end define + + def NewTransReaction(self, trans): + #print(f"{bcolors.magenta} trans: {bcolors.endc} {self.transNum}", "debug") + self.transNum += 1 + if self.ntr: + self.StartThread(self.ntr, args=(trans,)) + messageList = self.Try(self.ton.GetTrans, args=(trans,)) + for message in messageList: + self.NewMessageReaction(message) + #end define + + def NewMessageReaction(self, message): + if self.nmr: + self.StartThread(self.nmr, args=(message,)) + #print(f"{bcolors.yellow} message: {bcolors.endc} {message}") + #end define +#end class diff --git a/mytoncore/utils.py b/mytoncore/utils.py new file mode 100644 index 00000000..0a8bdc91 --- /dev/null +++ b/mytoncore/utils.py @@ -0,0 +1,99 @@ +import base64 +import json +import re + + +def str2b64(s): + b = s.encode("utf-8") + b64 = base64.b64encode(b) + b64 = b64.decode("utf-8") + return b64 +# end define + + +def b642str(b64): + b64 = b64.encode("utf-8") + b = base64.b64decode(b64) + s = b.decode("utf-8") + return s +# end define + + +def dict2b64(d): + s = json.dumps(d) + b64 = str2b64(s) + return b64 +# end define + + +def b642dict(b64): + s = b642str(b64) + d = json.loads(s) + return d +# end define + + +def hex2b64(input): # TODO: remove duplicates + hexBytes = bytes.fromhex(input) + b64Bytes = base64.b64encode(hexBytes) + b64String = b64Bytes.decode() + return b64String +# end define + + +def b642hex(input): + b64Bytes = input.encode() + hexBytes = base64.b64decode(b64Bytes) + hexString = hexBytes.hex() + return hexString +# end define + + +def xhex2hex(x): + try: + b = x[1:] + h = b.lower() + return h + except: + return None +#end define + +def hex2base64(h): # TODO: remove duplicates + b = bytes.fromhex(h) + b64 = base64.b64encode(b) + s = b64.decode("utf-8") + return s +#end define + + +def str2bool(str): + if str == "true": + return True + return False +# end define + + +def ng2g(ng): + if ng is None: + return + return int(ng)/10**9 +#end define + + +def parse_db_stats(path: str): + with open(path) as f: + lines = f.readlines() + result = {} + for line in lines: + s = line.strip().split(maxsplit=1) + items = re.findall(r"(\S+)\s:\s(\S+)", s[1]) + if len(items) == 1: + item = items[0] + if float(item[1]) > 0: + result[s[0]] = float(item[1]) + else: + if any(float(v) > 0 for k, v in items): + result[s[0]] = {} + result[s[0]] = {k: float(v) for k, v in items} + return result +# end define diff --git a/mytoncore/validator_console.py b/mytoncore/validator_console.py new file mode 100644 index 00000000..85d44891 --- /dev/null +++ b/mytoncore/validator_console.py @@ -0,0 +1,27 @@ +import subprocess + + +class ValidatorConsole: + def __init__(self, local): + self.local = local + self.appPath = None + self.privKeyPath = None + self.pubKeyPath = None + self.addr = None + #end define + + def Run(self, cmd, **kwargs): + console_timeout = self.local.db.console_timeout if self.local.db.console_timeout else 3 + timeout = kwargs.get("timeout", console_timeout) + if self.appPath is None or self.privKeyPath is None or self.pubKeyPath is None: + raise Exception("ValidatorConsole error: Validator console is not settings") + args = [self.appPath, "-k", self.privKeyPath, "-p", self.pubKeyPath, "-a", self.addr, "-v", "0", "--cmd", cmd] + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) + output = process.stdout.decode("utf-8") + err = process.stderr.decode("utf-8") + if len(err) > 0: + self.local.add_log("args: {args}".format(args=args), "error") + raise Exception("ValidatorConsole error: {err}".format(err=err)) + return output + #end define +#end class diff --git a/mytonctrl.py b/mytonctrl.py old mode 100755 new mode 100644 index 1cbb1683..4056f883 --- a/mytonctrl.py +++ b/mytonctrl.py @@ -1,1241 +1,23 @@ -#!/usr/bin/env python3 -# -*- coding: utf_8 -*- - -from mypylib.mypylib import * -from mypyconsole.mypyconsole import * -from custom_overlays import add_custom_overlay, list_custom_overlays, delete_custom_overlay - -from mytoncore import * -import sys, getopt, os - -local = MyPyClass(__file__) -console = MyPyConsole() -ton = MyTonCore() - -def Init(argv): - # Load translate table - local.init_translator(local.buffer.my_dir + "translate.json") - - # Create user console - console.name = "MyTonCtrl" - console.startFunction = PreUp - - console.AddItem("update", Update, local.translate("update_cmd")) - console.AddItem("upgrade", Upgrade, local.translate("upgrade_cmd")) - console.AddItem("installer", Installer, local.translate("installer_cmd")) - console.AddItem("status", PrintStatus, local.translate("status_cmd")) - console.AddItem("seqno", Seqno, local.translate("seqno_cmd")) - console.AddItem("getconfig", GetConfig, local.translate("getconfig_cmd")) - - console.AddItem("add_custom_overlay", add_custom_overlay, local.translate("add_custom_overlay_cmd")) - console.AddItem("list_custom_overlays", list_custom_overlays, local.translate("list_custom_overlays_cmd")) - console.AddItem("delete_custom_overlay", delete_custom_overlay, local.translate("delete_custom_overlay_cmd")) - console.AddItem("set_archive_ttl", set_archive_ttl, local.translate("set_archive_ttl_cmd")) - - console.AddItem("nw", CreatNewWallet, local.translate("nw_cmd")) - console.AddItem("aw", ActivateWallet, local.translate("aw_cmd")) - console.AddItem("wl", PrintWalletsList, local.translate("wl_cmd")) - console.AddItem("iw", ImportWallet, local.translate("iw_cmd")) - console.AddItem("swv", SetWalletVersion, local.translate("swv_cmd")) - console.AddItem("ew", ExportWallet, local.translate("ex_cmd")) - console.AddItem("dw", DeleteWallet, local.translate("dw_cmd")) - - console.AddItem("vas", ViewAccountStatus, local.translate("vas_cmd")) - console.AddItem("vah", ViewAccountHistory, local.translate("vah_cmd")) - console.AddItem("mg", MoveCoins, local.translate("mg_cmd")) - console.AddItem("mgtp", MoveCoinsThroughProxy, local.translate("mgtp_cmd")) - - console.AddItem("nb", CreatNewBookmark, local.translate("nb_cmd")) - console.AddItem("bl", PrintBookmarksList, local.translate("bl_cmd")) - console.AddItem("db", DeleteBookmark, local.translate("db_cmd")) - - console.AddItem("nd", NewDomain, local.translate("nd_cmd")) - console.AddItem("dl", PrintDomainsList, local.translate("dl_cmd")) - console.AddItem("vds", ViewDomainStatus, local.translate("vds_cmd")) - console.AddItem("dd", DeleteDomain, local.translate("dd_cmd")) - console.AddItem("gdfa", GetDomainFromAuction, local.translate("gdfa_cmd")) - - console.AddItem("ol", PrintOffersList, local.translate("ol_cmd")) - console.AddItem("vo", VoteOffer, local.translate("vo_cmd")) - console.AddItem("od", OfferDiff, local.translate("od_cmd")) - - console.AddItem("el", PrintElectionEntriesList, local.translate("el_cmd")) - console.AddItem("ve", VoteElectionEntry, local.translate("ve_cmd")) - console.AddItem("vl", PrintValidatorList, local.translate("vl_cmd")) - console.AddItem("cl", PrintComplaintsList, local.translate("cl_cmd")) - console.AddItem("vc", VoteComplaint, local.translate("vc_cmd")) - - console.AddItem("get", GetSettings, local.translate("get_cmd")) - console.AddItem("set", SetSettings, local.translate("set_cmd")) - console.AddItem("xrestart", Xrestart, local.translate("xrestart_cmd")) - console.AddItem("xlist", Xlist, local.translate("xlist_cmd")) - - console.AddItem("new_pool", NewPool, local.translate("new_pool_cmd")) - console.AddItem("pools_list", PrintPoolsList, local.translate("pools_list_cmd")) - console.AddItem("get_pool_data", GetPoolData, local.translate("get_pool_data_cmd")) - console.AddItem("activate_pool", ActivatePool, local.translate("activate_pool_cmd")) - console.AddItem("deposit_to_pool", DepositToPool, local.translate("deposit_to_pool_cmd")) - console.AddItem("withdraw_from_pool", WithdrawFromPool, local.translate("withdraw_from_pool_cmd")) - console.AddItem("delete_pool", DeletePool, local.translate("delete_pool_cmd")) - - # Process input parameters - opts, args = getopt.getopt(argv,"hc:w:",["config=","wallets="]) - for opt, arg in opts: - if opt == '-h': - print ('mytonctrl.py -c -w ') - sys.exit() - elif opt in ("-c", "--config"): - configfile = arg - if not os.access(configfile, os.R_OK): - print ("Configuration file " + configfile + " could not be opened") - sys.exit() - - ton.dbFile = configfile - ton.Refresh() - elif opt in ("-w", "--wallets"): - wallets = arg - if not os.access(wallets, os.R_OK): - print ("Wallets path " + wallets + " could not be opened") - sys.exit() - elif not os.path.isdir(wallets): - print ("Wallets path " + wallets + " is not a directory") - sys.exit() - ton.walletsDir = wallets - #end for - - local.db.config.logLevel = "debug" - local.db.config.isLocaldbSaving = False - local.run() -#end define - -def PreUp(): - CheckMytonctrlUpdate() # check mtc current branch update only if there wasnt warning about mtc2 - CheckMytonctrl2Update() - CheckDiskUsage() - check_vport() - # CheckTonUpdate() -#end define - -def Installer(args): - args = ["python3", "/usr/src/mytonctrl/mytoninstaller.py"] - subprocess.run(args) -#end define - -def GetItemFromList(data, index): - try: - return data[index] - except: pass -#end define - -def check_vport(): - vconfig = ton.GetValidatorConfig() - addr = vconfig.addrs.pop() - ip = int2ip(addr.ip) - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket: - result = client_socket.connect_ex((ip, addr.port)) - if result != 0: - color_print(local.translate("vport_error")) -#end define - -def check_git(input_args, default_repo, text): - src_dir = "/usr/src" - git_path = f"{src_dir}/{default_repo}" - default_author = "ton-blockchain" - default_branch = "master" - - # Get author, repo, branch - local_author, local_repo = get_git_author_and_repo(git_path) - local_branch = get_git_branch(git_path) - - # Set author, repo, branch - data = GetAuthorRepoBranchFromArgs(input_args) - need_author = data.get("author") - need_repo = data.get("repo") - need_branch = data.get("branch") - - # Check if remote repo is different from default - 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 {text} to the tip use `{text} {remote_url}` command") - elif need_branch is None and local_branch != default_branch: - raise Exception(f"{text} error: You are on {local_branch} branch, to {text} to the tip of {local_branch} branch use `{text} {local_branch}` command") - #end if - - if need_author is None: - need_author = local_author - if need_repo is None: - need_repo = local_repo - if need_branch is None: - need_branch = local_branch - #end if - - return need_author, need_repo, need_branch -#end define - -def GetAuthorRepoBranchFromArgs(args): - data = dict() - arg1 = GetItemFromList(args, 0) - arg2 = GetItemFromList(args, 1) - if arg1: - if "https://" in arg1: - buff = arg1[8:].split('/') - print(f"buff: {buff}") - data["author"] = buff[1] - data["repo"] = buff[2] - tree = GetItemFromList(buff, 3) - if tree: - data["branch"] = GetItemFromList(buff, 4) - else: - data["branch"] = arg1 - if arg2: - data["branch"] = arg2 - return data -#end define - -def Update(args): - repo = "mytonctrl" - author, repo, branch = check_git(args, repo, "update") - - # Run script - runArgs = ["bash", "/usr/src/mytonctrl/scripts/update.sh", "-a", author, "-r", repo, "-b", branch] - exitCode = run_as_root(runArgs) - if exitCode == 0: - text = "Update - {green}OK{endc}" - else: - text = "Update - {red}Error{endc}" - color_print(text) - local.exit() -#end define - -def Upgrade(args): - repo = "ton" - author, repo, branch = check_git(args, repo, "upgrade") - - # Run script - runArgs = ["bash", "/usr/src/mytonctrl/scripts/upgrade.sh", "-a", author, "-r", repo, "-b", branch] - exitCode = run_as_root(runArgs) - if exitCode == 0: - text = "Upgrade - {green}OK{endc}" - else: - text = "Upgrade - {red}Error{endc}" - color_print(text) -#end define - -def CheckMytonctrlUpdate(): - git_path = local.buffer.my_dir - result = check_git_update(git_path) - if result is True: - color_print(local.translate("mytonctrl_update_available")) -#end define - - -def CheckMytonctrl2Update(): - try: - if not ton.find_myself_in_el(): # we are not validator in current and prev rounds - print('============================================================================================') - color_print(local.translate("update_mtc2_warning")) - print('============================================================================================') - except Exception as err: - local.add_log(f'Failed to check node as validator: {err}', "error") -# end define - - -def CheckDiskUsage(): - usage = ton.GetDbUsage() - if usage > 90: - print('============================================================================================') - color_print(local.translate("disk_usage_warning")) - print('============================================================================================') -#end define - - -def CheckTonUpdate(): - git_path = "/usr/src/ton" - result = check_git_update(git_path) - if result is True: - color_print(local.translate("ton_update_available")) -#end define - -def PrintStatus(args): - opt = None - if len(args) == 1: - opt = args[0] - adnlAddr = ton.GetAdnlAddr() - rootWorkchainEnabledTime_int = ton.GetRootWorkchainEnabledTime() - config34 = ton.GetConfig34() - config36 = ton.GetConfig36() - totalValidators = config34["totalValidators"] - onlineValidators = None - validatorEfficiency = None - if opt != "fast": - onlineValidators = ton.GetOnlineValidators() - validatorEfficiency = ton.GetValidatorEfficiency() - if onlineValidators: - onlineValidators = len(onlineValidators) - oldStartWorkTime = config36.get("startWorkTime") - if oldStartWorkTime is None: - oldStartWorkTime = config34.get("startWorkTime") - shardsNumber = ton.GetShardsNumber() - validatorStatus = ton.GetValidatorStatus() - config15 = ton.GetConfig15() - config17 = ton.GetConfig17() - fullConfigAddr = ton.GetFullConfigAddr() - fullElectorAddr = ton.GetFullElectorAddr() - startWorkTime = ton.GetActiveElectionId(fullElectorAddr) - validatorIndex = ton.GetValidatorIndex() - validatorWallet = ton.GetValidatorWallet() - dbSize = ton.GetDbSize() - dbUsage = ton.GetDbUsage() - memoryInfo = GetMemoryInfo() - swapInfo = GetSwapInfo() - offersNumber = ton.GetOffersNumber() - complaintsNumber = ton.GetComplaintsNumber() - statistics = ton.GetSettings("statistics") - tpsAvg = ton.GetStatistics("tpsAvg", statistics) - netLoadAvg = ton.GetStatistics("netLoadAvg", statistics) - disksLoadAvg = ton.GetStatistics("disksLoadAvg", statistics) - disksLoadPercentAvg = ton.GetStatistics("disksLoadPercentAvg", statistics) - if validatorWallet is not None: - validatorAccount = ton.GetAccount(validatorWallet.addrB64) - else: - validatorAccount = None - PrintTonStatus(startWorkTime, totalValidators, onlineValidators, shardsNumber, offersNumber, complaintsNumber, tpsAvg) - PrintLocalStatus(adnlAddr, validatorIndex, validatorEfficiency, validatorWallet, validatorAccount, validatorStatus, dbSize, dbUsage, memoryInfo, swapInfo, netLoadAvg, disksLoadAvg, disksLoadPercentAvg) - PrintTonConfig(fullConfigAddr, fullElectorAddr, config15, config17) - PrintTimes(rootWorkchainEnabledTime_int, startWorkTime, oldStartWorkTime, config15) -#end define - -def PrintTonStatus(startWorkTime, totalValidators, onlineValidators, shardsNumber, offersNumber, complaintsNumber, tpsAvg): - tps1 = tpsAvg[0] - tps5 = tpsAvg[1] - tps15 = tpsAvg[2] - allValidators = totalValidators - newOffers = offersNumber.get("new") - allOffers = offersNumber.get("all") - newComplaints = complaintsNumber.get("new") - allComplaints = complaintsNumber.get("all") - tps1_text = bcolors.green_text(tps1) - tps5_text = bcolors.green_text(tps5) - tps15_text = bcolors.green_text(tps15) - tps_text = local.translate("ton_status_tps").format(tps1_text, tps5_text, tps15_text) - onlineValidators_text = GetColorInt(onlineValidators, border=allValidators*2/3, logic="more") - allValidators_text = bcolors.yellow_text(allValidators) - validators_text = local.translate("ton_status_validators").format(onlineValidators_text, allValidators_text) - shards_text = local.translate("ton_status_shards").format(bcolors.green_text(shardsNumber)) - newOffers_text = bcolors.green_text(newOffers) - allOffers_text = bcolors.yellow_text(allOffers) - offers_text = local.translate("ton_status_offers").format(newOffers_text, allOffers_text) - newComplaints_text = bcolors.green_text(newComplaints) - allComplaints_text = bcolors.yellow_text(allComplaints) - complaints_text = local.translate("ton_status_complaints").format(newComplaints_text, allComplaints_text) - - if startWorkTime == 0: - election_text = bcolors.yellow_text("closed") - else: - election_text = bcolors.green_text("open") - election_text = local.translate("ton_status_election").format(election_text) - - color_print(local.translate("ton_status_head")) - print(tps_text) - print(validators_text) - print(shards_text) - print(offers_text) - print(complaints_text) - print(election_text) - print() -#end define - -def PrintLocalStatus(adnlAddr, validatorIndex, validatorEfficiency, validatorWallet, validatorAccount, validatorStatus, dbSize, dbUsage, memoryInfo, swapInfo, netLoadAvg, disksLoadAvg, disksLoadPercentAvg): - if validatorWallet is None: - return - walletAddr = validatorWallet.addrB64 - walletBalance = validatorAccount.balance - cpuNumber = psutil.cpu_count() - loadavg = get_load_avg() - cpuLoad1 = loadavg[0] - cpuLoad5 = loadavg[1] - cpuLoad15 = loadavg[2] - netLoad1 = netLoadAvg[0] - netLoad5 = netLoadAvg[1] - netLoad15 = netLoadAvg[2] - validatorOutOfSync = validatorStatus.get("outOfSync") - - validatorIndex_text = GetColorInt(validatorIndex, 0, logic="more") - validatorIndex_text = local.translate("local_status_validator_index").format(validatorIndex_text) - validatorEfficiency_text = GetColorInt(validatorEfficiency, 10, logic="more", ending=" %") - validatorEfficiency_text = local.translate("local_status_validator_efficiency").format(validatorEfficiency_text) - adnlAddr_text = local.translate("local_status_adnl_addr").format(bcolors.yellow_text(adnlAddr)) - walletAddr_text = local.translate("local_status_wallet_addr").format(bcolors.yellow_text(walletAddr)) - walletBalance_text = local.translate("local_status_wallet_balance").format(bcolors.green_text(walletBalance)) - - # CPU status - cpuNumber_text = bcolors.yellow_text(cpuNumber) - cpuLoad1_text = GetColorInt(cpuLoad1, cpuNumber, logic="less") - cpuLoad5_text = GetColorInt(cpuLoad5, cpuNumber, logic="less") - cpuLoad15_text = GetColorInt(cpuLoad15, cpuNumber, logic="less") - cpuLoad_text = local.translate("local_status_cpu_load").format(cpuNumber_text, cpuLoad1_text, cpuLoad5_text, cpuLoad15_text) - - # Memory status - ramUsage = memoryInfo.get("usage") - ramUsagePercent = memoryInfo.get("usagePercent") - swapUsage = swapInfo.get("usage") - swapUsagePercent = swapInfo.get("usagePercent") - ramUsage_text = GetColorInt(ramUsage, 100, logic="less", ending=" Gb") - ramUsagePercent_text = GetColorInt(ramUsagePercent, 90, logic="less", ending="%") - swapUsage_text = GetColorInt(swapUsage, 100, logic="less", ending=" Gb") - swapUsagePercent_text = GetColorInt(swapUsagePercent, 90, logic="less", ending="%") - ramLoad_text = "{cyan}ram:[{default}{data}, {percent}{cyan}]{endc}" - ramLoad_text = ramLoad_text.format(cyan=bcolors.cyan, default=bcolors.default, endc=bcolors.endc, data=ramUsage_text, percent=ramUsagePercent_text) - swapLoad_text = "{cyan}swap:[{default}{data}, {percent}{cyan}]{endc}" - swapLoad_text = swapLoad_text.format(cyan=bcolors.cyan, default=bcolors.default, endc=bcolors.endc, data=swapUsage_text, percent=swapUsagePercent_text) - memoryLoad_text = local.translate("local_status_memory").format(ramLoad_text, swapLoad_text) - - # Network status - netLoad1_text = GetColorInt(netLoad1, 300, logic="less") - netLoad5_text = GetColorInt(netLoad5, 300, logic="less") - netLoad15_text = GetColorInt(netLoad15, 300, logic="less") - netLoad_text = local.translate("local_status_net_load").format(netLoad1_text, netLoad5_text, netLoad15_text) - - # Disks status - disksLoad_data = list() - for key, item in disksLoadAvg.items(): - diskLoad1_text = bcolors.green_text(item[0]) - diskLoad5_text = bcolors.green_text(item[1]) - diskLoad15_text = bcolors.green_text(item[2]) - diskLoadPercent1_text = GetColorInt(disksLoadPercentAvg[key][0], 80, logic="less", ending="%") - diskLoadPercent5_text = GetColorInt(disksLoadPercentAvg[key][1], 80, logic="less", ending="%") - diskLoadPercent15_text = GetColorInt(disksLoadPercentAvg[key][2], 80, logic="less", ending="%") - buff = "{}, {}" - buff = "{}{}:[{}{}{}]{}".format(bcolors.cyan, key, bcolors.default, buff, bcolors.cyan, bcolors.endc) - disksLoad_buff = buff.format(diskLoad15_text, diskLoadPercent15_text) - disksLoad_data.append(disksLoad_buff) - disksLoad_data = ", ".join(disksLoad_data) - disksLoad_text = local.translate("local_status_disks_load").format(disksLoad_data) - - # Thread status - mytoncoreStatus_bool = get_service_status("mytoncore") - validatorStatus_bool = get_service_status("validator") - mytoncoreUptime = get_service_uptime("mytoncore") - validatorUptime = get_service_uptime("validator") - mytoncoreUptime_text = bcolors.green_text(time2human(mytoncoreUptime)) - validatorUptime_text = bcolors.green_text(time2human(validatorUptime)) - mytoncoreStatus = GetColorStatus(mytoncoreStatus_bool) - validatorStatus = GetColorStatus(validatorStatus_bool) - mytoncoreStatus_text = local.translate("local_status_mytoncore_status").format(mytoncoreStatus, mytoncoreUptime_text) - validatorStatus_text = local.translate("local_status_validator_status").format(validatorStatus, validatorUptime_text) - validatorOutOfSync_text = local.translate("local_status_validator_out_of_sync").format(GetColorInt(validatorOutOfSync, 20, logic="less", ending=" s")) - 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" - validatorBinGitPath = "/usr/bin/ton/validator-engine/validator-engine" - mtcGitHash = get_git_hash(mtcGitPath, short=True) - validatorGitHash = GetBinGitHash(validatorBinGitPath, short=True) - mtcGitBranch = get_git_branch(mtcGitPath) - validatorGitBranch = get_git_branch(validatorGitPath) - mtcGitHash_text = bcolors.yellow_text(mtcGitHash) - validatorGitHash_text = bcolors.yellow_text(validatorGitHash) - mtcGitBranch_text = bcolors.yellow_text(mtcGitBranch) - validatorGitBranch_text = bcolors.yellow_text(validatorGitBranch) - mtcVersion_text = local.translate("local_status_version_mtc").format(mtcGitHash_text, mtcGitBranch_text) - 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) - print(adnlAddr_text) - 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(validatorOutOfSync_text) - print(dbStatus_text) - print(mtcVersion_text) - print(validatorVersion_text) - print() -#end define - -def GetColorInt(data, border, logic, ending=None): - if data is None: - result = bcolors.green_text("n/a") - elif logic == "more": - if data >= border: - result = bcolors.green_text(data, ending) - else: - result = bcolors.red_text(data, ending) - elif logic == "less": - if data <= border: - result = bcolors.green_text(data, ending) - else: - result = bcolors.red_text(data, ending) - return result -#end define - -def GetColorStatus(input): - if input == True: - result = bcolors.green_text("working") - else: - result = bcolors.red_text("not working") - return result -#end define - -def PrintTonConfig(fullConfigAddr, fullElectorAddr, config15, config17): - validatorsElectedFor = config15["validatorsElectedFor"] - electionsStartBefore = config15["electionsStartBefore"] - electionsEndBefore = config15["electionsEndBefore"] - stakeHeldFor = config15["stakeHeldFor"] - minStake = config17["minStake"] - maxStake = config17["maxStake"] - - fullConfigAddr_text = local.translate("ton_config_configurator_addr").format(bcolors.yellow_text(fullConfigAddr)) - fullElectorAddr_text = local.translate("ton_config_elector_addr").format(bcolors.yellow_text(fullElectorAddr)) - validatorsElectedFor_text = bcolors.yellow_text(validatorsElectedFor) - electionsStartBefore_text = bcolors.yellow_text(electionsStartBefore) - electionsEndBefore_text = bcolors.yellow_text(electionsEndBefore) - stakeHeldFor_text = bcolors.yellow_text(stakeHeldFor) - elections_text = local.translate("ton_config_elections").format(validatorsElectedFor_text, electionsStartBefore_text, electionsEndBefore_text, stakeHeldFor_text) - minStake_text = bcolors.yellow_text(minStake) - maxStake_text = bcolors.yellow_text(maxStake) - stake_text = local.translate("ton_config_stake").format(minStake_text, maxStake_text) - - color_print(local.translate("ton_config_head")) - print(fullConfigAddr_text) - print(fullElectorAddr_text) - print(elections_text) - print(stake_text) - print() -#end define - -def PrintTimes(rootWorkchainEnabledTime_int, startWorkTime, oldStartWorkTime, config15): - validatorsElectedFor = config15["validatorsElectedFor"] - electionsStartBefore = config15["electionsStartBefore"] - electionsEndBefore = config15["electionsEndBefore"] - - if startWorkTime == 0: - startWorkTime = oldStartWorkTime - #end if - - # Calculate time - startValidation = startWorkTime - endValidation = startWorkTime + validatorsElectedFor - startElection = startWorkTime - electionsStartBefore - endElection = startWorkTime - electionsEndBefore - startNextElection = startElection + validatorsElectedFor - - # timestamp to datetime - rootWorkchainEnabledTime = timestamp2datetime(rootWorkchainEnabledTime_int) - startValidationTime = timestamp2datetime(startValidation) - endValidationTime = timestamp2datetime(endValidation) - startElectionTime = timestamp2datetime(startElection) - endElectionTime = timestamp2datetime(endElection) - startNextElectionTime = timestamp2datetime(startNextElection) - - # datetime to color text - rootWorkchainEnabledTime_text = local.translate("times_root_workchain_enabled_time").format(bcolors.yellow_text(rootWorkchainEnabledTime)) - startValidationTime_text = local.translate("times_start_validation_time").format(GetColorTime(startValidationTime, startValidation)) - endValidationTime_text = local.translate("times_end_validation_time").format(GetColorTime(endValidationTime, endValidation)) - startElectionTime_text = local.translate("times_start_election_time").format(GetColorTime(startElectionTime, startElection)) - endElectionTime_text = local.translate("times_end_election_time").format(GetColorTime(endElectionTime, endElection)) - startNextElectionTime_text = local.translate("times_start_next_election_time").format(GetColorTime(startNextElectionTime, startNextElection)) - - color_print(local.translate("times_head")) - print(rootWorkchainEnabledTime_text) - print(startValidationTime_text) - print(endValidationTime_text) - print(startElectionTime_text) - print(endElectionTime_text) - print(startNextElectionTime_text) -#end define - -def GetColorTime(datetime, timestamp): - newTimestamp = get_timestamp() - if timestamp > newTimestamp: - result = bcolors.green_text(datetime) - else: - result = bcolors.yellow_text(datetime) - return result -#end define - -def Seqno(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(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(args): - try: - walletName = args[0] - except Exception as err: - walletName = "all" - if walletName == "all": - ton.WalletsCheck() - else: - wallet = ton.GetLocalWallet(walletName) - if not os.path.isfile(wallet.bocFilePath): - local.add_log("Wallet {walletName} already activated".format(walletName=walletName), "warning") - return - ton.ActivateWallet(wallet) - color_print("ActivateWallet - {green}OK{endc}") -#end define - -def PrintWalletsList(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 ImportWalletFromFile(args): - try: - filePath = args[0] - except: - color_print("{red}Bad args. Usage:{endc} iw ") - return - if (".addr" in filePath): - filePath = filePath.replace(".addr", '') - if (".pk" in filePath): - filePath = filePath.replace(".pk", '') - if os.path.isfile(filePath + ".addr") == False: - local.add_log("ImportWalletFromFile error: Address file not found: " + filePath, "error") - return - if os.path.isfile(filePath + ".pk") == False: - local.add_log("ImportWalletFromFile error: Private key not found: " + filePath, "error") - return - if '/' in filePath: - walletName = filePath[filePath.rfind('/')+1:] - else: - walletName = filePath - copyfile(filePath + ".addr", ton.walletsDir + walletName + ".addr") - copyfile(filePath + ".pk", ton.walletsDir + walletName + ".pk") - color_print("ImportWalletFromFile - {green}OK{endc}") -#end define - -def ImportWallet(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(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(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(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(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.GetWalletVersionFromHash(account.codeHash) - statusTable = list() - statusTable += [["Address", "Status", "Version", "Balance"]] - statusTable += [[addrB64, account.status, version, account.balance]] - historyTable = GetHistoryTable(addrB64, 10) - print_table(statusTable) - print() - print_table(historyTable) -#end define - -def ViewAccountHistory(args): - try: - addr = args[0] - limit = int(args[1]) - except: - color_print("{red}Bad args. Usage:{endc} vah ") - return - table = GetHistoryTable(addr, limit) - print_table(table) -#end define - -def GetHistoryTable(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(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(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(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(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") - data = item.get("data") - table += [[name, type, addr, data]] - print_table(table) -#end define - -def DeleteBookmark(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 PrintOffersList(args): - offers = ton.GetOffers() - if "--json" in args: - text = json.dumps(offers, indent=2) - print(text) - else: - table = list() - table += [["Hash", "Votes", "W/L", "Approved", "Is passed"]] - for item in offers: - 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, votedValidators, wl, approvedPercent_text, isPassed]] - print_table(table) -#end define - -def VoteOffer(args): - if len(args) == 0: - color_print("{red}Bad args. Usage:{endc} vo ") - return - for offerHash in args: - ton.VoteOffer(offerHash) - color_print("VoteOffer - {green}OK{endc}") -#end define - -def OfferDiff(args): - try: - offerHash = args[0] - offerHash = offerHash - except: - color_print("{red}Bad args. Usage:{endc} od ") - return - ton.GetOfferDiff(offerHash) -#end define - -def GetConfig(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(args): - past = "past" in args - complaints = ton.GetComplaints(past=past) - if "--json" in args: - text = json.dumps(complaints, indent=2) - print(text) - else: - table = list() - table += [["Election id", "ADNL", "Fine (part)", "Votes", "Approved", "Is passed"]] - for key, item in complaints.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 VoteComplaint(args): - try: - electionId = args[0] - complaintHash = args[1] - except: - color_print("{red}Bad args. Usage:{endc} vc ") - return - ton.VoteComplaint(electionId, complaintHash) - color_print("VoteComplaint - {green}OK{endc}") -#end define - -def NewDomain(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(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(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(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(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(args): - past = "past" in args - entries = ton.GetElectionEntries(past=past) - if "--json" in args: - text = json.dumps(entries, indent=2) - print(text) - else: - table = list() - table += [["ADNL", "Pubkey", "Wallet", "Stake", "Max-factor"]] - for key, item in entries.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 VoteElectionEntry(args): - Elections(ton) - color_print("VoteElectionEntry - {green}OK{endc}") -#end define - -def PrintValidatorList(args): - past = "past" in args - validators = ton.GetValidatorsList(past=past) - if "--json" in args: - text = json.dumps(validators, indent=2) - print(text) - else: - table = list() - table += [["ADNL", "Pubkey", "Wallet", "Efficiency", "Online"]] - for item in validators: - 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 += [[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(args): - try: - name = args[0] - except: - color_print("{red}Bad args. Usage:{endc} get ") - return - result = ton.GetSettings(name) - print(json.dumps(result, indent=2)) -#end define - -def SetSettings(args): - try: - name = args[0] - value = args[1] - except: - color_print("{red}Bad args. Usage:{endc} set ") - return - result = ton.SetSettings(name, value) - color_print("SetSettings - {green}OK{endc}") -#end define - -def Xrestart(inputArgs): - if len(inputArgs) < 2: - color_print("{red}Bad args. Usage:{endc} xrestart ") - return - args = ["python3", "/usr/src/mytonctrl/scripts/xrestart.py"] - args += inputArgs - exitCode = run_as_root(args) - if exitCode == 0: - text = "Xrestart - {green}OK{endc}" - else: - text = "Xrestart - {red}Error{endc}" - color_print(text) -#end define - -def Xlist(args): - color_print("Xlist - {green}OK{endc}") -#end define - -def NewPool(args): - try: - poolName = args[0] - validatorRewardSharePercent = float(args[1]) - maxNominatorsCount = int(args[2]) - minValidatorStake = int(args[3]) - minNominatorStake = int(args[4]) - except: - color_print("{red}Bad args. Usage:{endc} new_pool ") - return - ton.CreatePool(poolName, validatorRewardSharePercent, maxNominatorsCount, minValidatorStake, minNominatorStake) - color_print("NewPool - {green}OK{endc}") -#end define - -def ActivatePool(args): - try: - poolName = args[0] - except: - color_print("{red}Bad args. Usage:{endc} activate_pool ") - return - pool = ton.GetLocalPool(poolName) - if not os.path.isfile(pool.bocFilePath): - local.add_log(f"Pool {poolName} already activated", "warning") - return - ton.ActivatePool(pool) - color_print("ActivatePool - {green}OK{endc}") -#end define - -def PrintPoolsList(args): - table = list() - table += [["Name", "Status", "Balance", "Address"]] - data = ton.GetPools() - if (data is None or len(data) == 0): - print("No data") - return - for pool in data: - account = ton.GetAccount(pool.addrB64) - if account.status != "active": - pool.addrB64 = pool.addrB64_init - table += [[pool.name, account.status, account.balance, pool.addrB64]] - print_table(table) -#end define - -def GetPoolData(args): - try: - poolName = args[0] - except: - color_print("{red}Bad args. Usage:{endc} get_pool_data ") - return - if ton.IsAddr(poolName): - poolAddr = poolName - else: - pool = ton.GetLocalPool(poolName) - poolAddr = pool.addrB64 - poolData = ton.GetPoolData(poolAddr) - print(json.dumps(poolData, indent=4)) -#end define - -def DepositToPool(args): - try: - walletName = args[0] - pollAddr = args[1] - amount = float(args[2]) - except: - color_print("{red}Bad args. Usage:{endc} deposit_to_pool ") - return - ton.DepositToPool(walletName, pollAddr, amount) - color_print("DepositToPool - {green}OK{endc}") -#end define - -def WithdrawFromPool(args): - try: - poolAddr = args[0] - amount = float(args[1]) - except: - color_print("{red}Bad args. Usage:{endc} withdraw_from_pool ") - return - ton.WithdrawFromPool(poolAddr, amount) - color_print("WithdrawFromPool - {green}OK{endc}") -#end define - -def DeletePool(args): - try: - poolName = args[0] - except: - color_print("{red}Bad args. Usage:{endc} delete_pool ") - return - pool = ton.GetLocalPool(poolName) - pool.Delete() - color_print("DeletePool - {green}OK{endc}") -#end define - -def UpdateValidatorSet(args): - try: - poolAddr = args[0] - except: - color_print("{red}Bad args. Usage:{endc} update_validator_set ") - return - wallet = self.GetValidatorWallet() - self.PoolUpdateValidatorSet(poolAddr, wallet) - color_print("DeletePool - {green}OK{endc}") -#end define - +#!/bin/env python3 + +# +# This is a migration script to update from legacy version of MTC +# +import os +import sys +import subprocess + +requirements_path = "/usr/src/mytonctrl/requirements.txt" +if os.path.isfile(requirements_path): + args = ["pip3", "install", "-r", requirements_path] + subprocess.run(args) +#end if -def set_archive_ttl(args): - if len(args) != 1: - color_print("{red}Bad args. Usage:{endc} set_archive_ttl ") - return - ttl = args[0] - result = run_as_root(['python3', '/usr/src/mytonctrl/scripts/set_archive_ttl.py', ttl]) - if result: - color_print("set_archive_ttl - {red}Error{endc}") - return - color_print("set_archive_ttl - {green}OK{endc}") -#end define +sys.path.insert(0, '/usr/bin/mytonctrl') # Add path to mytonctrl module -### -### Start of the program -### +from mytonctrl.mytonctrl import run_migrations -if __name__ == "__main__": - Init(sys.argv[1:]) - console.Run() -#end if +if __name__ == '__main__': + print('Found new version of mytonctrl! Migrating!') + run_migrations() diff --git a/mytonctrl/__init__.py b/mytonctrl/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mytonctrl/__main__.py b/mytonctrl/__main__.py new file mode 100644 index 00000000..22f2c85b --- /dev/null +++ b/mytonctrl/__main__.py @@ -0,0 +1,4 @@ +from mytonctrl.mytonctrl import mytonctrl + +if __name__ == '__main__': + mytonctrl() diff --git a/mytonctrl/migrate.py b/mytonctrl/migrate.py new file mode 100644 index 00000000..34746566 --- /dev/null +++ b/mytonctrl/migrate.py @@ -0,0 +1,53 @@ +import os +import pkg_resources + +from mypylib.mypylib import MyPyClass +from mytoncore.mytoncore import MyTonCore + +from mypylib.mypylib import ( + run_as_root +) + +from typing import Optional + + +def migrate_to_version_1(local: MyPyClass, ton: MyTonCore): + # get script path + migrate_script_path = pkg_resources.resource_filename('mytonctrl', 'migrations/migration_001.sh') + args = ["/bin/bash", migrate_script_path] + exit_code = run_as_root(args) + if exit_code != 0: + raise RuntimeError(f'Failed to run migration error. Exit code: {exit_code}') + return + + +def migrate(version: 0, local: MyPyClass, ton: MyTonCore): + restart = False + if version < 1: + local.add_log(f'Running migration {version} -> 1', 'info') + restart_required = migrate_to_version_1(local, ton) + restart = restart or restart_required + return 1, restart + + +def run_migrations(local: Optional[MyPyClass]=None, ton: Optional[MyTonCore]=None): + if local is None: + local = MyPyClass('mytonctrl.py') + if ton is None: + ton = MyTonCore(MyPyClass('mytoncore.py')) + + # migrations + version = 0 + + workdir = local.buffer.my_work_dir + version_file_path = os.path.join(workdir, 'VERSION') + if os.path.exists(version_file_path): + with open(version_file_path, 'r') as f: + version = int(f.read()) + + new_version, restart = migrate(version, local, ton) + + with open(version_file_path, 'w') as f: + f.write(f'{new_version}') + return restart +#end define diff --git a/mytonctrl/migrations/migration_001.sh b/mytonctrl/migrations/migration_001.sh new file mode 100644 index 00000000..6d792af9 --- /dev/null +++ b/mytonctrl/migrations/migration_001.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# installing pip package +if [ -f "setup.py" ]; then + workdir=$(pwd) +else + workdir=/usr/src/mytonctrl +fi + +cd $workdir +pip3 install -U pip . + +# update /usr/bin/mytonctrl +echo " Updating /usr/bin/mytonctrl" +cat < /usr/bin/mytonctrl +#!/bin/bash +/usr/bin/python3 -m mytonctrl \$@ +EOF +chmod +x /usr/bin/mytonctrl + +# update /etc/systemd/system/mytoncore.service +echo " Updating mytoncore service" +sed -i 's\/usr/src/mytonctrl/mytoncore.py\-m mytoncore\g' /etc/systemd/system/mytoncore.service +systemctl daemon-reload diff --git a/mytonctrl/migrations/roll_back_001.sh b/mytonctrl/migrations/roll_back_001.sh new file mode 100644 index 00000000..eab2869c --- /dev/null +++ b/mytonctrl/migrations/roll_back_001.sh @@ -0,0 +1,16 @@ +pip3 uninstall -y mytonctrl + +cd /usr/src +rm -rf mytonctrl +git clone --recursive -b mytonctrl1 https://github.com/ton-blockchain/mytonctrl + +echo "Updating /usr/bin/mytonctrl" +echo "/usr/bin/python3 /usr/src/mytonctrl/mytonctrl.py $@" > /usr/bin/mytonctrl +chmod +x /usr/bin/mytonctrl + +echo "Updating mytoncore service" +sed -i 's\-m mytoncore\/usr/src/mytonctrl/mytoncore.py\g' /etc/systemd/system/mytoncore.service +systemctl daemon-reload +systemctl restart mytoncore + +echo "Done" diff --git a/mytonctrl/mytonctrl.py b/mytonctrl/mytonctrl.py new file mode 100755 index 00000000..33224717 --- /dev/null +++ b/mytonctrl/mytonctrl.py @@ -0,0 +1,1519 @@ +#!/usr/bin/env python3 +# -*- coding: utf_8 -*- +import base64 +import subprocess +import json +import psutil +import inspect +import pkg_resources +import socket + +from functools import partial + +from mypylib.mypylib import ( + int2ip, + get_git_author_and_repo, + get_git_branch, + get_git_hash, + check_git_update, + get_service_status, + get_service_uptime, + get_load_avg, + run_as_root, + time2human, + timeago, + timestamp2datetime, + get_timestamp, + print_table, + color_print, + color_text, + bcolors, + Dict, + MyPyClass +) + +from mypyconsole.mypyconsole import MyPyConsole +from mytoncore.mytoncore import MyTonCore +from mytoncore.functions import ( + Slashing, + GetMemoryInfo, + GetSwapInfo, + GetBinGitHash, +) +from mytoncore.telemetry import is_host_virtual +from mytonctrl.migrate import run_migrations +from mytonctrl.utils import GetItemFromList, timestamp2utcdatetime, fix_git_config + +import sys, getopt, os + + +def Init(local, ton, console, argv): + # Load translate table + translate_path = pkg_resources.resource_filename('mytonctrl', 'resources/translate.json') + local.init_translator(translate_path) + + # this function substitutes local and ton instances if function has this args + def inject_globals(func): + args = [] + for arg_name in inspect.getfullargspec(func)[0]: + if arg_name == 'local': + args.append(local) + elif arg_name == 'ton': + args.append(ton) + return partial(func, *args) + + # Create user console + console.name = "MyTonCtrl" + console.startFunction = inject_globals(PreUp) + console.debug = ton.GetSettings("debug") + + console.AddItem("update", inject_globals(Update), local.translate("update_cmd")) + console.AddItem("upgrade", inject_globals(Upgrade), local.translate("upgrade_cmd")) + console.AddItem("installer", inject_globals(Installer), local.translate("installer_cmd")) + console.AddItem("status", inject_globals(PrintStatus), local.translate("status_cmd")) + console.AddItem("status_modes", inject_globals(mode_status), local.translate("status_modes_cmd")) + console.AddItem("status_settings", inject_globals(settings_status), local.translate("settings_status_cmd")) + console.AddItem("enable_mode", inject_globals(enable_mode), local.translate("enable_mode_cmd")) + console.AddItem("disable_mode", inject_globals(disable_mode), local.translate("disable_mode_cmd")) + console.AddItem("about", inject_globals(about), local.translate("about_cmd")) + console.AddItem("get", inject_globals(GetSettings), local.translate("get_cmd")) + 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) + module.add_console_commands(console) + + from modules.collator_config import CollatorConfigModule + module = CollatorConfigModule(ton, local) + module.add_console_commands(console) + + if ton.using_validator(): + from modules.validator import ValidatorModule + module = ValidatorModule(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) + module.add_console_commands(console) + + if ton.using_nominator_pool(): + from modules.nominator_pool import NominatorPoolModule + module = NominatorPoolModule(ton, local) + module.add_console_commands(console) + + if ton.using_single_nominator(): + from modules.single_pool import SingleNominatorModule + module = SingleNominatorModule(ton, local) + module.add_console_commands(console) + + if ton.using_liquid_staking(): + from modules.controller import ControllerModule + module = ControllerModule(ton, local) + module.add_console_commands(console) + + console.AddItem("cleanup", inject_globals(cleanup_validator_db), local.translate("cleanup_cmd")) + 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")) + + # Process input parameters + opts, args = getopt.getopt(argv,"hc:w:",["config=","wallets="]) + for opt, arg in opts: + if opt == '-h': + print ('mytonctrl.py -c -w ') + sys.exit() + elif opt in ("-c", "--config"): + configfile = arg + if not os.access(configfile, os.R_OK): + print ("Configuration file " + configfile + " could not be opened") + sys.exit() + + ton.dbFile = configfile + ton.Refresh() + elif opt in ("-w", "--wallets"): + wallets = arg + if not os.access(wallets, os.R_OK): + print ("Wallets path " + wallets + " could not be opened") + sys.exit() + elif not os.path.isdir(wallets): + print ("Wallets path " + wallets + " is not a directory") + sys.exit() + ton.walletsDir = wallets + #end for + + local.db.config.logLevel = "debug" if console.debug else "info" + local.db.config.isLocaldbSaving = False + local.run() +#end define + + +def activate_ton_storage_provider(local, ton, args): + wallet_name = "provider_wallet_001" + wallet = ton.GetLocalWallet(wallet_name) + account = ton.GetAccount(wallet.addrB64) + if account.status == "active": + color_print("activate_ton_storage_provider - {green}Already activated{endc}") + #return + ton.ActivateWallet(wallet) + destination = "0:7777777777777777777777777777777777777777777777777777777777777777" + ton_storage = ton.GetSettings("ton_storage") + comment = f"tsp-{ton_storage.provider.pubkey}" + flags = ["-n", "-C", comment] + ton.MoveCoins(wallet, destination, 0.01, flags=flags) + color_print("activate_ton_storage_provider - {green}OK{endc}") +#end define + + +def about(local, ton, args): + from modules import get_mode, get_mode_settings + if len(args) != 1: + color_print("{red}Bad args. Usage:{endc} about ") + mode_name = args[0] + mode = get_mode(mode_name) + if mode is None: + color_print(f"{{red}}Mode {mode_name} not found{{endc}}") + return + mode_settings = get_mode_settings(mode_name) + color_print(f'''{{cyan}}===[ {mode_name} MODE ]==={{endc}}''') + color_print(f'''Description: {mode.description}''') + color_print('Enabled: ' + color_text('{green}yes{endc}' if ton.get_mode_value(mode_name) else '{red}no{endc}')) + print('Settings:', 'no' if len(mode_settings) == 0 else '') + for setting_name, setting in mode_settings.items(): + color_print(f' {{bold}}{setting_name}{{endc}}: {setting.description}.\n Default value: {setting.default_value}') +#end define + + +def check_installer_user(local): + args = ["whoami"] + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) + username = process.stdout.decode("utf-8").strip() + + args = ["ls", "-lh", "/var/ton-work/keys/"] + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) + output = process.stdout.decode("utf-8") + actual_user = output.split('\n')[1].split()[2] + + if username != actual_user: + local.add_log(f'mytonctrl was installed by another user. Probably you need to launch mtc with `{actual_user}` user.', 'error') +#end define + + +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 + + +def Installer(args): + # args = ["python3", "/usr/src/mytonctrl/mytoninstaller.py"] + cmd = ["python3", "-m", "mytoninstaller"] + if args: + cmd += ["-c", *args] + subprocess.run(cmd) +#end define + + +def GetAuthorRepoBranchFromArgs(args): + data = dict() + arg1 = GetItemFromList(args, 0) + arg2 = GetItemFromList(args, 1) + if arg1: + if "https://" in arg1: + buff = arg1[8:].split('/') + print(f"buff: {buff}") + data["author"] = buff[1] + data["repo"] = buff[2] + tree = GetItemFromList(buff, 3) + if tree: + data["branch"] = GetItemFromList(buff, 4) + else: + data["branch"] = arg1 + if arg2: + data["branch"] = arg2 + return data +#end define + + +def check_vport(local, ton): + try: + vconfig = ton.GetValidatorConfig() + except: + local.add_log("GetValidatorConfig error", "error") + return + addr = vconfig.addrs.pop() + ip = int2ip(addr.ip) + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket: + result = client_socket.connect_ex((ip, addr.port)) + if result != 0: + color_print(local.translate("vport_error")) +#end define + + +def check_git(input_args, default_repo, text, default_branch='master'): + src_dir = "/usr/src" + git_path = f"{src_dir}/{default_repo}" + fix_git_config(git_path) + default_author = "ton-blockchain" + + # Get author, repo, branch + local_author, local_repo = get_git_author_and_repo(git_path) + local_branch = get_git_branch(git_path) + + # Set author, repo, branch + data = GetAuthorRepoBranchFromArgs(input_args) + need_author = data.get("author") + need_repo = data.get("repo") + need_branch = data.get("branch") + + # Check if remote repo is different from default + 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") + elif need_branch is None and local_branch != default_branch: + raise Exception(f"{text} error: You are on {local_branch} branch, to update to the tip of {local_branch} branch use `{text} {local_branch}` command") + #end if + + if need_author is None: + need_author = local_author + if need_repo is None: + need_repo = local_repo + if need_branch is None: + need_branch = local_branch + check_branch_exists(need_author, need_repo, need_branch) + return need_author, need_repo, need_branch +#end define + +def check_branch_exists(author, repo, branch): + url = f"https://github.com/{author}/{repo}.git" + args = ["git", "ls-remote", "--heads", 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: + raise Exception(f"Branch {branch} not found in {url}") +#end define + +def Update(local, args): + repo = "mytonctrl" + author, repo, branch = check_git(args, repo, "update") + + # Run script + update_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/update.sh') + runArgs = ["bash", update_script_path, "-a", author, "-r", repo, "-b", branch] + exitCode = run_as_root(runArgs) + if exitCode == 0: + text = "Update - {green}OK{endc}" + else: + text = "Update - {red}Error{endc}" + color_print(text) + local.exit() +#end define + +def Upgrade(ton, args): + repo = "ton" + author, repo, branch = check_git(args, repo, "upgrade") + + # bugfix if the files are in the wrong place + liteClient = ton.GetSettings("liteClient") + configPath = liteClient.get("configPath") + pubkeyPath = liteClient.get("liteServer").get("pubkeyPath") + if "ton-lite-client-test1" in configPath: + liteClient["configPath"] = configPath.replace("lite-client/ton-lite-client-test1.config.json", "global.config.json") + if "/usr/bin/ton" in pubkeyPath: + liteClient["liteServer"]["pubkeyPath"] = "/var/ton-work/keys/liteserver.pub" + ton.SetSettings("liteClient", liteClient) + validatorConsole = ton.GetSettings("validatorConsole") + privKeyPath = validatorConsole.get("privKeyPath") + pubKeyPath = validatorConsole.get("pubKeyPath") + if "/usr/bin/ton" in privKeyPath: + validatorConsole["privKeyPath"] = "/var/ton-work/keys/client" + if "/usr/bin/ton" in pubKeyPath: + validatorConsole["pubKeyPath"] = "/var/ton-work/keys/server.pub" + ton.SetSettings("validatorConsole", validatorConsole) + + # Run script + 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: + text = "Upgrade - {red}Error{endc}" + color_print(text) +#end define + +def rollback_to_mtc1(local, ton, args): + color_print("{red}Warning: this is dangerous, please make sure you've backed up mytoncore's db.{endc}") + a = input("Do you want to continue? [Y/n]\n") + if a.lower() != 'y': + print('aborted.') + return + ton.rollback_modes() + + workdir = local.buffer.my_work_dir + version_file_path = os.path.join(workdir, 'VERSION') + if os.path.exists(version_file_path): + os.remove(version_file_path) + + rollback_script_path = pkg_resources.resource_filename('mytonctrl', 'migrations/roll_back_001.sh') + run_args = ["bash", rollback_script_path] + run_as_root(run_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') + etabar_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/etabar.py') + benchmark_result_path = "/tmp/benchmark_result.json" + run_args = ["python3", etabar_script_path, str(timeout), benchmark_script_path, benchmark_result_path] + exit_code = run_as_root(run_args) + with open(benchmark_result_path, 'rt') as file: + text = file.read() + if exit_code != 0: + color_print("Benchmark - {red}Error:{endc} " + text) + return + #end if + + data = Dict(json.loads(text)) + table = list() + table += [["Test type", "Read speed", "Write speed", "Read iops", "Write iops", "Random ops"]] + table += [["Fio lite", data.lite.read_speed, data.lite.write_speed, data.lite.read_iops, data.lite.write_iops, None]] # RND-4K-QD64 + table += [["Fio hard", data.hard.read_speed, data.hard.write_speed, data.hard.read_iops, data.hard.write_iops, None]] # RND-4K-QD1 + table += [["RocksDB", None, None, None, None, data.full.random_ops]] + print_table(table) +#end define + +def CheckMytonctrlUpdate(local): + git_path = local.buffer.my_dir + result = check_git_update(git_path) + if result is True: + color_print(local.translate("mytonctrl_update_available")) +#end define + +def print_warning(local, warning_name: str): + color_print("============================================================================================") + color_print(local.translate(warning_name)) + color_print("============================================================================================") +#end define + +def check_disk_usage(local, ton): + usage = ton.GetDbUsage() + if usage > 90: + print_warning(local, "disk_usage_warning") +#end define + +def check_sync(local, ton): + validator_status = ton.GetValidatorStatus() + if not validator_status.is_working or validator_status.out_of_sync >= 20: + print_warning(local, "sync_warning") +#end define + +def check_validator_balance(local, ton): + validator_status = ton.GetValidatorStatus() + if not validator_status.is_working or validator_status.out_of_sync >= 20: + # Do not check the validator wallet balance if the node is not synchronized (via public lite-servers) + return + if ton.using_validator(): + validator_wallet = ton.GetValidatorWallet() + validator_account = local.try_function(ton.GetAccount, args=[validator_wallet.addrB64]) + if validator_account is None: + local.add_log(f"Failed to check validator wallet balance", "warning") + return + if validator_account.balance < 100: + print_warning(local, "validator_balance_warning") +#end define + +def check_vps(local, ton): + if ton.using_validator(): + data = local.try_function(is_host_virtual) + if data and data["virtual"]: + color_print(f"Virtualization detected: {data['product_name']}") +#end define + +def warnings(local, ton): + check_disk_usage(local, ton) + check_sync(local, ton) + check_validator_balance(local, ton) + check_vps(local, ton) +#end define + +def CheckTonUpdate(local): + git_path = "/usr/src/ton" + result = check_git_update(git_path) + if result is True: + 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) + status = color_text('{green}enabled{endc}' if modes[mode_name] else '{red}disabled{endc}') + table.append([mode_name, status, mode.description]) + print_table(table) +#end define + + +def settings_status(ton, args): + from modules import SETTINGS + table = [["Name", "Description", "Mode", "Default value", "Current value"]] + for name, setting in SETTINGS.items(): + current_value = ton.local.db.get(name) + table.append([name, setting.description, setting.mode, setting.default_value, current_value]) + print_table(table) +#end define + + +def PrintStatus(local, ton, args): + opt = None + if len(args) == 1: + opt = args[0] + + # Local status + validator_status = ton.GetValidatorStatus() + adnl_addr = ton.GetAdnlAddr() + validator_index = None + onlineValidators = None + validator_efficiency = None + validator_wallet = ton.GetValidatorWallet() + validator_account = Dict() + db_size = ton.GetDbSize() + db_usage = ton.GetDbUsage() + memory_info = GetMemoryInfo() + swap_info = GetSwapInfo() + statistics = ton.GetSettings("statistics") + net_load_avg = ton.GetStatistics("netLoadAvg", statistics) + disks_load_avg = ton.GetStatistics("disksLoadAvg", statistics) + disks_load_percent_avg = ton.GetStatistics("disksLoadPercentAvg", statistics) + + all_status = validator_status.is_working == True and validator_status.out_of_sync < 20 + + try: + vconfig = ton.GetValidatorConfig() + fullnode_adnl = base64.b64decode(vconfig.fullnode).hex().upper() + except: + fullnode_adnl = 'n/a' + + if all_status: + network_name = ton.GetNetworkName() + rootWorkchainEnabledTime_int = ton.GetRootWorkchainEnabledTime() + config34 = ton.GetConfig34() + config36 = ton.GetConfig36() + totalValidators = config34["totalValidators"] + + if opt != "fast": + onlineValidators = ton.GetOnlineValidators() + validator_efficiency = ton.GetValidatorEfficiency() + if onlineValidators: + onlineValidators = len(onlineValidators) + + oldStartWorkTime = config36.get("startWorkTime") + 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, + db_size, db_usage, memory_info, swap_info, net_load_avg, disks_load_avg, disks_load_percent_avg, fullnode_adnl) + if all_status: + PrintTonConfig(local, fullConfigAddr, fullElectorAddr, config15, config17) + PrintTimes(local, rootWorkchainEnabledTime_int, startWorkTime, oldStartWorkTime, config15) +#end define + +def PrintTonStatus(local, network_name, startWorkTime, totalValidators, onlineValidators, shardsNumber, offersNumber, complaintsNumber, tpsAvg): + #tps1 = tpsAvg[0] + #tps5 = tpsAvg[1] + #tps15 = tpsAvg[2] + allValidators = totalValidators + newOffers = offersNumber.get("new") + allOffers = offersNumber.get("all") + newComplaints = complaintsNumber.get("new") + allComplaints = complaintsNumber.get("all") + #tps1_text = bcolors.green_text(tps1) + #tps5_text = bcolors.green_text(tps5) + #tps15_text = bcolors.green_text(tps15) + + color_network_name = bcolors.green_text(network_name) if network_name == "mainnet" else bcolors.yellow_text(network_name) + network_name_text = local.translate("ton_status_network_name").format(color_network_name) + #tps_text = local.translate("ton_status_tps").format(tps1_text, tps5_text, tps15_text) + onlineValidators_text = GetColorInt(onlineValidators, border=allValidators*2/3, logic="more") + allValidators_text = bcolors.yellow_text(allValidators) + validators_text = local.translate("ton_status_validators").format(onlineValidators_text, allValidators_text) + shards_text = local.translate("ton_status_shards").format(bcolors.green_text(shardsNumber)) + newOffers_text = bcolors.green_text(newOffers) + allOffers_text = bcolors.yellow_text(allOffers) + offers_text = local.translate("ton_status_offers").format(newOffers_text, allOffers_text) + newComplaints_text = bcolors.green_text(newComplaints) + allComplaints_text = bcolors.yellow_text(allComplaints) + complaints_text = local.translate("ton_status_complaints").format(newComplaints_text, allComplaints_text) + + if startWorkTime == 0: + election_text = bcolors.yellow_text("closed") + else: + election_text = bcolors.green_text("open") + election_text = local.translate("ton_status_election").format(election_text) + + color_print(local.translate("ton_status_head")) + print(network_name_text) + #print(tps_text) + print(validators_text) + print(shards_text) + print(offers_text) + print(complaints_text) + print(election_text) + print() +#end define + +def PrintLocalStatus(local, adnlAddr, validatorIndex, validatorEfficiency, validatorWallet, validatorAccount, validator_status, dbSize, dbUsage, memoryInfo, swapInfo, netLoadAvg, disksLoadAvg, disksLoadPercentAvg, fullnode_adnl): + if validatorWallet is None: + return + walletAddr = validatorWallet.addrB64 + walletBalance = validatorAccount.balance + cpuNumber = psutil.cpu_count() + loadavg = get_load_avg() + cpuLoad1 = loadavg[0] + cpuLoad5 = loadavg[1] + cpuLoad15 = loadavg[2] + netLoad1 = netLoadAvg[0] + netLoad5 = netLoadAvg[1] + netLoad15 = netLoadAvg[2] + + validatorIndex_text = GetColorInt(validatorIndex, 0, logic="more") + validatorIndex_text = local.translate("local_status_validator_index").format(validatorIndex_text) + validatorEfficiency_text = GetColorInt(validatorEfficiency, 10, logic="more", ending=" %") + validatorEfficiency_text = local.translate("local_status_validator_efficiency").format(validatorEfficiency_text) + adnlAddr_text = local.translate("local_status_adnl_addr").format(bcolors.yellow_text(adnlAddr)) + fullnode_adnl_text = local.translate("local_status_fullnode_adnl").format(bcolors.yellow_text(fullnode_adnl)) + walletAddr_text = local.translate("local_status_wallet_addr").format(bcolors.yellow_text(walletAddr)) + walletBalance_text = local.translate("local_status_wallet_balance").format(bcolors.green_text(walletBalance)) + + # CPU status + cpuNumber_text = bcolors.yellow_text(cpuNumber) + cpuLoad1_text = GetColorInt(cpuLoad1, cpuNumber, logic="less") + cpuLoad5_text = GetColorInt(cpuLoad5, cpuNumber, logic="less") + cpuLoad15_text = GetColorInt(cpuLoad15, cpuNumber, logic="less") + cpuLoad_text = local.translate("local_status_cpu_load").format(cpuNumber_text, cpuLoad1_text, cpuLoad5_text, cpuLoad15_text) + + # Memory status + ramUsage = memoryInfo.get("usage") + ramUsagePercent = memoryInfo.get("usagePercent") + swapUsage = swapInfo.get("usage") + swapUsagePercent = swapInfo.get("usagePercent") + ramUsage_text = GetColorInt(ramUsage, 100, logic="less", ending=" Gb") + ramUsagePercent_text = GetColorInt(ramUsagePercent, 90, logic="less", ending="%") + swapUsage_text = GetColorInt(swapUsage, 100, logic="less", ending=" Gb") + swapUsagePercent_text = GetColorInt(swapUsagePercent, 90, logic="less", ending="%") + ramLoad_text = "{cyan}ram:[{default}{data}, {percent}{cyan}]{endc}" + ramLoad_text = ramLoad_text.format(cyan=bcolors.cyan, default=bcolors.default, endc=bcolors.endc, data=ramUsage_text, percent=ramUsagePercent_text) + swapLoad_text = "{cyan}swap:[{default}{data}, {percent}{cyan}]{endc}" + swapLoad_text = swapLoad_text.format(cyan=bcolors.cyan, default=bcolors.default, endc=bcolors.endc, data=swapUsage_text, percent=swapUsagePercent_text) + memoryLoad_text = local.translate("local_status_memory").format(ramLoad_text, swapLoad_text) + + # Network status + netLoad1_text = GetColorInt(netLoad1, 300, logic="less") + netLoad5_text = GetColorInt(netLoad5, 300, logic="less") + netLoad15_text = GetColorInt(netLoad15, 300, logic="less") + netLoad_text = local.translate("local_status_net_load").format(netLoad1_text, netLoad5_text, netLoad15_text) + + # Disks status + disksLoad_data = list() + for key, item in disksLoadAvg.items(): + diskLoad1_text = bcolors.green_text(item[0]) # TODO: this variables is unused. Why? + diskLoad5_text = bcolors.green_text(item[1]) # TODO: this variables is unused. Why? + diskLoad15_text = bcolors.green_text(item[2]) + diskLoadPercent1_text = GetColorInt(disksLoadPercentAvg[key][0], 80, logic="less", ending="%") # TODO: this variables is unused. Why? + diskLoadPercent5_text = GetColorInt(disksLoadPercentAvg[key][1], 80, logic="less", ending="%") # TODO: this variables is unused. Why? + diskLoadPercent15_text = GetColorInt(disksLoadPercentAvg[key][2], 80, logic="less", ending="%") + buff = "{}, {}" + buff = "{}{}:[{}{}{}]{}".format(bcolors.cyan, key, bcolors.default, buff, bcolors.cyan, bcolors.endc) + disksLoad_buff = buff.format(diskLoad15_text, diskLoadPercent15_text) + disksLoad_data.append(disksLoad_buff) + disksLoad_data = ", ".join(disksLoad_data) + disksLoad_text = local.translate("local_status_disks_load").format(disksLoad_data) + + # Thread status + mytoncoreStatus_bool = get_service_status("mytoncore") + validatorStatus_bool = get_service_status("validator") + mytoncoreUptime = get_service_uptime("mytoncore") + validatorUptime = get_service_uptime("validator") + mytoncoreUptime_text = bcolors.green_text(time2human(mytoncoreUptime)) + validatorUptime_text = bcolors.green_text(time2human(validatorUptime)) + mytoncoreStatus_color = GetColorStatus(mytoncoreStatus_bool) + 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') + + 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" + validatorBinGitPath = "/usr/bin/ton/validator-engine/validator-engine" + mtcGitHash = get_git_hash(mtcGitPath, short=True) + validatorGitHash = GetBinGitHash(validatorBinGitPath, short=True) + fix_git_config(mtcGitPath) + fix_git_config(validatorGitPath) + mtcGitBranch = get_git_branch(mtcGitPath) + validatorGitBranch = get_git_branch(validatorGitPath) + mtcGitHash_text = bcolors.yellow_text(mtcGitHash) + validatorGitHash_text = bcolors.yellow_text(validatorGitHash) + mtcGitBranch_text = bcolors.yellow_text(mtcGitBranch) + validatorGitBranch_text = bcolors.yellow_text(validatorGitBranch) + mtcVersion_text = local.translate("local_status_version_mtc").format(mtcGitHash_text, mtcGitBranch_text) + 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) + print(adnlAddr_text) + print(fullnode_adnl_text) + 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) + print(dbStatus_text) + print(mtcVersion_text) + print(validatorVersion_text) + print() +#end define + +def GetColorInt(data, border, logic, ending=None): + if data is None: + result = "n/a" + elif logic == "more": + if data >= border: + result = bcolors.green_text(data, ending) + else: + result = bcolors.red_text(data, ending) + elif logic == "less": + if data <= border: + result = bcolors.green_text(data, ending) + else: + result = bcolors.red_text(data, ending) + return result +#end define + +def GetColorStatus(input): + if input == True: + result = bcolors.green_text("working") + else: + result = bcolors.red_text("not working") + return result +#end define + +def PrintTonConfig(local, fullConfigAddr, fullElectorAddr, config15, config17): + validatorsElectedFor = config15["validatorsElectedFor"] + electionsStartBefore = config15["electionsStartBefore"] + electionsEndBefore = config15["electionsEndBefore"] + stakeHeldFor = config15["stakeHeldFor"] + minStake = config17["minStake"] + maxStake = config17["maxStake"] + + fullConfigAddr_text = local.translate("ton_config_configurator_addr").format(bcolors.yellow_text(fullConfigAddr)) + fullElectorAddr_text = local.translate("ton_config_elector_addr").format(bcolors.yellow_text(fullElectorAddr)) + validatorsElectedFor_text = bcolors.yellow_text(validatorsElectedFor) + electionsStartBefore_text = bcolors.yellow_text(electionsStartBefore) + electionsEndBefore_text = bcolors.yellow_text(electionsEndBefore) + stakeHeldFor_text = bcolors.yellow_text(stakeHeldFor) + elections_text = local.translate("ton_config_elections").format(validatorsElectedFor_text, electionsStartBefore_text, electionsEndBefore_text, stakeHeldFor_text) + minStake_text = bcolors.yellow_text(minStake) + maxStake_text = bcolors.yellow_text(maxStake) + stake_text = local.translate("ton_config_stake").format(minStake_text, maxStake_text) + + color_print(local.translate("ton_config_head")) + print(fullConfigAddr_text) + print(fullElectorAddr_text) + print(elections_text) + print(stake_text) + print() +#end define + +def PrintTimes(local, rootWorkchainEnabledTime_int, startWorkTime, oldStartWorkTime, config15): + validatorsElectedFor = config15["validatorsElectedFor"] + electionsStartBefore = config15["electionsStartBefore"] + electionsEndBefore = config15["electionsEndBefore"] + + if startWorkTime == 0: + startWorkTime = oldStartWorkTime + #end if + + # Calculate time + startValidation = startWorkTime + endValidation = startWorkTime + validatorsElectedFor + startElection = startWorkTime - electionsStartBefore + endElection = startWorkTime - electionsEndBefore + startNextElection = startElection + validatorsElectedFor + + # timestamp to datetime + rootWorkchainEnabledTime = timestamp2utcdatetime(rootWorkchainEnabledTime_int) + startValidationTime = timestamp2utcdatetime(startValidation) + endValidationTime = timestamp2utcdatetime(endValidation) + startElectionTime = timestamp2utcdatetime(startElection) + endElectionTime = timestamp2utcdatetime(endElection) + startNextElectionTime = timestamp2utcdatetime(startNextElection) + + # datetime to color text + rootWorkchainEnabledTime_text = local.translate("times_root_workchain_enabled_time").format(bcolors.yellow_text(rootWorkchainEnabledTime)) + startValidationTime_text = local.translate("times_start_validation_time").format(GetColorTime(startValidationTime, startValidation)) + endValidationTime_text = local.translate("times_end_validation_time").format(GetColorTime(endValidationTime, endValidation)) + startElectionTime_text = local.translate("times_start_election_time").format(GetColorTime(startElectionTime, startElection)) + endElectionTime_text = local.translate("times_end_election_time").format(GetColorTime(endElectionTime, endElection)) + startNextElectionTime_text = local.translate("times_start_next_election_time").format(GetColorTime(startNextElectionTime, startNextElection)) + + color_print(local.translate("times_head")) + print(rootWorkchainEnabledTime_text) + print(startValidationTime_text) + print(endValidationTime_text) + print(startElectionTime_text) + print(endElectionTime_text) + print(startNextElectionTime_text) +#end define + +def GetColorTime(datetime, timestamp): + newTimestamp = get_timestamp() + if timestamp > newTimestamp: + result = bcolors.green_text(datetime) + else: + result = bcolors.yellow_text(datetime) + 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 + data = ton.GetValidatorsList(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", "Efficiency", "Online"]] + for item in 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 += [[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] + except: + color_print("{red}Bad args. Usage:{endc} get ") + return + result = ton.GetSettings(name) + print(json.dumps(result, indent=2)) +#end define + +def SetSettings(ton, args): + try: + name = args[0] + value = args[1] + except: + color_print("{red}Bad args. Usage:{endc} set ") + return + if name == 'usePool' or name == 'useController': + mode_name = 'nominator-pool' if name == 'usePool' else 'liquid-staking' + color_print(f"{{red}} Error: set {name} ... is deprecated and does not work {{endc}}." + f"\nInstead, use {{bold}}enable_mode {mode_name}{{endc}}") + return + force = False + if len(args) > 2: + if args[2] == "--force": + force = True + from modules import get_setting + setting = get_setting(name) + if setting is None and not force: + color_print(f"{{red}} Error: setting {name} not found.{{endc}} Use flag --force to set it anyway") + return + if setting is not None and setting.mode is not None: + if not ton.get_mode_value(setting.mode) and not force: + color_print(f"{{red}} Error: mode {setting.mode} is disabled.{{endc}} Use flag --force to set it anyway") + return + ton.SetSettings(name, value) + color_print("SetSettings - {green}OK{endc}") +#end define + + +def enable_mode(local, ton, args): + try: + name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} enable_mode ") + return + ton.enable_mode(name) + color_print("enable_mode - {green}OK{endc}") + local.exit() +#end define + +def disable_mode(local, ton, args): + try: + name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} disable_mode ") + return + ton.disable_mode(name) + color_print("disable_mode - {green}OK{endc}") + local.exit() +#end define + +def Xrestart(inputArgs): + if len(inputArgs) < 2: + color_print("{red}Bad args. Usage:{endc} xrestart ") + return + xrestart_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/xrestart.py') + args = ["python3", xrestart_script_path] # TODO: Fix path + args += inputArgs + exitCode = run_as_root(args) + if exitCode == 0: + text = "Xrestart - {green}OK{endc}" + else: + text = "Xrestart - {red}Error{endc}" + color_print(text) +#end define + +def Xlist(args): + color_print("Xlist - {green}OK{endc}") +#end define + +def GetPubKey(ton, args): + adnlAddr = ton.GetAdnlAddr() + pubkey = ton.GetPubKey(adnlAddr) + print("pubkey:", pubkey) +#end define + +def SignShardOverlayCert(ton, args): + try: + adnl = args[0] + pubkey = args[0] + except: + color_print("{red}Bad args. Usage:{endc} ssoc ") + return + ton.SignShardOverlayCert(adnl, pubkey) +#end define + +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(): + local = MyPyClass('mytonctrl.py') + mytoncore_local = MyPyClass('mytoncore.py') + ton = MyTonCore(mytoncore_local) + console = MyPyConsole() + + # migrations + restart = run_migrations(local, ton) + + if not restart: + Init(local, ton, console, sys.argv[1:]) + console.Run() +#end define diff --git a/mytonctrl/progressbar.py b/mytonctrl/progressbar.py new file mode 100644 index 00000000..a8776184 --- /dev/null +++ b/mytonctrl/progressbar.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 + +from os import popen +from time import sleep +from sys import stdout +from threading import Thread + +class EtaBar: + def __init__(self, **kwargs): + self.toolbar_width = kwargs.get("toolbar_width", 10) + self.snake_width = kwargs.get("snake_width", 4) + self.timeout = kwargs.get("timeout", 60) + self.sleep_time = 0.1 + + self.square_symbol = '\u25A0' + self.new_line_symbol = '\r' + self.indent_symbol = ' ' + + self.tty_height, self.tty_width = self.get_tty_size() + stdout.reconfigure(encoding="utf-8") + #end define + + def run(self, func=None, *args, **kwargs): + if func is None: + func = self.stub + self.start_thread(func, args=args, kwargs=kwargs) + self.snake_process() + return self.thread_result + #end define + + def stub(self): + sleep(self.timeout) + #end define + + def get_tty_size(self): + with popen("stty size", 'r') as file: + tty_height, tty_width = file.read().split() + tty_height = int(tty_height) + tty_width = int(tty_width) + return tty_height, tty_width + #end define + + def start_thread(self, func, **kwargs): + self.thread_result = None + self.thread = Thread(target=self.thread_process, name=func.__name__, args=(func,), kwargs=kwargs, daemon=True) + self.thread.start() + #end define + + def thread_process(self, func, **kwargs): + args = kwargs.get("args") + kwargs = kwargs.get("kwargs") + self.thread_result = func(*args, **kwargs) + #end define + + def snake_process(self): + snake_len = 0 + indent_len = 0 + cycles = int(self.timeout / self.sleep_time) + for cycle in range(cycles): + if self.thread.is_alive() == False: + break + sleep(self.sleep_time) + if indent_len == self.toolbar_width: + indent_len = 0 + elif indent_len == self.toolbar_width - snake_len: + snake_len -= 1 + indent_len += 1 + elif snake_len == self.snake_width: + indent_len += 1 + elif snake_len < self.snake_width: + snake_len += 1 + snake = indent_len * self.indent_symbol + snake_len * self.square_symbol + filling_len = self.toolbar_width - indent_len - snake_len + filling = self.indent_symbol * filling_len + eta = int(self.timeout - cycle * self.sleep_time) + eta_text = f" ETA <= {eta} seconds" + ending_len = self.tty_width - self.toolbar_width - 2 - len(eta_text) + ending = ending_len * self.indent_symbol + text = self.new_line_symbol + '[' + snake + filling + ']' + eta_text + ending + stdout.write(text) + #end for + + stdout.write(self.new_line_symbol + self.indent_symbol * self.tty_width) + stdout.write(self.new_line_symbol) + #end define +#end class diff --git a/translate.json b/mytonctrl/resources/translate.json similarity index 81% rename from translate.json rename to mytonctrl/resources/translate.json index e2aa23ed..7bbfcf1d 100644 --- a/translate.json +++ b/mytonctrl/resources/translate.json @@ -14,6 +14,36 @@ "ru": "Показать статус TON", "zh_TW": "顯示 TON 狀態" }, + "status_modes_cmd": { + "en": "Show MTC modes", + "ru": "Показать режимы MTC", + "zh_TW": "顯示 MTC 模式" + }, + "settings_status_cmd": { + "en": "Show all available settings with their description and values", + "ru": "Показать все доступные настройки с их описанием и значениями", + "zh_TW": "顯示所有可用設定及其描述和值" + }, + "about_cmd": { + "en": "Mode description", + "ru": "Описание режима", + "zh_TW": "模式描述" + }, + "enable_mode_cmd": { + "en": "Enable mode", + "ru": "Включить режим", + "zh_TW": "啟用模式" + }, + "disable_mode_cmd": { + "en": "Disable mode", + "ru": "Выключить режим", + "zh_TW": "禁用模式" + }, + "rollback_cmd" : { + "en": "Rollback to mytonctrl 1.0", + "ru": "Откатиться к mytonctrl 1.0", + "zh_TW": "回滾到 mytonctrl 1.0" + }, "seqno_cmd": { "en": "Get seqno wallet", "ru": "Получить seqno кошелька", @@ -194,6 +224,11 @@ "ru": "{cyan}===[ Статус сети TON ]==={endc}", "zh_TW": "{cyan}===[ TON 網路狀態 ]==={endc}" }, + "ton_status_network_name": { + "en": "Network name: {0}", + "ru": "Название сети: {0}", + "zh_TW": "網路名字: {0}" + }, "ton_status_tps": { "en": "Transactions per second (TPS): {0}, {1}, {2}", "ru": "Транзакций в секунду (TPS): {0}, {1}, {2}", @@ -239,11 +274,21 @@ "ru": "Эффективность валидатора: {0}", "zh_TW": "驗證者效率: {0}" }, + "local_status_validator_efficiency_ver2": { + "en": "Validator efficiency, ver.2: {0}", + "ru": "Эффективность валидатора, вер.2: {0}", + "zh_TW": "驗證器效率,版本2: {0}" + }, "local_status_adnl_addr": { "en": "ADNL address of local validator: {0}", "ru": "ADNL адрес локального валидатора: {0}", "zh_TW": "本地驗證者的 ADNL 地址: {0}" }, + "local_status_fullnode_adnl": { + "en": "Public ADNL address of node: {0}", + "ru": "Публичный ADNL адрес ноды: {0}", + "zh_TW": "節點的公共 ADNL 地址: {0}" + }, "local_status_wallet_addr": { "en": "Local validator wallet address: {0}", "ru": "Адрес кошелька локального валидатора: {0}", @@ -289,6 +334,11 @@ "ru": "Рассинхронизация локального валидатора: {0}", "zh_TW": "本地驗證者不同步: {0}" }, + "local_status_validator_out_of_ser": { + "en": "Local validator last state serialization: {0}", + "ru": "Серализация стейта локального валидатора была: {0}", + "zh_TW": "本地驗證者最後一次狀態序列化: {0}" + }, "local_status_db": { "en": "Local validator database size: {0}, {1}", "ru": "Размер БД локального валидатора: {0}, {1}", @@ -384,6 +434,26 @@ "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}" + }, + "sync_warning": { + "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}" + }, + "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}" + }, "vport_error": { "en": "{red}Error - UDP port of the validator is not accessible from the outside.{endc}", "ru": "{red}Ошибка - UDP порт валидатора недоступен извне.{endc}", diff --git a/mytonctrl/scripts/benchmark.sh b/mytonctrl/scripts/benchmark.sh new file mode 100644 index 00000000..f7f6c323 --- /dev/null +++ b/mytonctrl/scripts/benchmark.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -e + +# Проверить sudo +if [ "$(id -u)" != "0" ]; then + echo "Please run script as root" + exit 1 +fi + +file_path=/var/ton-work/db/test.img +db_path=/var/ton-work/db/bench + +function get_fio_json { + read_iops=$(echo "$1" | grep "read:" | awk '{print $2}' | awk -F '=' '{print $2}') + write_iops=$(echo "$1" | grep "write:" | awk '{print $2}' | awk -F '=' '{print $2}') + read_iops=$(echo "${read_iops//,/}") + write_iops=$(echo "${write_iops//,/}") + read_speed=$(echo "$1" | grep "read:" | awk '{print $3}' | awk -F '=' '{print $2}') + write_speed=$(echo "$1" | grep "write:" | awk '{print $3}' | awk -F '=' '{print $2}') + json=$(cat <<-END + { + "read_iops": "${read_iops}", + "write_iops": "${write_iops}", + "read_speed": "${read_speed}", + "write_speed": "${write_speed}" + } + END + ) + echo $json +} + +function get_rocksd_bench_json { + random_ops=$(echo "$1" | grep "randomtransaction" | awk '{print $5}') + json=$(cat <<-END + { + "random_ops": "${random_ops}" + } + END + ) + echo $json +} + +function print_json_result { + json=$(cat <<-END + { + "lite": ${lite_json_result}, + "hard": ${hard_json_result}, + "full": ${full_json_result} + } + END + ) + echo $json +} + +# https://superuser.com/questions/1049382/ssd-4k-random-read-write-qd1-32-and-iops-values +# lite +lite_result=$(fio --name=test --runtime=60 --readwrite=randrw --blocksize=4k --ioengine=libaio --direct=1 --size=4G --filename=${file_path} --rwmixread=75 --randrepeat=1 --gtod_reduce=1 --iodepth=64) +lite_json_result=$(get_fio_json "$lite_result") + +# hard +hard_result=$(fio --name=test --runtime=60 --readwrite=randrw --blocksize=4k --ioengine=libaio --direct=1 --size=4G --filename=${file_path} --rwmixread=75 --io_size=10g --fsync=1 --iodepth=1 --numjobs=1) +hard_json_result=$(get_fio_json "$hard_result") + +# full +full_result=$(/usr/bin/db_bench --benchmarks="randomtransaction" -max_background_flushes 2 max_background_compactions 4 -bytes_per_sync 1048576 -writable_file_max_buffer_size 32768 -duration 60 -threads 8 -db=${db_path} 2>/dev/null) +full_json_result=$(get_rocksd_bench_json "$full_result") + +# clear temp files +rm ${file_path} +rm -rf ${db_path} + +print_json_result diff --git a/mytonctrl/scripts/cleanup.sh b/mytonctrl/scripts/cleanup.sh new file mode 100644 index 00000000..d3d10a2e --- /dev/null +++ b/mytonctrl/scripts/cleanup.sh @@ -0,0 +1,35 @@ +#!/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/etabar.py b/mytonctrl/scripts/etabar.py new file mode 100644 index 00000000..57d7957c --- /dev/null +++ b/mytonctrl/scripts/etabar.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from sys import exit, argv +from subprocess import run, PIPE +from mytonctrl.progressbar import EtaBar + + +timeout = int(argv[1]) +script = argv[2] +result = argv[3] + +bar = EtaBar(timeout=timeout) +args = ["bash", script] +process = bar.run(run, args, stdin=PIPE, stdout=PIPE, stderr=PIPE, timeout=timeout) +exit_code = -1 +output = "process is None" +if process != None: + exit_code = process.returncode + stdout = process.stdout.decode("utf-8") + stderr = process.stderr.decode("utf-8") + if exit_code == 0: + output = stdout + else: + output = stderr +with open(result, 'wt') as file: + file.write(output) +exit(exit_code) diff --git a/scripts/update.sh b/mytonctrl/scripts/update.sh similarity index 76% rename from scripts/update.sh rename to mytonctrl/scripts/update.sh index 6c17a217..463d422c 100644 --- a/scripts/update.sh +++ b/mytonctrl/scripts/update.sh @@ -28,17 +28,20 @@ done COLOR='\033[92m' ENDC='\033[0m' -# Установка компонентов python3 -pip3 install fastcrc - # Go to work dir cd ${srcdir} + +# uninstall previous version rm -rf ${srcdir}/${repo} +pip3 uninstall -y mytonctrl # Update code echo "https://github.com/${author}/${repo}.git -> ${branch}" -git clone --recursive https://github.com/${author}/${repo}.git -cd ${repo} && git checkout ${branch} && git submodule update --init --recursive +git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git +cd ${repo} +pip3 install -U . + +systemctl daemon-reload systemctl restart mytoncore # Конец diff --git a/scripts/upgrade.sh b/mytonctrl/scripts/upgrade.sh similarity index 74% rename from scripts/upgrade.sh rename to mytonctrl/scripts/upgrade.sh index ca896434..7814f0aa 100644 --- a/scripts/upgrade.sh +++ b/mytonctrl/scripts/upgrade.sh @@ -29,7 +29,7 @@ COLOR='\033[92m' ENDC='\033[0m' # Установить дополнительные зависимости -apt-get install -y libsecp256k1-dev libsodium-dev ninja-build liblz4-dev libjemalloc-dev +apt-get install -y libsecp256k1-dev libsodium-dev ninja-build fio rocksdb-tools liblz4-dev libjemalloc-dev # bugfix if the files are in the wrong place wget "https://ton-blockchain.github.io/global.config.json" -O global.config.json @@ -42,48 +42,44 @@ else cp /usr/bin/ton/validator-engine-console/client.pub /var/ton-work/keys/client.pub cp /usr/bin/ton/validator-engine-console/server.pub /var/ton-work/keys/server.pub cp /usr/bin/ton/validator-engine-console/liteserver.pub /var/ton-work/keys/liteserver.pub + + # fix validator.service + sed -i 's/validator-engine\/ton-global.config.json/global.config.json/' /etc/systemd/system/validator.service + systemctl daemon-reload fi +# compile openssl_3 +rm -rf ${bindir}/openssl_3 +git clone https://github.com/openssl/openssl ${bindir}/openssl_3 +cd ${bindir}/openssl_3 +git checkout openssl-3.1.4 +./config +make build_libs -j$(nproc) +opensslPath=`pwd` + # Go to work dir cd ${srcdir} rm -rf ${srcdir}/${repo} # Update code echo "https://github.com/${author}/${repo}.git -> ${branch}" -git clone --recursive https://github.com/${author}/${repo}.git -cd ${repo} && git checkout ${branch} && git submodule update --init --recursive +git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git +cd ${repo} export CC=/usr/bin/clang export CXX=/usr/bin/clang++ export CCACHE_DISABLE=1 -cd ${bindir} -rm -rf openssl_3 -git clone https://github.com/openssl/openssl openssl_3 -cd openssl_3 -opensslPath=`pwd` -git checkout openssl-3.1.4 -./config -make build_libs -j12 - # Update binary cd ${bindir}/${repo} -ls --hide=global.config.json --hide=local.config.json | xargs -d '\n' rm -rf +ls --hide=global.config.json | xargs -d '\n' rm -rf rm -rf .ninja_* memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') -let "cpuNumber = memory / 2100000" || cpuNumber=1 +cpuNumber=$(cat /proc/cpuinfo | grep "processor" | wc -l) cmake -DCMAKE_BUILD_TYPE=Release ${srcdir}/${repo} -GNinja -DTON_USE_JEMALLOC=ON -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=$opensslPath/include -DOPENSSL_CRYPTO_LIBRARY=$opensslPath/libcrypto.a ninja -j ${cpuNumber} fift validator-engine lite-client pow-miner validator-engine-console generate-random-id dht-server func tonlibjson rldp-http-proxy systemctl restart validator -if [ -e /usr/src/mytonctrl/scripts/set_state_ttl.py ] -then - /usr/bin/python3 /usr/src/mytonctrl/scripts/set_state_ttl.py -else - echo "Set state ttl script is not found!" -fi - - # Конец echo -e "${COLOR}[1/1]${ENDC} TON components update completed" exit 0 diff --git a/mytonctrl/scripts/validator-desync.sh b/mytonctrl/scripts/validator-desync.sh new file mode 100644 index 00000000..898f527f --- /dev/null +++ b/mytonctrl/scripts/validator-desync.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +stats=$(validator-console -c getstats) +unixtime=$(echo "$stats" | grep unixtime | awk '{print $2}') +mastertime=$(echo "$stats" | grep masterchainblocktime | awk '{print $2}') +if [ -z $mastertime ] +then + result="mastertime is None" +else + result=$(($unixtime-$mastertime)) +fi + +echo $result diff --git a/scripts/xrestart.py b/mytonctrl/scripts/xrestart.py similarity index 99% rename from scripts/xrestart.py rename to mytonctrl/scripts/xrestart.py index b367eb1f..797835a6 100644 --- a/scripts/xrestart.py +++ b/mytonctrl/scripts/xrestart.py @@ -70,4 +70,4 @@ def result(): if __name__ == "__main__": Xguard() -#end if \ No newline at end of file +#end if diff --git a/mytonctrl/utils.py b/mytonctrl/utils.py new file mode 100644 index 00000000..ba6cd105 --- /dev/null +++ b/mytonctrl/utils.py @@ -0,0 +1,31 @@ +import subprocess +import time + + +def timestamp2utcdatetime(timestamp, format="%d.%m.%Y %H:%M:%S"): + datetime = time.gmtime(timestamp) + result = time.strftime(format, datetime) + ' UTC' + return result + + +def GetItemFromList(data, index): + try: + return data[index] + except: + pass + + +def fix_git_config(git_path: str): + args = ["git", "status"] + try: + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=git_path, timeout=3) + err = process.stderr.decode("utf-8") + except Exception as e: + err = str(e) + if err: + if 'git config --global --add safe.directory' in err: + args = ["git", "config", "--global", "--add", "safe.directory", git_path] + subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) + else: + raise Exception(f'Failed to check git status: {err}') +#end define diff --git a/mytoninstaller/__init__.py b/mytoninstaller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mytoninstaller/__main__.py b/mytoninstaller/__main__.py new file mode 100644 index 00000000..78efe3f2 --- /dev/null +++ b/mytoninstaller/__main__.py @@ -0,0 +1,5 @@ +from mytoninstaller.mytoninstaller import mytoninstaller + + +if __name__ == '__main__': + mytoninstaller() diff --git a/mytoninstaller/config.py b/mytoninstaller/config.py new file mode 100644 index 00000000..83039853 --- /dev/null +++ b/mytoninstaller/config.py @@ -0,0 +1,166 @@ +import os +import json +import re +import subprocess +import requests +import base64 + +from mytoncore.utils import hex2b64, dict2b64 +from mytoninstaller.utils import StartMytoncore, GetInitBlock, get_ed25519_pubkey_text +from mypylib.mypylib import ip2int, Dict + + +defaultLocalConfigPath = "/usr/bin/ton/local.config.json" + + +def GetConfig(**kwargs): + path = kwargs.get("path") + file = open(path, 'rt') + text = file.read() + file.close() + config = Dict(json.loads(text)) + return config +#end define + + +def SetConfig(**kwargs): + path = kwargs.get("path") + data = kwargs.get("data") + + # write config + text = json.dumps(data, indent=4) + file = open(path, 'wt') + file.write(text) + file.close() +#end define + + +def backup_config(local, config_path): + backup_path = f"{config_path}.backup" + local.add_log(f"Backup config file '{config_path}' to '{backup_path}'", "debug") + args = ["cp", config_path, backup_path] + subprocess.run(args) +#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 + backupPath = mconfig_path + ".backup" + args = ["cp", mconfig_path, backupPath] + subprocess.run(args) +#end define + + +def GetPortsFromVconfig(local): + vconfig_path = local.buffer.vconfig_path + + # read vconfig + local.add_log("read vconfig", "debug") + vconfig = GetConfig(path=vconfig_path) + + # read mconfig + local.add_log("read mconfig", "debug") + mconfig_path = local.buffer.mconfig_path + mconfig = GetConfig(path=mconfig_path) + + # edit mytoncore config file + local.add_log("edit mytoncore config file", "debug") + mconfig.liteClient.liteServer.port = mconfig.liteservers[0].port + mconfig.validatorConsole.addr = f"127.0.0.1:{mconfig.control[0].port}" + + # write mconfig + local.add_log("write mconfig", "debug") + SetConfig(path=mconfig_path, data=mconfig) + + # restart mytoncore + StartMytoncore(local) +#end define + + +def CreateLocalConfig(local, initBlock, localConfigPath=defaultLocalConfigPath): + # dirty hack, but GetInitBlock() function uses the same technique + from mytoncore import hex2base64 + + # read global config file + file = open("/usr/bin/ton/global.config.json", 'rt') + text = file.read() + data = json.loads(text) + file.close() + + # edit config + liteServerConfig = GetLiteServerConfig(local) + data["liteservers"] = [liteServerConfig] + data["validator"]["init_block"]["seqno"] = initBlock["seqno"] + data["validator"]["init_block"]["root_hash"] = hex2base64(initBlock["rootHash"]) + data["validator"]["init_block"]["file_hash"] = hex2base64(initBlock["fileHash"]) + text = json.dumps(data, indent=4) + + # write local config file + file = open(localConfigPath, 'wt') + file.write(text) + file.close() + + # chown + user = local.buffer.user + args = ["chown", "-R", user + ':' + user, localConfigPath] + + print("Local config file created:", localConfigPath) +#end define + + +def get_own_ip(): + pat = re.compile(r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$") + requests.packages.urllib3.util.connection.HAS_IPV6 = False + ip = requests.get("https://ifconfig.me/ip").text + if not pat.fullmatch(ip): + ip = requests.get("https://ipinfo.io/ip").text + if not pat.fullmatch(ip): + raise Exception('Cannot get own IP address') + return ip +#end define + + +def GetLiteServerConfig(local): + keys_dir = local.buffer.keys_dir + liteserver_key = keys_dir + "liteserver" + liteserver_pubkey = liteserver_key + ".pub" + result = Dict() + file = open(liteserver_pubkey, 'rb') + data = file.read() + file.close() + key = base64.b64encode(data[4:]) + ip = get_own_ip() + mconfig = GetConfig(path=local.buffer.mconfig_path) + result.ip = ip2int(ip) + result.port = mconfig.liteClient.liteServer.port + result.id = Dict() + result.id["@type"]= "pub.ed25519" + result.id.key= key.decode() + return result +#end define + +def get_ls_proxy_config(local): + ls_proxy_config_path = "/var/ls_proxy/ls-proxy-config.json" + ls_proxy_config = GetConfig(path=ls_proxy_config_path) + ip = get_own_ip() + port = ls_proxy_config.ListenAddr.split(':')[1] + privkey_text = ls_proxy_config.Clients[0].PrivateKey + + result = Dict() + result.ip = ip2int(ip) + result.port = port + result.id = Dict() + result.id["@type"]= "pub.ed25519" + result.id.key= get_ed25519_pubkey_text(privkey_text) + return result +#end define diff --git a/mytoninstaller/mytoninstaller.py b/mytoninstaller/mytoninstaller.py new file mode 100644 index 00000000..160b9210 --- /dev/null +++ b/mytoninstaller/mytoninstaller.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# -*- coding: utf_8 -*- + +import os, sys +import inspect +import random +import json +import subprocess + +import pkg_resources + +from mypylib.mypylib import MyPyClass, run_as_root, color_print +from mypyconsole.mypyconsole import MyPyConsole + +from mytoninstaller.config import GetLiteServerConfig, get_ls_proxy_config +from mytoninstaller.node_args import get_node_args +from mytoninstaller.utils import GetInitBlock +from mytoncore.utils import dict2b64, str2bool, b642dict + +from mytoninstaller.settings import ( + FirstNodeSettings, + FirstMytoncoreSettings, + EnableValidatorConsole, + EnableLiteServer, + EnableDhtServer, + EnableJsonRpc, + EnableTonHttpApi, + DangerousRecoveryValidatorConfigFile, + CreateSymlinks, + enable_ls_proxy, + enable_ton_storage, + enable_ton_storage_provider, + EnableMode +) +from mytoninstaller.config import ( + CreateLocalConfig, + BackupVconfig, + BackupMconfig, +) + +from functools import partial + + +def Init(local, console): + local.db.config.isStartOnlyOneProcess = False + local.db.config.logLevel = "debug" + local.db.config.isIgnorLogWarning = True # disable warning + local.run() + local.db.config.isIgnorLogWarning = False # enable warning + + + # create variables + user = os.environ.get("USER", "root") + local.buffer.user = user + local.buffer.vuser = "validator" + local.buffer.cport = random.randint(2000, 65000) + local.buffer.lport = random.randint(2000, 65000) + + # this funciton injects MyPyClass instance + def inject_globals(func): + args = [] + for arg_name in inspect.getfullargspec(func)[0]: + if arg_name == 'local': + args.append(local) + return partial(func, *args) + + # Create user console + console.name = "MyTonInstaller" + console.color = console.RED + console.AddItem("status", inject_globals(Status), "Print TON component status") + console.AddItem("set_node_argument", inject_globals(set_node_argument), "Set node argument") + console.AddItem("enable", inject_globals(Enable), "Enable some function") + console.AddItem("update", inject_globals(Enable), "Update some function: 'JR' - jsonrpc. Example: 'update JR'") + console.AddItem("plsc", inject_globals(PrintLiteServerConfig), "Print lite-server config") + console.AddItem("clcf", inject_globals(CreateLocalConfigFile), "Create lite-server config file") + console.AddItem("print_ls_proxy_config", inject_globals(print_ls_proxy_config), "Print ls-proxy config") + console.AddItem("create_ls_proxy_config_file", inject_globals(create_ls_proxy_config_file), "Create ls-proxy config file") + console.AddItem("drvcf", inject_globals(DRVCF), "Dangerous recovery validator config file") + console.AddItem("setwebpass", inject_globals(SetWebPassword), "Set a password for the web admin interface") + + Refresh(local) +#end define + + +def Refresh(local): + user = local.buffer.user + local.buffer.mconfig_path = "/home/{user}/.local/share/mytoncore/mytoncore.db".format(user=user) + if user == 'root': + local.buffer.mconfig_path = "/usr/local/bin/mytoncore/mytoncore.db" + #end if + + # create variables + bin_dir = "/usr/bin/" + src_dir = "/usr/src/" + ton_work_dir = "/var/ton-work/" + ton_bin_dir = bin_dir + "ton/" + ton_src_dir = src_dir + "ton/" + local.buffer.bin_dir = bin_dir + local.buffer.src_dir = src_dir + local.buffer.ton_work_dir = ton_work_dir + local.buffer.ton_bin_dir = ton_bin_dir + local.buffer.ton_src_dir = ton_src_dir + ton_db_dir = ton_work_dir + "db/" + keys_dir = ton_work_dir + "keys/" + local.buffer.ton_db_dir = ton_db_dir + local.buffer.keys_dir = keys_dir + local.buffer.ton_log_path = ton_work_dir + "log" + local.buffer.validator_app_path = ton_bin_dir + "validator-engine/validator-engine" + local.buffer.global_config_path = ton_bin_dir + "global.config.json" + local.buffer.vconfig_path = ton_db_dir + "config.json" +#end define + + +def Status(local, args): + keys_dir = local.buffer.keys_dir + server_key = keys_dir + "server" + client_key = keys_dir + "client" + liteserver_key = keys_dir + "liteserver" + liteserver_pubkey = liteserver_key + ".pub" + + statuses = { + 'Full node status': os.path.isfile(local.buffer.vconfig_path), + 'Mytoncore status': os.path.isfile(local.buffer.mconfig_path), + 'V.console status': os.path.isfile(server_key) or os.path.isfile(client_key), + 'Liteserver status': os.path.isfile(liteserver_pubkey) + } + + color_print("{cyan}===[ Services status ]==={endc}") + for item in statuses.items(): + status = '{green}enabled{endc}' if item[1] else '{red}disabled{endc}' + color_print(f"{item[0]}: {status}") + + node_args = get_node_args() + color_print("{cyan}===[ Node arguments ]==={endc}") + for key, value in node_args.items(): + print(f"{key}: {value}") +#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)]") + return + arg_name = args[0] + args = [arg_name, args[1] if len(args) > 1 else ""] + 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}") +#end define + + +def Enable(local, args): + try: + name = args[0] + except: + color_print("{red}Bad args. Usage:{endc} enable ") + print("'FN' - Full node") + print("'VC' - Validator console") + print("'LS' - Lite-Server") + print("'DS' - DHT-Server") + print("'JR' - jsonrpc") + print("'THA' - ton-http-api") + print("'LSP' - ls-proxy") + print("'TSP' - ton-storage + ton-storage-provider") + print("Example: 'enable FN'") + return + if name == "THA": + CreateLocalConfigFile(local, args) + args = ["python3", "-m", "mytoninstaller", "-u", local.buffer.user, "-e", f"enable{name}"] + run_as_root(args) +#end define + + +def DRVCF(local, args): + user = local.buffer["user"] + args = ["python3", "-m", "mytoninstaller", "-u", local.buffer.user, "-e", "drvcf"] + run_as_root(args) +#end define + + +def SetWebPassword(args): + args = ["python3", "/usr/src/mtc-jsonrpc/mtc-jsonrpc.py", "-p"] + subprocess.run(args) +#end define + + +def PrintLiteServerConfig(local, args): + liteServerConfig = GetLiteServerConfig(local) + text = json.dumps(liteServerConfig, indent=4) + print(text) +#end define + + +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] + run_as_root(args) +#end define + +def print_ls_proxy_config(local, args): + ls_proxy_config = get_ls_proxy_config(local) + text = json.dumps(ls_proxy_config, indent=4) + print(text) +#end define + +def create_ls_proxy_config_file(local, args): + print("TODO") +#end define + +def Event(local, name): + if name == "enableFN": + FirstNodeSettings(local) + if name == "enableVC": + EnableValidatorConsole(local) + if name == "enableLS": + EnableLiteServer(local) + if name == "enableDS": + EnableDhtServer(local) + if name == "drvcf": + DangerousRecoveryValidatorConfigFile(local) + if name == "enableJR": + EnableJsonRpc(local) + if name == "enableTHA": + EnableTonHttpApi(local) + if name == "enableLSP": + enable_ls_proxy(local) + if name == "enableTSP": + enable_ton_storage(local) + enable_ton_storage_provider(local) + if name == "clc": + ix = sys.argv.index("-i") + initBlock_b64 = sys.argv[ix+1] + initBlock = b642dict(initBlock_b64) + CreateLocalConfig(local, initBlock) + local.exit() +#end define + + +def Command(local, args, console): + cmd = args[0] + args = args[1:] + for item in console.menu_items: + if cmd == item.cmd: + console._try(item.func, args) + print() + local.exit() + print(console.unknown_cmd) + local.exit() +#end define + + +def General(local, console): + if "-u" in sys.argv: + ux = sys.argv.index("-u") + user = sys.argv[ux+1] + local.buffer.user = user + Refresh(local) + if "-c" in sys.argv: + cx = sys.argv.index("-c") + args = sys.argv[cx+1:] + Command(local, args, console) + if "-e" in sys.argv: + ex = sys.argv.index("-e") + name = sys.argv[ex+1] + Event(local, name) + if "-t" in sys.argv: + mx = sys.argv.index("-t") + telemetry = sys.argv[mx+1] + local.buffer.telemetry = str2bool(telemetry) + if "--dump" in sys.argv: + mx = sys.argv.index("--dump") + dump = sys.argv[mx+1] + local.buffer.dump = str2bool(dump) + if "-m" in sys.argv: + mx = sys.argv.index("-m") + mode = sys.argv[mx+1] + local.buffer.mode = mode + #end if + + FirstMytoncoreSettings(local) + FirstNodeSettings(local) + EnableValidatorConsole(local) + EnableLiteServer(local) + BackupVconfig(local) + BackupMconfig(local) + CreateSymlinks(local) + EnableMode(local) +#end define + + +### +### Start of the program +### +def mytoninstaller(): + local = MyPyClass(__file__) + console = MyPyConsole() + + Init(local, console) + if len(sys.argv) > 1: + General(local, console) + else: + console.Run() + local.exit() diff --git a/mytoninstaller/node_args.py b/mytoninstaller/node_args.py new file mode 100644 index 00000000..ec99eea3 --- /dev/null +++ b/mytoninstaller/node_args.py @@ -0,0 +1,35 @@ + + +def get_validator_service(): + path = '/etc/systemd/system/validator.service' + with open(path, 'r') as f: + return f.read() +#end define + + +def get_node_start_command(): + service = get_validator_service() + for line in service.split('\n'): + if 'ExecStart' in line: + return line.split('=')[1].strip() +#end define + + +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] = '' + return result +#end define + diff --git a/mytoninstaller/scripts/__init__.py b/mytoninstaller/scripts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mytoninstaller/scripts/add2systemd.sh b/mytoninstaller/scripts/add2systemd.sh new file mode 100755 index 00000000..13626522 --- /dev/null +++ b/mytoninstaller/scripts/add2systemd.sh @@ -0,0 +1,62 @@ +#!/bin/bash +set -e + +# Проверить sudo +if [ "$(id -u)" != "0" ]; then + echo "Please run script as root" + exit 1 +fi + +post="/bin/echo service down" +user=root +group=root + +while getopts n:s:p:u:g: flag +do + case "${flag}" in + n) name=${OPTARG};; + s) start=${OPTARG};; + p) post=${OPTARG};; + u) user=${OPTARG};; + g) group=${OPTARG};; + esac +done + +if [ -z "$name" ]; then + echo "name is empty" + exit 1 +fi + +if [ -z "$start" ]; then + echo "start is empty" + exit 1 +fi + + +DAEMON_PATH="/etc/systemd/system/${name}.service" + +cat < $DAEMON_PATH +[Unit] +Description = $name service. Created by https://github.com/igroman787/mypylib. +After = network.target + +[Service] +Type = simple +Restart = always +RestartSec = 30 +ExecStart = $start +ExecStopPost = $post +User = $user +Group = $group +LimitNOFILE = infinity +LimitNPROC = infinity +LimitMEMLOCK = infinity + +[Install] +WantedBy = multi-user.target +EOF + +chmod 664 $DAEMON_PATH +chmod +x $DAEMON_PATH +systemctl daemon-reload +systemctl enable ${name} diff --git a/scripts/jsonrpcinstaller.sh b/mytoninstaller/scripts/jsonrpcinstaller.sh old mode 100644 new mode 100755 similarity index 62% rename from scripts/jsonrpcinstaller.sh rename to mytoninstaller/scripts/jsonrpcinstaller.sh index a401eca3..e6338480 --- a/scripts/jsonrpcinstaller.sh +++ b/mytoninstaller/scripts/jsonrpcinstaller.sh @@ -15,6 +15,13 @@ do esac done +author=kdimentionaltree +repo=mtc-jsonrpc +branch=master + +echo "User: $user" +echo "Workdir: `pwd`" + # Цвета COLOR='\033[95m' ENDC='\033[0m' @@ -25,14 +32,17 @@ pip3 install Werkzeug json-rpc cloudscraper pyotp # Клонирование репозиториев с github.com echo -e "${COLOR}[2/4]${ENDC} Cloning github repository" +echo "https://github.com/${author}/${repo}.git -> ${branch}" + cd /usr/src/ rm -rf mtc-jsonrpc -git clone --recursive https://github.com/igroman787/mtc-jsonrpc.git +git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git # Прописать автозагрузку echo -e "${COLOR}[3/4]${ENDC} Add to startup" -cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import *; Add2Systemd(name='mtc-jsonrpc', user='${user}', start='/usr/bin/python3 /usr/src/mtc-jsonrpc/mtc-jsonrpc.py')" -python3 -c "${cmd}" +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +echo "Script dir: ${SCRIPT_DIR}" +${SCRIPT_DIR}/add2systemd.sh -n mtc-jsonrpc -s "/usr/bin/python3 /usr/src/mtc-jsonrpc/mtc-jsonrpc.py" -u ${user} -g ${user} systemctl restart mtc-jsonrpc # Выход из программы diff --git a/mytoninstaller/scripts/ls_proxy_installer.sh b/mytoninstaller/scripts/ls_proxy_installer.sh new file mode 100644 index 00000000..b20d6333 --- /dev/null +++ b/mytoninstaller/scripts/ls_proxy_installer.sh @@ -0,0 +1,86 @@ +#!/bin/bash +set -e + +# import functions: check_superuser, get_cpu_number, check_go_version +my_dir=$(dirname $(realpath ${0})) +. ${my_dir}/utils.sh + +# Проверить sudo +check_superuser + +# install parameters +src_path=/usr/src +bin_path=/usr/bin +openssl_path=${bin_path}/openssl_3 + +# Get arguments +while getopts u:s:b:o: flag +do + case "${flag}" in + u) user=${OPTARG};; + s) src_path=${OPTARG};; + b) bin_path=${OPTARG};; + o) openssl_path=${OPTARG};; + *) + echo "Flag -${flag} is not recognized. Aborting" + exit 1;; + esac +done + +# install parameters +author=xssnick +repo=tonutils-liteserver-proxy +branch=master +bin_name=ls_proxy + +# Цвета +COLOR='\033[95m' +ENDC='\033[0m' + +# Клонирование репозиториев с github.com +echo -e "${COLOR}[1/4]${ENDC} Cloning github repository" +echo "https://github.com/${author}/${repo}.git -> ${branch}" + + +package_src_path=${src_path}/${repo} +rm -rf ${package_src_path} + +cd ${src_path} +git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git + +# Установка компонентов +echo -e "${COLOR}[2/4]${ENDC} Installing required packages" +go_path=/usr/local/go/bin/go +check_go_version "${package_src_path}/go.mod" ${go_path} + +# Компилируем из исходников +cpu_number=$(get_cpu_number) +echo -e "${COLOR}[3/4]${ENDC} Source compilation, use ${cpu_number} cpus" + +ton_src_path=${package_src_path}/ton +proxy_internal_path=${package_src_path}/internal/emulate/lib + +proxy_build_path=${bin_path}/${bin_name} +ton_build_path=${proxy_build_path}/ton +db_path=/var/${bin_name} +lib_path=${db_path}/lib + +mkdir -p ${lib_path} +mkdir -p ${ton_build_path} && cd ${ton_build_path} +cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=${openssl_path}/include -DOPENSSL_CRYPTO_LIBRARY=${openssl_path}/libcrypto.a ${ton_src_path} +make emulator -j ${cpu_number} +cp ${ton_build_path}/emulator/libemulator.so ${lib_path} +cp ${ton_build_path}/emulator/libemulator.so ${proxy_internal_path} +cp ${ton_build_path}/emulator/emulator_export.h ${proxy_internal_path} + +# Компилируем +cd ${package_src_path} +entry_point=$(find ${package_src_path} -name "main.go" | head -n 1) +CGO_ENABLED=1 ${go_path} build -o ${db_path}/${bin_name} ${entry_point} + +# Настроить директорию работы +chown -R ${user}:${user} ${db_path} + +# Выход из программы +echo -e "${COLOR}[4/4]${ENDC} ${bin_name} installation complete" +exit 0 \ No newline at end of file diff --git a/scripts/pytonv3installer.sh b/mytoninstaller/scripts/pytonv3installer.sh old mode 100644 new mode 100755 similarity index 76% rename from scripts/pytonv3installer.sh rename to mytoninstaller/scripts/pytonv3installer.sh index 0eb8d730..32010a4c --- a/scripts/pytonv3installer.sh +++ b/mytoninstaller/scripts/pytonv3installer.sh @@ -39,8 +39,9 @@ cd /usr/bin/ton && make tonlibjson # Прописать автозагрузку echo -e "${COLOR}[3/4]${ENDC} Add to startup" -cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import *; Add2Systemd(name='pytonv3', user='${user}', workdir='/usr/src/pytonv3', start='/usr/bin/python3 -m pyTON --liteserverconfig /usr/bin/ton/local.config.json --libtonlibjson /usr/bin/ton/tonlib/libtonlibjson.so')" -python3 -c "${cmd}" +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +echo "Script dir: ${SCRIPT_DIR}" +${SCRIPT_DIR}/add2systemd -n pytonv3 -s "/usr/bin/python3 -m pyTON --liteserverconfig /usr/bin/ton/local.config.json --libtonlibjson /usr/bin/ton/tonlib/libtonlibjson.so" -u ${user} -g ${user} systemctl restart pytonv3 # Конец diff --git a/mytoninstaller/scripts/set_node_argument.py b/mytoninstaller/scripts/set_node_argument.py new file mode 100644 index 00000000..19fe67a5 --- /dev/null +++ b/mytoninstaller/scripts/set_node_argument.py @@ -0,0 +1,45 @@ +import sys +import subprocess +from mytoninstaller.node_args import get_node_args, get_node_start_command, get_validator_service + + +def set_node_arg(arg_name: str, arg_value: str = ''): + """ + :param arg_name: + :param arg_value: arg value. if None, remove the arg; if empty string, argument is set without value + :return: + """ + 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: + raise Exception('Cannot find node start command in service file') + args = get_node_args(command) + if arg_value == '-d': + 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) + restart_node() +#end define + + +def restart_node(): + exit_code = subprocess.run(["systemctl", "daemon-reload"]).returncode + if exit_code: + raise Exception(f"`systemctl daemon-reload` failed with exit code {exit_code}") + exit_code = subprocess.run(["systemctl", "restart", "validator"]).returncode + if exit_code: + raise Exception(f"`systemctl restart validator` failed with exit code {exit_code}") +#end define + + +if __name__ == '__main__': + name = sys.argv[1] + value = sys.argv[2] if len(sys.argv) > 2 else '' + set_node_arg(name, value) diff --git a/mytoninstaller/scripts/ton_storage_installer.sh b/mytoninstaller/scripts/ton_storage_installer.sh new file mode 100644 index 00000000..262c1bee --- /dev/null +++ b/mytoninstaller/scripts/ton_storage_installer.sh @@ -0,0 +1,70 @@ +#!/bin/bash +set -e + +# import functions: check_superuser, check_go_version +my_dir=$(dirname $(realpath ${0})) +. ${my_dir}/utils.sh + +# Проверить sudo +check_superuser + +# install parameters +src_path=/usr/src +bin_path=/usr/bin + +# Get arguments +while getopts u:s:b: flag +do + case "${flag}" in + u) user=${OPTARG};; + s) src_path=${OPTARG};; + b) bin_path=${OPTARG};; + *) + echo "Flag -${flag} is not recognized. Aborting" + exit 1;; + esac +done + +# install parameters +author=xssnick +repo=tonutils-storage +branch=master +bin_name=ton_storage + +# Цвета +COLOR='\033[95m' +ENDC='\033[0m' + +# Клонирование репозиториев с github.com +echo -e "${COLOR}[1/4]${ENDC} Cloning github repository" +echo "https://github.com/${author}/${repo}.git -> ${branch}" + +package_src_path="${src_path}/${repo}" +rm -rf ${package_src_path} + +cd ${src_path} +git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git + +# Установка компонентов +echo -e "${COLOR}[2/4]${ENDC} Installing required packages" +go_path=/usr/local/go/bin/go +check_go_version "${package_src_path}/go.mod" ${go_path} + +# Компилируем из исходников +echo -e "${COLOR}[3/4]${ENDC} Source compilation" + +# Создать директорию работы +db_path="/var/${bin_name}" +mkdir -p ${db_path} + +# Компилируем +cd ${package_src_path} +#entry_point=$(find ${package_src_path} -name "main.go" | head -n 1) +CGO_ENABLED=1 ${go_path} build -o ${db_path}/${bin_name} ${package_src_path}/cli/main.go + +# Настроить директорию работы +chown -R ${user}:${user} ${db_path} + +# Выход из программы +echo -e "${COLOR}[4/4]${ENDC} ${bin_name} installation complete" +exit 0 diff --git a/mytoninstaller/scripts/ton_storage_provider_installer.sh b/mytoninstaller/scripts/ton_storage_provider_installer.sh new file mode 100644 index 00000000..1d4ac8cf --- /dev/null +++ b/mytoninstaller/scripts/ton_storage_provider_installer.sh @@ -0,0 +1,70 @@ +#!/bin/bash +set -e + +# import functions: check_superuser, check_go_version +my_dir=$(dirname $(realpath ${0})) +. ${my_dir}/utils.sh + +# Проверить sudo +check_superuser + +# install parameters +src_path=/usr/src +bin_path=/usr/bin + +# Get arguments +while getopts u:s:b: flag +do + case "${flag}" in + u) user=${OPTARG};; + s) src_path=${OPTARG};; + b) bin_path=${OPTARG};; + *) + echo "Flag -${flag} is not recognized. Aborting" + exit 1;; + esac +done + +# install parameters +author=xssnick +repo=tonutils-storage-provider +branch=master +bin_name=ton_storage_provider + +# Цвета +COLOR='\033[95m' +ENDC='\033[0m' + +# Клонирование репозиториев с github.com +echo -e "${COLOR}[1/4]${ENDC} Cloning github repository" +echo "https://github.com/${author}/${repo}.git -> ${branch}" + +package_src_path="${src_path}/${repo}" +rm -rf ${package_src_path} + +cd ${src_path} +git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git + +# Установка компонентов +echo -e "${COLOR}[2/4]${ENDC} Installing required packages" +go_path=/usr/local/go/bin/go +check_go_version "${package_src_path}/go.mod" ${go_path} + +# Компилируем из исходников +echo -e "${COLOR}[3/4]${ENDC} Source compilation" + +# Создать директорию работы +db_path="/var/${bin_name}" +mkdir -p ${db_path} + +# Компилируем +cd ${package_src_path} +#entry_point=$(find ${package_src_path} -name "main.go" | head -n 1) +CGO_ENABLED=1 ${go_path} build -o ${db_path}/${bin_name} ${package_src_path}/cmd/main.go + +# Настроить директорию работы +chown -R ${user}:${user} ${db_path} + +# Выход из программы +echo -e "${COLOR}[4/4]${ENDC} ${bin_name} installation complete" +exit 0 diff --git a/mytoninstaller/scripts/tonhttpapiinstaller.sh b/mytoninstaller/scripts/tonhttpapiinstaller.sh new file mode 100755 index 00000000..c22b98e0 --- /dev/null +++ b/mytoninstaller/scripts/tonhttpapiinstaller.sh @@ -0,0 +1,38 @@ +#!/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/scripts/utils.sh b/mytoninstaller/scripts/utils.sh new file mode 100644 index 00000000..c304039c --- /dev/null +++ b/mytoninstaller/scripts/utils.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +check_superuser() { + if [ "$(id -u)" != "0" ]; then + echo "Please run script as root" + exit 1 + fi +} + +get_cpu_number() { + if [[ "$OSTYPE" =~ darwin.* ]]; then + cpu_number=$(sysctl -n hw.logicalcpu) + else + memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') + cpu_number=$(($memory/2100000)) + max_cpu_number=$(nproc) + if [ ${cpu_number} -gt ${max_cpu_number} ]; then + cpu_number=$((${max_cpu_number}-1)) + fi + if [ ${cpu_number} == 0 ]; then + echo "Warning! insufficient RAM" + cpu_number=1 + fi + fi + echo ${cpu_number} +} + +check_go_version() { + go_mod_path=${1} + go_path=${2} + go_mod_text=$(cat ${go_mod_path}) || exit 1 + need_version_text=$(echo "${go_mod_text}" | grep "go " | head -n 1 | awk '{print $2}') + current_version_text=$(${go_path} version | awk '{print $3}' | sed 's\go\\g') + echo "start check_go_version function, need_version: ${need_version_text}, current_version: ${current_version_text}" + current_version_1=$(echo ${current_version_text} | cut -d "." -f 1) + current_version_2=$(echo ${current_version_text} | cut -d "." -f 2) + current_version_3=$(echo ${current_version_text} | cut -d "." -f 3) + need_version_1=$(echo ${need_version_text} | cut -d "." -f 1) + need_version_2=$(echo ${need_version_text} | cut -d "." -f 2) + need_version_3=$(echo ${need_version_text} | cut -d "." -f 3) + if (( need_version_1 > current_version_1 )) || ((need_version_2 > current_version_2 )) || ((need_version_3 > current_version_3 )); then + install_go + fi +} + +install_go() { + echo "start install_go function" + arc=$(dpkg --print-architecture) + go_version_url=https://go.dev/VERSION?m=text + go_version=$(curl -s ${go_version_url} | head -n 1) + go_url=https://go.dev/dl/${go_version}.linux-${arc}.tar.gz + rm -rf /usr/local/go + wget -c ${go_url} -O - | tar -C /usr/local -xz +} diff --git a/mytoninstaller.py b/mytoninstaller/settings.py similarity index 58% rename from mytoninstaller.py rename to mytoninstaller/settings.py index b14fb194..d04c5673 100644 --- a/mytoninstaller.py +++ b/mytoninstaller/settings.py @@ -1,249 +1,27 @@ -#!/usr/bin/env python3 -# -*- coding: utf_8 -*- - -import pwd -import random +import os +import os.path +import psutil +import base64 +import subprocess import requests -from mypylib.mypylib import * -from mypyconsole.mypyconsole import * - -local = MyPyClass(__file__) -console = MyPyConsole() -defaultLocalConfigPath = "/usr/bin/ton/local.config.json" - - -def Init(): - local.db.config.isStartOnlyOneProcess = False - local.db.config.logLevel = "debug" - local.db.config.isIgnorLogWarning = True # disable warning - local.run() - local.db.config.isIgnorLogWarning = False # enable warning - - - # create variables - user = os.environ.get("USER", "root") - local.buffer.user = user - local.buffer.vuser = "validator" - local.buffer.cport = random.randint(2000, 65000) - local.buffer.lport = random.randint(2000, 65000) - - # Create user console - console.name = "MyTonInstaller" - console.color = console.RED - console.AddItem("status", Status, "Print TON component status") - console.AddItem("enable", Enable, "Enable some function: 'FN' - Full node, 'VC' - Validator console, 'LS' - Liteserver, 'DS' - DHT-Server, 'JR' - jsonrpc, 'PT' - pyTONv3. Example: 'enable FN'") - console.AddItem("update", Enable, "Update some function: 'JR' - jsonrpc. Example: 'update JR'") - console.AddItem("plsc", PrintLiteServerConfig, "Print LiteServer config") - console.AddItem("clcf", CreateLocalConfigFile, "CreateLocalConfigFile") - console.AddItem("drvcf", DRVCF, "Dangerous recovery validator config file") - console.AddItem("setwebpass", SetWebPassword, "Set a password for the web admin interface") - - Refresh() -#end define - -def Refresh(): - user = local.buffer.user - local.buffer.mconfig_path = "/home/{user}/.local/share/mytoncore/mytoncore.db".format(user=user) - if user == 'root': - local.buffer.mconfig_path = "/usr/local/bin/mytoncore/mytoncore.db" - #end if - - # create variables - bin_dir = "/usr/bin/" - src_dir = "/usr/src/" - ton_work_dir = "/var/ton-work/" - ton_bin_dir = bin_dir + "ton/" - ton_src_dir = src_dir + "ton/" - local.buffer.bin_dir = bin_dir - local.buffer.src_dir = src_dir - local.buffer.ton_work_dir = ton_work_dir - local.buffer.ton_bin_dir = ton_bin_dir - local.buffer.ton_src_dir = ton_src_dir - ton_db_dir = ton_work_dir + "db/" - keys_dir = ton_work_dir + "keys/" - local.buffer.ton_db_dir = ton_db_dir - local.buffer.keys_dir = keys_dir - local.buffer.ton_log_path = ton_work_dir + "log" - local.buffer.validator_app_path = ton_bin_dir + "validator-engine/validator-engine" - local.buffer.global_config_path = ton_bin_dir + "global.config.json" - local.buffer.vconfig_path = ton_db_dir + "config.json" -#end define - -def Status(args): - keys_dir = local.buffer.keys_dir - server_key = keys_dir + "server" - client_key = keys_dir + "client" - liteserver_key = keys_dir + "liteserver" - liteserver_pubkey = liteserver_key + ".pub" - - - fnStatus = os.path.isfile(local.buffer.vconfig_path) - mtcStatus = os.path.isfile(local.buffer.mconfig_path) - vcStatus = os.path.isfile(server_key) or os.path.isfile(client_key) - lsStatus = os.path.isfile(liteserver_pubkey) - - print("Full node status:", fnStatus) - print("Mytoncore status:", mtcStatus) - print("V.console status:", vcStatus) - print("Liteserver status:", lsStatus) -#end define - -def Enable(args): - name = args[0] - if name == "PT": - CreateLocalConfigFile(args) - args = ["python3", local.buffer.my_path, "-u", local.buffer.user, "-e", "enable{name}".format(name=name)] - run_as_root(args) -#end define - -def DRVCF(args): - args = ["python3", local.buffer.my_path, "-u", local.buffer.user, "-e", "drvcf"] - run_as_root(args) -#end define - -def get_own_ip(): - requests.packages.urllib3.util.connection.HAS_IPV6 = False - ip = requests.get("https://ifconfig.me/ip").text - return ip -#end define - -def GetLiteServerConfig(): - keys_dir = local.buffer.keys_dir - liteserver_key = keys_dir + "liteserver" - liteserver_pubkey = liteserver_key + ".pub" - result = Dict() - file = open(liteserver_pubkey, 'rb') - data = file.read() - file.close() - key = base64.b64encode(data[4:]) - ip = get_own_ip() - mconfig = GetConfig(path=local.buffer.mconfig_path) - result.ip = ip2int(ip) - result.port = mconfig.liteClient.liteServer.port - result.id = Dict() - result.id["@type"]= "pub.ed25519" - result.id.key= key.decode() - return result -#end define - -def GetInitBlock(): - from mytoncore import MyTonCore - ton = MyTonCore() - initBlock = ton.GetInitBlock() - return initBlock -#end define - -def CreateLocalConfig(initBlock, localConfigPath=defaultLocalConfigPath): - # dirty hack, but GetInitBlock() function uses the same technique - from mytoncore import hex2base64 - - # read global config file - file = open("/usr/bin/ton/global.config.json", 'rt') - text = file.read() - data = json.loads(text) - file.close() - - # edit config - liteServerConfig = GetLiteServerConfig() - data["liteservers"] = [liteServerConfig] - data["validator"]["init_block"]["seqno"] = initBlock["seqno"] - data["validator"]["init_block"]["root_hash"] = hex2base64(initBlock["rootHash"]) - data["validator"]["init_block"]["file_hash"] = hex2base64(initBlock["fileHash"]) - text = json.dumps(data, indent=4) - - # write local config file - file = open(localConfigPath, 'wt') - file.write(text) - file.close() - - # chown - user = local.buffer.user - args = ["chown", "-R", user + ':' + user, localConfigPath] - - print("Local config file created:", localConfigPath) -#end define - -def PrintLiteServerConfig(args): - liteServerConfig = GetLiteServerConfig() - text = json.dumps(liteServerConfig, indent=4) - print(text) -#end define - -def CreateLocalConfigFile(args): - initBlock = GetInitBlock() - initBlock_b64 = dict2b64(initBlock) - args = ["python3", local.buffer.my_path, "-u", local.buffer.user, "-e", "clc", "-i", initBlock_b64] - run_as_root(args) -#end define - -def Event(name): - if name == "enableFN": - FirstNodeSettings() - if name == "enableVC": - EnableValidatorConsole() - if name == "enableLS": - EnableLiteServer() - if name == "enableDS": - EnableDhtServer() - if name == "drvcf": - DangerousRecoveryValidatorConfigFile() - if name == "enableJR": - EnableJsonRpc() - if name == "enablePT": - EnablePytonv3() - if name == "clc": - ix = sys.argv.index("-i") - initBlock_b64 = sys.argv[ix+1] - initBlock = b642dict(initBlock_b64) - CreateLocalConfig(initBlock) -#end define - -def General(): - if "-u" in sys.argv: - ux = sys.argv.index("-u") - user = sys.argv[ux+1] - local.buffer.user = user - Refresh() - if "-e" in sys.argv: - ex = sys.argv.index("-e") - name = sys.argv[ex+1] - Event(name) - if "-m" in sys.argv: - mx = sys.argv.index("-m") - mode = sys.argv[mx+1] - if "-t" in sys.argv: - mx = sys.argv.index("-t") - telemetry = sys.argv[mx+1] - local.buffer.telemetry = Str2Bool(telemetry) - if "--dump" in sys.argv: - mx = sys.argv.index("--dump") - dump = sys.argv[mx+1] - local.buffer.dump = Str2Bool(dump) - #end if - - # Создать настройки для mytoncore.py - FirstMytoncoreSettings() - - if mode == "full": - FirstNodeSettings() - EnableValidatorConsole() - EnableLiteServer() - BackupVconfig() - BackupMconfig() - #end if - - # Создать символические ссылки - CreateSymlinks() - #end if -#end define - -def Str2Bool(str): - if str == "true": - return True - return False -#end define - -def FirstNodeSettings(): +import random +import json +import pkg_resources + +from mypylib.mypylib import ( + add2systemd, + get_dir_from_path, + run_as_root, + color_print, + ip2int, + Dict +) +from mytoninstaller.utils import StartValidator, StartMytoncore, start_service, stop_service, get_ed25519_pubkey +from mytoninstaller.config import SetConfig, GetConfig, get_own_ip, backup_config +from mytoncore.utils import hex2b64 + + +def FirstNodeSettings(local): local.add_log("start FirstNodeSettings fuction", "debug") # Создать переменные @@ -256,10 +34,11 @@ def FirstNodeSettings(): 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.path.isfile(vconfig_path): - local.add_log("Validators config.json already exist. Break FirstNodeSettings fuction", "warning") + local.add_log(f"Validators config '{vconfig_path}' already exist. Break FirstNodeSettings fuction", "warning") return #end if @@ -279,8 +58,7 @@ def FirstNodeSettings(): # Прописать автозагрузку cpus = psutil.cpu_count() - 1 - cmd = "{validatorAppPath} --threads {cpus} --daemonize --global-config {globalConfigPath} --db {ton_db_dir} --logname {tonLogPath} --state-ttl 604800 --verbosity 1" - cmd = cmd.format(validatorAppPath=validatorAppPath, globalConfigPath=globalConfigPath, ton_db_dir=ton_db_dir, tonLogPath=tonLogPath, cpus=cpus) + 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\"" # Получить внешний ip адрес @@ -295,7 +73,7 @@ def FirstNodeSettings(): subprocess.run(args) # Скачать дамп - DownloadDump() + DownloadDump(local) # chown 1 local.add_log("Chown ton-work dir", "debug") @@ -303,10 +81,11 @@ def FirstNodeSettings(): subprocess.run(args) # start validator - StartValidator() + StartValidator(local) #end define -def DownloadDump(): + +def DownloadDump(local): dump = local.buffer.dump if dump == False: return @@ -314,7 +93,7 @@ def DownloadDump(): local.add_log("start DownloadDump fuction", "debug") url = "https://dump.ton.org" - dumpSize = requests.get(url + "/dumps/latest.size.archive.txt").text + dumpSize = requests.get(url + "/dumps/latest.tar.size.archive.txt").text print("dumpSize:", dumpSize) needSpace = int(dumpSize) * 3 diskSpace = psutil.disk_usage("/var") @@ -323,26 +102,33 @@ def DownloadDump(): #end if # apt install - cmd = "apt install plzip pv -y" + cmd = "apt install plzip pv curl -y" os.system(cmd) # download dump - cmd = "curl -Ls {url}/dumps/latest.tar.lz | pv | plzip -d -n8 | tar -xC /var/ton-work/db".format(url=url) + cmd = "curl -s {url}/dumps/latest.tar.lz | pv | plzip -d -n8 | tar -xC /var/ton-work/db".format(url=url) os.system(cmd) #end define -def FirstMytoncoreSettings(): + +def FirstMytoncoreSettings(local): local.add_log("start FirstMytoncoreSettings fuction", "debug") user = local.buffer.user # Прописать mytoncore.py в автозагрузку - add2systemd(name="mytoncore", user=user, start="/usr/bin/python3 /usr/src/mytonctrl/mytoncore.py") + # add2systemd(name="mytoncore", user=user, start="/usr/bin/python3 /usr/src/mytonctrl/mytoncore.py") # TODO: fix path + add2systemd(name="mytoncore", user=user, start="/usr/bin/python3 -m mytoncore") # Проверить конфигурацию path = "/home/{user}/.local/share/mytoncore/mytoncore.db".format(user=user) + if os.path.isfile(path): + local.add_log(f"{path} already exist. Break FirstMytoncoreSettings fuction", "warning") + return + #end if + path2 = "/usr/local/bin/mytoncore/mytoncore.db" - if os.path.isfile(path) or os.path.isfile(path2): - local.add_log("mytoncore.db already exist. Break FirstMytoncoreSettings fuction", "warning") + if os.path.isfile(path2): + local.add_log(f"{path2}.db already exist. Break FirstMytoncoreSettings fuction", "warning") return #end if @@ -395,10 +181,10 @@ def FirstMytoncoreSettings(): subprocess.run(args) # start mytoncore - StartMytoncore() + StartMytoncore(local) #end define -def EnableValidatorConsole(): +def EnableValidatorConsole(local): local.add_log("start EnableValidatorConsole function", "debug") # Create variables @@ -417,8 +203,13 @@ def EnableValidatorConsole(): server_pubkey = server_key + ".pub" # Check if key exist - if os.path.isfile(server_key) or os.path.isfile(client_key): - local.add_log("Server or client key already exist. Break EnableValidatorConsole fuction", "warning") + if os.path.isfile(server_key): + local.add_log(f"Server key '{server_key}' already exist. Break EnableValidatorConsole fuction", "warning") + return + #end if + + if os.path.isfile(client_key): + local.add_log(f"Client key '{client_key}' already exist. Break EnableValidatorConsole fuction", "warning") return #end if @@ -468,7 +259,7 @@ def EnableValidatorConsole(): SetConfig(path=vconfig_path, data=vconfig) # restart validator - StartValidator() + StartValidator(local) # read mconfig mconfig_path = local.buffer.mconfig_path @@ -486,15 +277,16 @@ def EnableValidatorConsole(): SetConfig(path=mconfig_path, data=mconfig) # Подтянуть событие в mytoncore.py - cmd = "python3 {src_dir}mytonctrl/mytoncore.py -e \"enableVC\"".format(src_dir=src_dir) + # cmd = "python3 {srcDir}mytonctrl/mytoncore.py -e \"enableVC\"".format(srcDir=srcDir) + cmd = 'python3 -m mytoncore -e "enableVC"' args = ["su", "-l", user, "-c", cmd] subprocess.run(args) # restart mytoncore - StartMytoncore() + StartMytoncore(local) #end define -def EnableLiteServer(): +def EnableLiteServer(local): local.add_log("start EnableLiteServer function", "debug") # Create variables @@ -512,7 +304,7 @@ def EnableLiteServer(): # Check if key exist if os.path.isfile(liteserver_pubkey): - local.add_log("Liteserver key already exist. Break EnableLiteServer fuction", "warning") + local.add_log(f"Liteserver key '{liteserver_pubkey}' already exist. Break EnableLiteServer fuction", "warning") return #end if @@ -557,7 +349,7 @@ def EnableLiteServer(): SetConfig(path=vconfig_path, data=vconfig) # restart validator - StartValidator() + StartValidator(local) # edit mytoncore config file # read mconfig @@ -578,97 +370,295 @@ def EnableLiteServer(): SetConfig(path=mconfig_path, data=mconfig) # restart mytoncore - StartMytoncore() + StartMytoncore(local) #end define -def StartValidator(): - # restart validator - local.add_log("Start/restart validator service", "debug") - args = ["systemctl", "restart", "validator"] - subprocess.run(args) - # sleep 10 sec - local.add_log("sleep 10 sec", "debug") - time.sleep(10) -#end define +def EnableDhtServer(local): + local.add_log("start EnableDhtServer function", "debug") + vuser = local.buffer.vuser + ton_bin_dir = local.buffer.ton_bin_dir + globalConfigPath = local.buffer.global_config_path + dht_server = ton_bin_dir + "dht-server/dht-server" + generate_random_id = ton_bin_dir + "utils/generate-random-id" + tonDhtServerDir = "/var/ton-dht-server/" + tonDhtKeyringDir = tonDhtServerDir + "keyring/" -def StartMytoncore(): - # restart mytoncore - local.add_log("Start/restart mytoncore service", "debug") - args = ["systemctl", "restart", "mytoncore"] + # Проверить конфигурацию + dht_config_path = "/var/ton-dht-server/config.json" + if os.path.isfile(dht_config_path): + local.add_log(f"DHT-Server '{dht_config_path}' already exist. Break EnableDhtServer fuction", "warning") + return + #end if + + # Подготовить папку + os.makedirs(tonDhtServerDir, exist_ok=True) + + # Прописать автозагрузку + cmd = "{dht_server} -C {globalConfigPath} -D {tonDhtServerDir}" + cmd = cmd.format(dht_server=dht_server, globalConfigPath=globalConfigPath, tonDhtServerDir=tonDhtServerDir) + add2systemd(name="dht-server", user=vuser, start=cmd) + + # Получить внешний ip адрес + ip = get_own_ip() + port = random.randint(2000, 65000) + addr = "{ip}:{port}".format(ip=ip, port=port) + + # Первый запуск + args = [dht_server, "-C", globalConfigPath, "-D", tonDhtServerDir, "-I", addr] + subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + # Получить вывод конфига + key = os.listdir(tonDhtKeyringDir)[0] + ip = ip2int(ip) + text = '{"@type": "adnl.addressList", "addrs": [{"@type": "adnl.address.udp", "ip": ' + str(ip) + ', "port": ' + str(port) + '}], "version": 0, "reinit_date": 0, "priority": 0, "expire_at": 0}' + args = [generate_random_id, "-m", "dht", "-k", tonDhtKeyringDir + key, "-a", text] + process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) + output = process.stdout.decode("utf-8") + err = process.stderr.decode("utf-8") + if len(err) > 0: + raise Exception(err) + #end if + + data = json.loads(output) + text = json.dumps(data, indent=4) + print(text) + + # chown 1 + args = ["chown", "-R", vuser + ':' + vuser, tonDhtServerDir] subprocess.run(args) + + # start DHT-Server + start_service(local, "dht-server") #end define -def GetConfig(**kwargs): - path = kwargs.get("path") - file = open(path, 'rt') - text = file.read() - file.close() - config = Dict(json.loads(text)) - return config + +def EnableJsonRpc(local): + local.add_log("start EnableJsonRpc function", "debug") + user = local.buffer.user + + jsonrpcinstaller_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'jsonrpcinstaller.sh') + local.add_log(f"Running script: {jsonrpcinstaller_path}", "debug") + exit_code = run_as_root(["bash", jsonrpcinstaller_path, "-u", user]) # TODO: fix path + if exit_code == 0: + text = "EnableJsonRpc - {green}OK{endc}" + else: + text = "EnableJsonRpc - {red}Error{endc}" + color_print(text) #end define -def SetConfig(**kwargs): - path = kwargs.get("path") - data = kwargs.get("data") +def EnableTonHttpApi(local): + local.add_log("start EnablePytonv3 function", "debug") + user = local.buffer.user - # write config - text = json.dumps(data, indent=4) - file = open(path, 'wt') - file.write(text) - file.close() + 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]) + if exit_code == 0: + text = "EnableTonHttpApi - {green}OK{endc}" + else: + text = "EnableTonHttpApi - {red}Error{endc}" + color_print(text) #end define -def BackupVconfig(): - 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) +def enable_ls_proxy(local): + local.add_log("start enable_ls_proxy function", "debug") + user = local.buffer.user + ls_proxy_port = random.randint(2000, 65000) + metrics_port = random.randint(2000, 65000) + bin_name = "ls_proxy" + ls_proxy_db_path = f"/var/{bin_name}" + ls_proxy_path = f"{ls_proxy_db_path}/{bin_name}" + ls_proxy_config_path = f"{ls_proxy_db_path}/ls-proxy-config.json" + + installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'ls_proxy_installer.sh') + local.add_log(f"Running script: {installer_path}", "debug") + exit_code = run_as_root(["bash", installer_path, "-u", user]) + if exit_code != 0: + color_print("enable_ls_proxy - {red}Error{endc}") + raise Exception("enable_ls_proxy - Error") + #end if + + # Прописать автозагрузку + add2systemd(name=bin_name, user=user, start=ls_proxy_path, workdir=ls_proxy_db_path) + + # Первый запуск - создание конфига + start_service(local, bin_name) + stop_service(local, bin_name) + + # read ls_proxy config + local.add_log("read ls_proxy config", "debug") + ls_proxy_config = GetConfig(path=ls_proxy_config_path) + + # read mytoncore config + local.add_log("read mytoncore config", "debug") + mconfig = GetConfig(path=local.buffer.mconfig_path) + ls_pubkey_path = mconfig.liteClient.liteServer.pubkeyPath + ls_port = mconfig.liteClient.liteServer.port + + # read ls_pubkey + with open(ls_pubkey_path, 'rb') as file: + data = file.read() + pubkey = data[4:] + ls_pubkey = base64.b64encode(pubkey).decode("utf-8") + #end with + + # prepare config + ls_proxy_config.ListenAddr = f"0.0.0.0:{ls_proxy_port}" + ls_proxy_config.MetricsAddr = f"127.0.0.1:{metrics_port}" + ls_proxy_config.Backends = [{ + "Name": "local_ls", + "Addr": f"127.0.0.1:{ls_port}", + "Key": ls_pubkey + }] + + # write ls_proxy config + local.add_log("write ls_proxy config", "debug") + SetConfig(path=ls_proxy_config_path, data=ls_proxy_config) + + # start ls_proxy + start_service(local, bin_name) + color_print("enable_ls_proxy - {green}OK{endc}") #end define -def BackupMconfig(): - local.add_log("Backup mytoncore config file 'mytoncore.db' to 'mytoncore.db.backup'", "debug") +def enable_ton_storage(local): + local.add_log("start enable_ton_storage function", "debug") + user = local.buffer.user + udp_port = random.randint(2000, 65000) + api_port = random.randint(2000, 65000) + bin_name = "ton_storage" + db_path = f"/var/{bin_name}" + bin_path = f"{db_path}/{bin_name}" + config_path = f"{db_path}/tonutils-storage-db/config.json" + network_config = "/usr/bin/ton/global.config.json" + + installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'ton_storage_installer.sh') + local.add_log(f"Running script: {installer_path}", "debug") + exit_code = run_as_root(["bash", installer_path, "-u", user]) + if exit_code != 0: + color_print("enable_ton_storage - {red}Error{endc}") + raise Exception("enable_ton_storage - Error") + #end if + + # Прописать автозагрузку + start_cmd = f"{bin_path} -network-config {network_config} -daemon -api 127.0.0.1:{api_port}" + add2systemd(name=bin_name, user=user, start=start_cmd, workdir=db_path, force=True) + + # Первый запуск - создание конфига + start_service(local, bin_name, sleep=10) + stop_service(local, bin_name) + + # read ton_storage config + local.add_log("read ton_storage config", "debug") + ton_storage_config = GetConfig(path=config_path) + + # prepare config + ton_storage_config.ListenAddr = f"0.0.0.0:{udp_port}" + ton_storage_config.ExternalIP = get_own_ip() + + # write ton_storage config + local.add_log("write ton_storage config", "debug") + SetConfig(path=config_path, data=ton_storage_config) + + # backup config + backup_config(local, config_path) + + # read mconfig + local.add_log("read mconfig", "debug") mconfig_path = local.buffer.mconfig_path - backupPath = mconfig_path + ".backup" - args = ["cp", mconfig_path, backupPath] - subprocess.run(args) + mconfig = GetConfig(path=mconfig_path) + + # edit mytoncore config file + local.add_log("edit mytoncore config file", "debug") + ton_storage = Dict() + ton_storage.udp_port = udp_port + ton_storage.api_port = api_port + mconfig.ton_storage = ton_storage + + # write mconfig + local.add_log("write mconfig", "debug") + SetConfig(path=mconfig_path, data=mconfig) + + # start ton_storage + start_service(local, bin_name) + color_print("enable_ton_storage - {green}OK{endc}") #end define -def GetPortsFromVconfig(): - vconfig_path = local.buffer.vconfig_path +def enable_ton_storage_provider(local): + local.add_log("start enable_ton_storage_provider function", "debug") + user = local.buffer.user + udp_port = random.randint(2000, 65000) + bin_name = "ton_storage_provider" + db_path = f"/var/{bin_name}" + bin_path = f"{db_path}/{bin_name}" + config_path = f"{db_path}/config.json" + network_config = "/usr/bin/ton/global.config.json" + + installer_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'ton_storage_provider_installer.sh') + local.add_log(f"Running script: {installer_path}", "debug") + exit_code = run_as_root(["bash", installer_path, "-u", user]) + if exit_code != 0: + color_print("enable_ton_storage_provider - {red}Error{endc}") + raise Exception("enable_ton_storage_provider - Error") + #end if - # read vconfig - local.add_log("read vconfig", "debug") - vconfig = GetConfig(path=vconfig_path) + # Прописать автозагрузку + start_cmd = f"{bin_path} -network-config {network_config}" + add2systemd(name=bin_name, user=user, start=start_cmd, workdir=db_path, force=True) + + # Первый запуск - создание конфига + start_service(local, bin_name, sleep=10) + stop_service(local, bin_name) # read mconfig local.add_log("read mconfig", "debug") mconfig_path = local.buffer.mconfig_path mconfig = GetConfig(path=mconfig_path) + # read ton_storage_provider config + local.add_log("read ton_storage_provider config", "debug") + config = GetConfig(path=config_path) + + # prepare config + config.ListenAddr = f"0.0.0.0:{udp_port}" + config.ExternalIP = get_own_ip() + config.Storages[0].BaseURL = f"http://127.0.0.1:{mconfig.ton_storage.api_port}" + + # write ton_storage_provider config + local.add_log("write ton_storage_provider config", "debug") + SetConfig(path=config_path, data=config) + + # backup config + backup_config(local, config_path) + + # get provider pubkey + key_bytes = base64.b64decode(config.ProviderKey) + pubkey_bytes = key_bytes[32:64] + # edit mytoncore config file local.add_log("edit mytoncore config file", "debug") - mconfig.liteClient.liteServer.port = mconfig.liteservers[0].port - mconfig.validatorConsole.addr = f"127.0.0.1:{mconfig.control[0].port}" + provider = Dict() + provider.udp_port = udp_port + provider.config_path = config_path + provider.pubkey = pubkey_bytes.hex() + mconfig.ton_storage.provider = provider # write mconfig local.add_log("write mconfig", "debug") SetConfig(path=mconfig_path, data=mconfig) - # restart mytoncore - StartMytoncore() + # Подтянуть событие в mytoncore.py + cmd = 'python3 -m mytoncore -e "enable_ton_storage_provider"' + args = ["su", "-l", user, "-c", cmd] + subprocess.run(args) + + # start ton_storage_provider + start_service(local, bin_name) + color_print("enable_ton_storage_provider - {green}OK{endc}") #end define -def DangerousRecoveryValidatorConfigFile(): +def DangerousRecoveryValidatorConfigFile(local): local.add_log("start DangerousRecoveryValidatorConfigFile function", "info") - # install and import cryptography library - args = ["pip3", "install", "cryptography"] - subprocess.run(args) - from cryptography.hazmat.primitives import serialization - from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey - # Get keys from keyring keys = list() keyringDir = "/var/ton-work/db/keyring/" @@ -712,10 +702,8 @@ def DangerousRecoveryValidatorConfigFile(): file = open(path, 'rb') data = file.read() file.close() - peivkey = data[4:] - privkeyObject = Ed25519PrivateKey.from_private_bytes(peivkey) - pubkeyObject = privkeyObject.public_key() - pubkey = pubkeyObject.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) + privkey = data[4:] + pubkey = get_ed25519_pubkey(privkey) if pubkey == ls_pubkey: ls_id = hex2b64(item) keys.remove(ls_id) @@ -746,10 +734,8 @@ def DangerousRecoveryValidatorConfigFile(): file = open(path, 'rb') data = file.read() file.close() - peivkey = data[4:] - privkeyObject = Ed25519PrivateKey.from_private_bytes(peivkey) - pubkeyObject = privkeyObject.public_key() - pubkey = pubkeyObject.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw) + privkey = data[4:] + pubkey = get_ed25519_pubkey(privkey) if pubkey == vPubkey: vcId = hex2b64(item) keys.remove(vcId) @@ -865,21 +851,8 @@ def DangerousRecoveryValidatorConfigFile(): print("keys:", keys) #end define -def hex2b64(input): - hexBytes = bytes.fromhex(input) - b64Bytes = base64.b64encode(hexBytes) - b64String = b64Bytes.decode() - return b64String -#end define - -def b642hex(input): - b64Bytes = input.encode() - hexBytes = base64.b64decode(b64Bytes) - hexString = hexBytes.hex() - return hexString -#end define -def CreateSymlinks(): +def CreateSymlinks(local): local.add_log("start CreateSymlinks fuction", "debug") cport = local.buffer.cport @@ -889,7 +862,8 @@ def CreateSymlinks(): validator_console_file = "/usr/bin/validator-console" env_file = "/etc/environment" file = open(mytonctrl_file, 'wt') - file.write("/usr/bin/python3 /usr/src/mytonctrl/mytonctrl.py $@") + # file.write("/usr/bin/python3 /usr/src/mytonctrl/mytonctrl.py $@") # TODO: fix path + file.write("/usr/bin/python3 -m mytonctrl $@") # TODO: fix path file.close() file = open(fift_file, 'wt') file.write("/usr/bin/ton/crypto/fift $@") @@ -915,128 +889,12 @@ def CreateSymlinks(): file.close() #end define -def EnableDhtServer(): - local.add_log("start EnableDhtServer function", "debug") - vuser = local.buffer.vuser - ton_bin_dir = local.buffer.ton_bin_dir - globalConfigPath = local.buffer.global_config_path - dht_server = ton_bin_dir + "dht-server/dht-server" - generate_random_id = ton_bin_dir + "utils/generate-random-id" - tonDhtServerDir = "/var/ton-dht-server/" - tonDhtKeyringDir = tonDhtServerDir + "keyring/" - # Проверить конфигурацию - if os.path.isfile("/var/ton-dht-server/config.json"): - local.add_log("DHT-Server config.json already exist. Break EnableDhtServer fuction", "warning") +def EnableMode(local): + args = ["python3", "-m", "mytoncore", "-e"] + if local.buffer.mode == 'liteserver': + args.append("enable_liteserver_mode") + else: return - #end if - - # Подготовить папку - os.makedirs(tonDhtServerDir, exist_ok=True) - - # Прописать автозагрузку - cmd = "{dht_server} -C {globalConfigPath} -D {tonDhtServerDir}" - cmd = cmd.format(dht_server=dht_server, globalConfigPath=globalConfigPath, tonDhtServerDir=tonDhtServerDir) - add2systemd(name="dht-server", user=vuser, start=cmd) - - # Получить внешний ip адрес - ip = get_own_ip() - port = random.randint(2000, 65000) - addr = "{ip}:{port}".format(ip=ip, port=port) - - # Первый запуск - args = [dht_server, "-C", globalConfigPath, "-D", tonDhtServerDir, "-I", addr] - subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - - # Получить вывод конфига - key = os.listdir(tonDhtKeyringDir)[0] - ip = ip2int(ip) - text = '{"@type": "adnl.addressList", "addrs": [{"@type": "adnl.address.udp", "ip": ' + str(ip) + ', "port": ' + str(port) + '}], "version": 0, "reinit_date": 0, "priority": 0, "expire_at": 0}' - args = [generate_random_id, "-m", "dht", "-k", tonDhtKeyringDir + key, "-a", text] - process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) - output = process.stdout.decode("utf-8") - err = process.stderr.decode("utf-8") - if len(err) > 0: - raise Exeption(err) - #end if - - data = json.loads(output) - text = json.dumps(data, indent=4) - print(text) - - # chown 1 - args = ["chown", "-R", vuser + ':' + vuser, tonDhtServerDir] - subprocess.run(args) - - # start DHT-Server - args = ["systemctl", "restart", "dht-server"] + args = ["su", "-l", local.buffer.user, "-c", ' '.join(args)] subprocess.run(args) -#end define - -def SetWebPassword(args): - args = ["python3", "/usr/src/mtc-jsonrpc/mtc-jsonrpc.py", "-p"] - subprocess.run(args) -#end define - -def EnableJsonRpc(): - local.add_log("start EnableJsonRpc function", "debug") - user = local.buffer.user - exitCode = run_as_root(["bash", "/usr/src/mytonctrl/scripts/jsonrpcinstaller.sh", "-u", user]) - if exitCode == 0: - text = "EnableJsonRpc - {green}OK{endc}" - else: - text = "EnableJsonRpc - {red}Error{endc}" - color_print(text) -#end define - -def EnablePytonv3(): - local.add_log("start EnablePytonv3 function", "debug") - user = local.buffer.user - exitCode = run_as_root(["bash", "/usr/src/mytonctrl/scripts/pytonv3installer.sh", "-u", user]) - if exitCode == 0: - text = "EnablePytonv3 - {green}OK{endc}" - else: - text = "EnablePytonv3 - {red}Error{endc}" - color_print(text) -#end define - -def str2b64(s): - b = s.encode("utf-8") - b64 = base64.b64encode(b) - b64 = b64.decode("utf-8") - return b64 -#end define - -def b642str(b64): - b64 = b64.encode("utf-8") - b = base64.b64decode(b64) - s = b.decode("utf-8") - return s -#end define - -def dict2b64(d): - s = json.dumps(d) - b64 = str2b64(s) - return b64 -#end define - -def b642dict(b64): - s = b642str(b64) - d = json.loads(s) - return d -#end define - - - -### -### Start of the program -### - -if __name__ == "__main__": - Init() - if len(sys.argv) > 1: - General() - else: - console.Run() - local.exit() -#end if diff --git a/mytoninstaller/utils.py b/mytoninstaller/utils.py new file mode 100644 index 00000000..a2cd817f --- /dev/null +++ b/mytoninstaller/utils.py @@ -0,0 +1,52 @@ +import base64 +import json +import time +import subprocess +from nacl.signing import SigningKey + + +def GetInitBlock(): + from mypylib.mypylib import MyPyClass + from mytoncore import MyTonCore + + mytoncore_local = MyPyClass('mytoncore.py') + ton = MyTonCore(mytoncore_local) + initBlock = ton.GetInitBlock() + return initBlock +#end define + +def start_service(local, service_name:str, sleep:int=1): + local.add_log(f"Start/restart {service_name} service", "debug") + args = ["systemctl", "restart", service_name] + subprocess.run(args) + + local.add_log(f"sleep {sleep} sec", "debug") + time.sleep(sleep) +#end define + +def stop_service(local, service_name:str): + local.add_log(f"Stop {service_name} service", "debug") + args = ["systemctl", "stop", service_name] + subprocess.run(args) +#end define + +def StartValidator(local): + start_service(local, "validator", sleep=10) +#end define + +def StartMytoncore(local): + start_service(local, "mytoncore") +#end define + +def get_ed25519_pubkey_text(privkey_text): + privkey = base64.b64decode(privkey_text) + pubkey = get_ed25519_pubkey(privkey) + pubkey_text = base64.b64encode(pubkey).decode("utf-8") + return pubkey_text +#end define + +def get_ed25519_pubkey(privkey): + privkey_obj = SigningKey(privkey) + pubkey = privkey_obj.verify_key.encode() + return pubkey +#end define diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..f7705c47 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +crc16 +requests +psutil +fastcrc +jsonpickle +pynacl diff --git a/scripts/install.sh b/scripts/install.sh index 8f80eebb..cfe84c76 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,20 +1,33 @@ #!/bin/bash set -e -# Проверить sudo +# colors +COLOR='\033[92m' +ENDC='\033[0m' +mydir=`pwd` + +# check sudo permissions if [ "$(id -u)" != "0" ]; then - echo "Please run script as root" - exit 1 + echo "Please run script as root" + exit 1 fi +author="ton-blockchain" +repo="mytonctrl" +branch="master" +mode="validator" + show_help_and_exit() { - echo 'Supported argumets:' - echo ' -m [lite|full] Choose installation mode' - 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 ' -h Show this help' + 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' exit } @@ -22,88 +35,99 @@ if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then show_help_and_exit fi -# Get arguments +# node install parameters config="https://ton-blockchain.github.io/global.config.json" telemetry=true ignore=false dump=false -while getopts m:c:tidh flag + + + +while getopts c:tida:r:b:m: flag do case "${flag}" in - m) mode=${OPTARG};; c) config=${OPTARG};; t) telemetry=false;; i) ignore=true;; d) dump=true;; - h) show_help_and_exit;; - *) + 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 +# check machine configuration +echo -e "${COLOR}[1/5]${ENDC} Checking system requirements" -# Проверка режима установки -if [ "${mode}" != "lite" ] && [ "${mode}" != "full" ]; then - echo "Run script with flag '-m lite' or '-m full'" - exit 1 -fi - -# Проверка мощностей cpus=$(lscpu | grep "CPU(s)" | head -n 1 | awk '{print $2}') -memory=$(grep MemTotal /proc/meminfo | awk '{print $2}') -if [ "${mode}" = "lite" ] && [ "$ignore" = false ] && ([ "${cpus}" -lt 2 ] || [ "${memory}" -lt 2000000 ]); then - echo "Insufficient resources. Requires a minimum of 2 processors and 2Gb RAM." - exit 1 -fi -if [ "${mode}" = "full" ] && [ "$ignore" = false ] && ([ "${cpus}" -lt 8 ] || [ "${memory}" -lt 8000000 ]); then - echo "Insufficient resources. Requires a minimum of 8 processors and 8Gb RAM." +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 fi -# Цвета -COLOR='\033[92m' -ENDC='\033[0m' - -# Начинаю установку mytonctrl -echo -e "${COLOR}[1/4]${ENDC} Starting installation MyTonCtrl" -mydir=$(pwd) - -# На OSX нет такой директории по-умолчанию, поэтому создаем... +echo -e "${COLOR}[2/5]${ENDC} Checking for required TON components" SOURCES_DIR=/usr/src 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} fi -# Проверяю наличие компонентов TON -echo -e "${COLOR}[2/4]${ENDC} Checking for required TON components" +# check TON components file1=${BIN_DIR}/ton/crypto/fift file2=${BIN_DIR}/ton/lite-client/lite-client file3=${BIN_DIR}/ton/validator-engine-console/validator-engine-console -if [ -f "${file1}" ] && [ -f "${file2}" ] && [ -f "${file3}" ]; then - echo "TON exist" - cd $SOURCES_DIR - rm -rf $SOURCES_DIR/mytonctrl - git clone --recursive https://github.com/ton-blockchain/mytonctrl.git -else - rm -f toninstaller.sh - wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/toninstaller.sh - bash toninstaller.sh -c "${config}" - rm -f toninstaller.sh + +if [ ! -f "${file1}" ] || [ ! -f "${file2}" ] || [ ! -f "${file3}" ]; then + echo "TON does not exists, building" + wget https://raw.githubusercontent.com/${author}/${repo}/${branch}/scripts/ton_installer.sh -O /tmp/ton_installer.sh + bash /tmp/ton_installer.sh -c ${config} fi -# Запускаю установщик mytoninstaller.py -echo -e "${COLOR}[3/4]${ENDC} Launching the mytoninstaller.py" +# Cloning mytonctrl +echo -e "${COLOR}[3/5]${ENDC} Installing MyTonCtrl" +echo "https://github.com/${author}/${repo}.git -> ${branch}" + +# remove previous installation +cd $SOURCES_DIR +rm -rf $SOURCES_DIR/mytonctrl +pip3 uninstall -y mytonctrl + +git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git ${repo} # TODO: return --recursive back when fix libraries +git config --global --add safe.directory $SOURCES_DIR/${repo} +cd $SOURCES_DIR/${repo} + +pip3 install -U . # TODO: make installation from git directly + +echo -e "${COLOR}[4/5]${ENDC} Running mytoninstaller" +# DEBUG + parent_name=$(ps -p $PPID -o comm=) user=$(whoami) if [ "$parent_name" = "sudo" ] || [ "$parent_name" = "su" ]; then user=$(logname) fi -python3 ${SOURCES_DIR}/mytonctrl/mytoninstaller.py -m ${mode} -u ${user} -t ${telemetry} --dump ${dump} +echo "User: $user" +python3 -m mytoninstaller -u ${user} -t ${telemetry} --dump ${dump} -m ${mode} + +# set migrate version +migrate_version=1 +version_dir="/home/${user}/.local/share/mytonctrl" +version_path="${version_dir}/VERSION" +mkdir -p ${version_dir} +echo ${migrate_version} > ${version_path} +chown ${user}:${user} ${version_dir} ${version_path} -# Выход из программы -echo -e "${COLOR}[4/4]${ENDC} Mytonctrl installation completed" +echo -e "${COLOR}[5/5]${ENDC} Mytonctrl installation completed" exit 0 diff --git a/scripts/set_archive_ttl.py b/scripts/set_archive_ttl.py deleted file mode 100644 index f11fd436..00000000 --- a/scripts/set_archive_ttl.py +++ /dev/null @@ -1,38 +0,0 @@ -import subprocess -import sys - -with open('/etc/systemd/system/validator.service', 'r') as file: - service = file.read() - - -for line in service.split('\n'): - if line.startswith('ExecStart'): - exec_start = line - break - - -if '--archive-ttl' in exec_start: - print('Archive TTL is already set') - sys.exit(100) - -default_command = 'ExecStart = /usr/bin/ton/validator-engine/validator-engine --threads --daemonize --global-config /usr/bin/ton/global.config.json --db /var/ton-work/db/ --logname /var/ton-work/log --state-ttl 604800 --verbosity' - -# ExecStart = /usr/bin/ton/validator-engine/validator-engine --threads 31 --daemonize --global-config /usr/bin/ton/global.config.json --db /var/ton-work/db/ --logname /var/ton-work/log --state-ttl 604800 --verbosity 1 - -t = exec_start.split(' ') -t.pop(t.index('--threads') + 1) # pop threads value since it's different for each node -t.pop(t.index('--verbosity') + 1) # pop verbosity value - -if ' '.join(t) != default_command: - print('ExecStart is not default. Please set archive-ttl manually in `/etc/systemd/system/validator.service`.') - sys.exit(101) - -archive_ttl = sys.argv[1] - -new_exec_start = exec_start + f' --archive-ttl {archive_ttl}' - -with open('/etc/systemd/system/validator.service', 'w') as file: - file.write(service.replace(exec_start, new_exec_start)) - -subprocess.run(['systemctl', 'daemon-reload']) -subprocess.run(['systemctl', 'restart', 'validator']) diff --git a/scripts/set_state_ttl.py b/scripts/set_state_ttl.py deleted file mode 100644 index 4838fe29..00000000 --- a/scripts/set_state_ttl.py +++ /dev/null @@ -1,30 +0,0 @@ -import subprocess -import sys - -with open('/etc/systemd/system/validator.service', 'r') as file: - service = file.read() - - -for line in service.split('\n'): - if line.startswith('ExecStart'): - exec_start = line - break - - -if exec_start.split(' ')[2] != '/usr/bin/ton/validator-engine/validator-engine': - raise Exception('Invalid node start command in service file') - - -if '--state-ttl 604800' not in exec_start: - print('No state-ttl or custom one found in ExecStart') - sys.exit(0) - -new_exec_start = exec_start.replace('--state-ttl 604800', '') - -with open('/etc/systemd/system/validator.service', 'w') as file: - file.write(service.replace(exec_start, new_exec_start)) - -subprocess.run(['systemctl', 'daemon-reload']) -subprocess.run(['systemctl', 'restart', 'validator']) - -print('Removed state-ttl from service file.') diff --git a/scripts/ton_http_api_installer.sh b/scripts/ton_http_api_installer.sh new file mode 100644 index 00000000..9327f870 --- /dev/null +++ b/scripts/ton_http_api_installer.sh @@ -0,0 +1,40 @@ +#!/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' + +# Установка компонентов python3 +pip3 install virtualenv + +# Подготовить папку с виртуальным окружением +echo -e "${COLOR}[1/4]${ENDC} Preparing the virtual environment" +venv_path="/opt/virtualenv/ton_http_api" +virtualenv ${venv_path} + +# Установка компонентов python3 +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} + +# Прописать автозагрузку +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/')" +python3 -c "${cmd}" +systemctl restart ton_http_api + +# Конец +echo -e "${COLOR}[4/4]${ENDC} ton_http_api service installation complete" +exit 0 diff --git a/scripts/toninstaller.sh b/scripts/ton_installer.sh old mode 100755 new mode 100644 similarity index 58% rename from scripts/toninstaller.sh rename to scripts/ton_installer.sh index 740fd468..500a92f3 --- a/scripts/toninstaller.sh +++ b/scripts/ton_installer.sh @@ -1,3 +1,4 @@ + #!/bin/bash set -e @@ -8,7 +9,7 @@ if [ "$(id -u)" != "0" ]; then fi # Get arguments -config="https://ton-blockchain.github.io/global.config.json" +config=https://ton-blockchain.github.io/global.config.json while getopts c: flag do case "${flag}" in @@ -32,71 +33,36 @@ fi # Установка требуемых пакетов echo -e "${COLOR}[1/6]${ENDC} Installing required packages" if [ "$OSTYPE" == "linux-gnu" ]; then - - # Determine if it is a CentOS system - if cat /etc/*release | grep ^NAME | grep CentOS ; then - echo "CentOS Linux detected." - yum update -y + if [ hash yum 2>/dev/null ]; then + echo "RHEL-based Linux detected." yum install -y epel-release - yum install -y git gflags gflags-devel zlib zlib-devel openssl-devel openssl-libs readline-devel libmicrohttpd python3 python3-pip python36-devel g++ gcc libstdc++-devel zlib1g-dev libssl-dev which make gcc-c++ libstdc++-devel - - # Upgrade make and gcc in CentOS system - yum install centos-release-scl -y - yum install devtoolset-10 -y - echo "source /opt/rh/devtoolset-10/enable" >> /etc/bashrc - source /opt/rh/devtoolset-10/enable - - # Install the new version of CMake - yum remove cmake -y - wget https://cmake.org/files/v3.24/cmake-3.24.2.tar.gz - tar -zxvf cmake-3.24.2.tar.gz - cd cmake-3.24.2 && ./bootstrap && make -j -j$(nproc) && make install - yum remove cmake -y - ln -s /usr/local/bin/cmake /usr/bin/cmake - source ~/.bashrc - cmake --version - - # Install ninja - yum install -y ninja-build - - # Red Hat systems are not supported - elif cat /etc/*release | grep ^NAME | grep Red ; then - echo "Red Hat Linux detected." - echo "This OS is not supported with this script at present. Sorry." - echo "Please refer to https://github.com/ton-blockchain/mytonctrl for setup information." - exit 1 - - # Suse systems are not supported + dnf config-manager --set-enabled PowerTools + yum install -y curl git make cmake clang gflags gflags-devel zlib zlib-devel openssl-devel openssl-libs readline-devel libmicrohttpd python3 python3-pip python36-devel elif [ -f /etc/SuSE-release ]; then echo "Suse Linux detected." echo "This OS is not supported with this script at present. Sorry." echo "Please refer to https://github.com/ton-blockchain/mytonctrl for setup information." exit 1 - elif [ -f /etc/arch-release ]; then echo "Arch Linux detected." - pacman -Syuy --noconfirm - pacman -S --noconfirm git cmake clang gflags zlib openssl readline libmicrohttpd python python-pip - - # Install ninja - pacman -S --noconfirm ninja - + pacman -Syuy + pacman -S --noconfirm curl git make cmake clang gflags zlib openssl readline libmicrohttpd python python-pip elif [ -f /etc/debian_version ]; then echo "Ubuntu/Debian Linux detected." apt-get update - apt-get install -y build-essential 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 # Install ninja apt-get install -y ninja-build + # Install for benchmark + apt install -y fio rocksdb-tools else echo "Unknown Linux distribution." echo "This OS is not supported with this script at present. Sorry." echo "Please refer to https://github.com/ton-blockchain/mytonctrl for setup information." exit 1 fi - -# Detected mac os system. elif [[ "$OSTYPE" =~ darwin.* ]]; then echo "Mac OS (Darwin) detected." if [ ! which brew >/dev/null 2>&1 ]; then @@ -108,46 +74,39 @@ elif [[ "$OSTYPE" =~ darwin.* ]]; then su $LOCAL_USERNAME -c "brew update" su $LOCAL_USERNAME -c "brew install openssl cmake llvm" - - # Install ninja - su $LOCAL_USERNAME -c "brew install ninja" - elif [ "$OSTYPE" == "freebsd"* ]; then echo "FreeBSD detected." echo "This OS is not supported with this script at present. Sorry." - echo "Please refer to https://github.com/paritytech/substrate for setup information." + echo "Please refer to https://github.com/paritytech/substrate for setup information." # TODO: remove links exit 1 else echo "Unknown operating system." echo "This OS is not supported with this script at present. Sorry." - echo "Please refer to https://github.com/paritytech/substrate for setup information." + echo "Please refer to https://github.com/paritytech/substrate for setup information." # TODO: remove links exit 1 fi # Установка компонентов python3 -pip3 install psutil fastcrc requests +pip3 install psutil crc16 requests + +# build openssl 3.0 +echo -e "${COLOR}[2/6]${ENDC} Building OpenSSL 3.0" +rm -rf $BIN_DIR/openssl_3 +git clone https://github.com/openssl/openssl $BIN_DIR/openssl_3 +cd $BIN_DIR/openssl_3 +opensslPath=`pwd` +git checkout openssl-3.1.4 +./config +make build_libs -j$(nproc) # Клонирование репозиториев с github.com -echo -e "${COLOR}[2/6]${ENDC} Cloning github repository" +echo -e "${COLOR}[3/6]${ENDC} Preparing for compilation" cd $SOURCES_DIR rm -rf $SOURCES_DIR/ton -rm -rf $SOURCES_DIR/mytonctrl git clone --recursive https://github.com/ton-blockchain/ton.git -git clone --recursive https://github.com/ton-blockchain/mytonctrl.git git config --global --add safe.directory $SOURCES_DIR/ton -git config --global --add safe.directory $SOURCES_DIR/mytonctrl - -cd $BIN_DIR -rm -rf openssl_3 -git clone https://github.com/openssl/openssl openssl_3 -cd openssl_3 -opensslPath=`pwd` -git checkout openssl-3.1.4 -./config -make build_libs -j12 # Подготавливаем папки для компиляции -echo -e "${COLOR}[3/6]${ENDC} Preparing for compilation" rm -rf $BIN_DIR/ton mkdir $BIN_DIR/ton cd $BIN_DIR/ton @@ -167,29 +126,32 @@ fi if [[ "$OSTYPE" =~ darwin.* ]]; then if [[ $(uname -p) == 'arm' ]]; then echo M1 - CC="clang -mcpu=apple-a14" CXX="clang++ -mcpu=apple-a14" cmake $SOURCES_DIR/ton -DCMAKE_BUILD_TYPE=Release -DTON_ARCH= -Wno-dev -GNinja + CC="clang -mcpu=apple-a14" CXX="clang++ -mcpu=apple-a14" cmake $SOURCES_DIR/ton -DCMAKE_BUILD_TYPE=Release -DTON_ARCH= -Wno-dev else - cmake -DCMAKE_BUILD_TYPE=Release $SOURCES_DIR/ton -GNinja + cmake -DCMAKE_BUILD_TYPE=Release $SOURCES_DIR/ton fi else cmake -DCMAKE_BUILD_TYPE=Release $SOURCES_DIR/ton -GNinja -DTON_USE_JEMALLOC=ON -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=$opensslPath/include -DOPENSSL_CRYPTO_LIBRARY=$opensslPath/libcrypto.a fi -# Компилируем из исходников -echo -e "${COLOR}[4/6]${ENDC} Source Compilation" +# Расчитываем количество процессоров для сборки if [[ "$OSTYPE" =~ darwin.* ]]; then - cpuNumber=$(sysctl -n hw.logicalcpu) + cpu_number=$(sysctl -n hw.logicalcpu) else memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') - cpuNumber=$(($memory/2100000)) - if [ ${cpuNumber} == 0 ]; then + cpu_number=$(($memory/2100000)) + max_cpu_number=$(nproc) + if [ ${cpu_number} -gt ${max_cpu_number} ]; then + cpu_number=$((${max_cpu_number}-1)) + fi + if [ ${cpu_number} == 0 ]; then echo "Warning! insufficient RAM" - cpuNumber=1 + cpu_number=1 fi fi -echo "use ${cpuNumber} cpus" -ninja -j ${cpuNumber} fift validator-engine lite-client validator-engine-console generate-random-id dht-server func tonlibjson rldp-http-proxy +echo -e "${COLOR}[4/6]${ENDC} Source compilation, use ${cpu_number} cpus" +ninja -j ${cpu_number} fift validator-engine lite-client validator-engine-console generate-random-id dht-server func tonlibjson rldp-http-proxy # Скачиваем конфигурационные файлы lite-client echo -e "${COLOR}[5/6]${ENDC} Downloading config files" diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh index 87ef4a6b..9e1f275c 100644 --- a/scripts/uninstall.sh +++ b/scripts/uninstall.sh @@ -1,4 +1,10 @@ #!/bin/bash +full=true +while getopts f flag; do + case "${flag}" in + f) full=false + esac +done # Проверить sudo if [ "$(id -u)" != "0" ]; then @@ -26,11 +32,17 @@ rm -rf /etc/systemd/system/dht-server.service systemctl daemon-reload # Удаление файлов -rm -rf /usr/src/ton +if $full; then + echo "removing Ton node" + rm -rf /usr/src/ton + rm -rf /usr/bin/ton + rm -rf /var/ton-work + rm -rf /var/ton-dht-server +fi + rm -rf /usr/src/mytonctrl -rm -rf /usr/bin/ton -rm -rf /var/ton-work -rm -rf /var/ton-dht-server +rm -rf /usr/src/mtc-jsonrpc +rm -rf /usr/src/pytonv3 rm -rf /tmp/myton* rm -rf /usr/local/bin/mytoninstaller/ rm -rf /usr/local/bin/mytoncore/mytoncore.db @@ -38,10 +50,17 @@ rm -rf /home/${user}/.local/share/mytonctrl rm -rf /home/${user}/.local/share/mytoncore/mytoncore.db # Удаление ссылок -rm -rf /usr/bin/fift -rm -rf /usr/bin/liteclient -rm -rf /usr/bin/validator-console +if $full; then + echo "removing ton node" + rm -rf /usr/bin/fift + rm -rf /usr/bin/liteclient + rm -rf /usr/bin/validator-console +fi rm -rf /usr/bin/mytonctrl +# removing pip packages +pip3 uninstall -y mytonctrl +pip3 uninstall -y ton-http-api + # Конец echo -e "${COLOR}Uninstall Complete${ENDC}" diff --git a/scripts/upgrade.py b/scripts/upgrade.py deleted file mode 100644 index 9971694a..00000000 --- a/scripts/upgrade.py +++ /dev/null @@ -1,31 +0,0 @@ -# Говнокод ON -import os -from sys import path -from os.path import dirname as dir -path.append(dir(path[0])) -# Говнокод OFF - -from mypylib.mypylib import * - - -# validator.service -file = open("/etc/systemd/system/validator.service", 'rt') -text = file.read() -file.close() -lines = text.split('\n') - -for i in range(len(lines)): - line = lines[i] - if "ExecStart" not in line: - continue - if "ton-global.config.json" in line: - lines[i] += line.replace("validator-engine/ton-global.config.json", "global.config.json") -#end for - -text = "\n".join(lines) -file = open("/etc/systemd/system/validator.service", 'wt') -file.write(text) -file.close() - -args = ["systemctl", "daemon-reload"] -subprocess.run(args) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..c4302d88 --- /dev/null +++ b/setup.py @@ -0,0 +1,49 @@ +from setuptools import setup, find_packages +from os.path import dirname, join + +with open(join(dirname(__file__), "README.md"), "r") as f: + long_description = f.read() +with open(join(dirname(__file__), "requirements.txt")) as file: + install_requires = file.read().split('\n') + + +version = 'v0.1' + +setup( + author='igroman787', + author_email='igroman787', + name='mytonctrl', + version=version, + packages=find_packages('.', exclude=['tests']), + install_requires=install_requires, + package_data={ + 'mytoninstaller.scripts': ['*.sh'], + 'mytoncore': [ + 'contracts/**/*', + 'complaints/*' + ], + 'mytonctrl': [ + 'resources/*', + 'scripts/*', + 'migrations/*.sh' + ], + '': ['requirements.txt'], + }, + zip_safe=True, + python_requires='>=3.7', + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "License :: Other/Proprietary License", + "Topic :: Software Development :: Libraries" + ], + url="https://github.com/ton-blockchain/mytonctrl", + description="MyTonCtrl", + long_description_content_type="text/markdown", + long_description=long_description, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/blocksScanner.py b/tests/blocksScanner.py index b8296746..88b012ff 100644 --- a/tests/blocksScanner.py +++ b/tests/blocksScanner.py @@ -1,9 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf_8 -*- -import sys -sys.path.append("/usr/src/mytonctrl/") -from mypylib.mypylib import bcolors, Sleep +from mypylib.mypylib import bcolors, Sleep, MyPyClass from mytoncore import MyTonCore, TonBlocksScanner def NewBlockReaction(block): @@ -19,7 +17,8 @@ def NewMessageReaction(message): #end define -ton = MyTonCore() +local = MyPyClass('./tests') +ton = MyTonCore(local) scanner = TonBlocksScanner(ton, nbr=NewBlockReaction, ntr=NewTransReaction, nmr=NewMessageReaction) scanner.Run() Sleep() diff --git a/tests/bounce.py b/tests/bounce.py index 79d60b32..08e5aa20 100644 --- a/tests/bounce.py +++ b/tests/bounce.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf_8 -*-l -from sys import path -path.append("/usr/src/mytonctrl/") -from mytoncore import * +import time -Local = MyPyClass(__file__) -ton = MyTonCore() +from mypylib.mypylib import MyPyClass +from mytoncore import MyTonCore + +local = MyPyClass('./tests') +ton = MyTonCore(local) def Init(): wallets = list() - Local.buffer["wallets"] = wallets + local.buffer["wallets"] = wallets walletsNameList = ton.GetWalletsNameList() # Create tests wallet @@ -50,7 +51,7 @@ def Init(): buff_wallet = wallet buff_wallet.oldseqno = ton.GetSeqno(wallet) ton.MoveCoinsFromHW(wallet, [[testsWallet.addr, need]], wait=False) - Local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) + local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) if buff_wallet: ton.WaitTransaction(buff_wallet) #end for @@ -63,18 +64,18 @@ def Init(): if wallet.account.status == "uninit": wallet.oldseqno = ton.GetSeqno(wallet) ton.SendFile(wallet.bocFilePath) - Local.AddLog(str(wallet.subwallet) + " - OK") + local.AddLog(str(wallet.subwallet) + " - OK") ton.WaitTransaction(wallets[-1]) #end define def Work(): - wallets = Local.buffer["wallets"] + wallets = local.buffer["wallets"] destList = list() destList.append(["EQAY_2_A88HD43S96hbVGbCLB21e6_k1nbaqICwS3ZCrMBaZ", 2]) for wallet in wallets: wallet.oldseqno = ton.GetSeqno(wallet) ton.MoveCoinsFromHW(wallet, destList, wait=False) - Local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") + local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") ton.WaitTransaction(wallets[-1]) #end define @@ -83,7 +84,7 @@ def General(): while True: time.sleep(1) Work() - Local.AddLog("Work - OK") + local.AddLog("Work - OK") #end while #end define @@ -92,10 +93,10 @@ def General(): ### ### Start test ### -Local.Run() +local.Run() load = 200 -Local.StartCycle(General, sec=1) +local.StartCycle(General, sec=1) while True: time.sleep(60) #load += 10 diff --git a/tests/mg.py b/tests/mg.py index 536982b1..49877117 100644 --- a/tests/mg.py +++ b/tests/mg.py @@ -3,13 +3,13 @@ import sys import time -sys.path.append("/usr/src/mytonctrl/") + from mypylib.mypylib import bcolors, Sleep from mytoncore import MyTonCore from mypylib.mypylib import MyPyClass -ton = MyTonCore() local = MyPyClass(__file__) +ton = MyTonCore(local) def TestMoveCoins(wallet, dest, coins, **kwargs): start = time.time() diff --git a/tests/tpsLoad.py b/tests/tpsLoad.py index 8d44ae5c..3edb92dd 100644 --- a/tests/tpsLoad.py +++ b/tests/tpsLoad.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 # -*- coding: utf_8 -*-l -from sys import path -path.append("/usr/src/mytonctrl/") -from mytoncore import * +import time -Local = MyPyClass(__file__) -ton = MyTonCore() +from mypylib.mypylib import MyPyClass +from mytoncore import MyTonCore, Sleep + + +local = MyPyClass('./tests') +local.db["config"]["logLevel"] = "info" +load = 100 +ton = MyTonCore(local) def Init(): wallets = list() - Local.buffer["wallets"] = wallets + local.buffer["wallets"] = wallets walletsNameList = ton.GetWalletsNameList() # Create tests wallet @@ -50,7 +54,7 @@ def Init(): buff_wallet = wallet buff_wallet.oldseqno = ton.GetSeqno(wallet) ton.MoveGrams(wallet, testsWallet.addr, need, wait=False) - Local.AddLog(testsWallet.name + " <<< " + wallet.name) + local.AddLog(testsWallet.name + " <<< " + wallet.name) if buff_wallet: ton.WaitTransaction(buff_wallet, False) #end for @@ -63,12 +67,12 @@ def Init(): if wallet.account.status == "uninit": wallet.oldseqno = ton.GetSeqno(wallet) ton.SendFile(wallet.bocFilePath) - Local.AddLog(str(wallet.subwallet) + " - OK") + local.AddLog(str(wallet.subwallet) + " - OK") ton.WaitTransaction(wallets[-1]) #end define def Work(): - wallets = Local.buffer["wallets"] + wallets = local.buffer["wallets"] for i in range(load): if i + 1 == load: i = -1 @@ -78,7 +82,7 @@ def Work(): wallet2 = wallets[i+1] wallet1.oldseqno = ton.GetSeqno(wallet1) ton.MoveGrams(wallet1, wallet2.addr, 3.14, wait=False) - Local.AddLog(wallet1.name + " >>> " + wallet2.name) + local.AddLog(wallet1.name + " >>> " + wallet2.name) ton.WaitTransaction(wallets[-1]) #end define @@ -87,7 +91,7 @@ def General(): while True: time.sleep(1) Work() - Local.AddLog("Work - OK") + local.AddLog("Work - OK") #end while #end define @@ -96,13 +100,8 @@ def General(): ### ### Start test ### -Local = MyPyClass(__file__) -Local.db["config"]["logLevel"] = "info" -Local.Run() -ton = MyTonCore() -local.db["config"]["logLevel"] = "info" +local.Run() load = 100 - -Local.StartCycle(General, sec=1) +local.StartCycle(General, sec=1) Sleep() diff --git a/tests/tpsLoad2.py b/tests/tpsLoad2.py index 250c2800..b7f5f542 100644 --- a/tests/tpsLoad2.py +++ b/tests/tpsLoad2.py @@ -1,17 +1,20 @@ #!/usr/bin/env python3 # -*- coding: utf_8 -*-l -from sys import path -path.append("/usr/src/mytonctrl/") -from mytoncore import * +import time -Local = MyPyClass(__file__) -ton = MyTonCore() +from mypylib.mypylib import MyPyClass +from mytoncore import MyTonCore + +local = MyPyClass('./tests') +local.db["config"]["logLevel"] = "info" +load = 10 +ton = MyTonCore(local) def Init(): wallets = list() - Local.buffer["wallets"] = wallets + local.buffer["wallets"] = wallets walletsNameList = ton.GetWalletsNameList() # Create tests wallet @@ -50,7 +53,7 @@ def Init(): buff_wallet = wallet buff_wallet.oldseqno = ton.GetSeqno(wallet) ton.MoveGramsFromHW(wallet, [[testsWallet.addr, need]], wait=False) - Local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) + local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) if buff_wallet: ton.WaitTransaction(buff_wallet) #end for @@ -63,7 +66,7 @@ def Init(): if wallet.account.status == "uninit": wallet.oldseqno = ton.GetSeqno(wallet) ton.SendFile(wallet.bocFilePath) - Local.AddLog(str(wallet.subwallet) + " - OK") + local.AddLog(str(wallet.subwallet) + " - OK") ton.WaitTransaction(wallets[-1]) #end define @@ -75,7 +78,7 @@ def Work(): for wallet in wallets: wallet.oldseqno = ton.GetSeqno(wallet) ton.MoveGramsFromHW(wallet, destList, wait=False) - Local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") + local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") ton.WaitTransaction(wallets[-1]) #end define @@ -84,7 +87,7 @@ def General(): while True: time.sleep(1) Work() - Local.AddLog("Work - OK") + local.AddLog("Work - OK") #end while #end define @@ -93,15 +96,8 @@ def General(): ### ### Start test ### -Local = MyPyClass(__file__) -Local.db["config"]["logLevel"] = "info" -Local.Run() - -ton = MyTonCore() -local.db["config"]["logLevel"] = "info" -load = 10 - -Local.StartCycle(General, sec=1) +local.Run() +local.StartCycle(General, sec=1) while True: time.sleep(60) hour_str = time.strftime("%H")