├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── functions └── session_stats.py ├── modules ├── __init__.py ├── alert_bot.py ├── backups.py ├── collator_config.py ├── controller.py ├── custom_overlays.py ├── liteserver.py ├── module.py ├── nominator_pool.py ├── pool.py ├── prometheus.py ├── single_pool.py ├── utilities.py ├── validator.py └── wallet.py ├── mytoncore ├── __init__.py ├── __main__.py ├── complaints │ └── remove-proofs-v2.fif ├── contracts │ ├── __init__.py │ └── single-nominator-pool │ │ ├── HOWTO-deploy.md │ │ ├── README.md │ │ ├── build.sh │ │ ├── change-validator.fif │ │ ├── init.fif │ │ ├── recover-stake.fif │ │ ├── send-raw-msg.fif │ │ ├── single-nominator-code.fc │ │ ├── single-nominator-code.hex │ │ ├── single-nominator.tlb │ │ ├── upgrade.fif │ │ ├── validator-elect-signed.fif │ │ ├── validator-withdraw.fif │ │ ├── wallet-v3.fif │ │ └── withdraw.fif ├── fift.py ├── functions.py ├── liteclient.py ├── models.py ├── mytoncore.py ├── telemetry.py ├── tonblocksscanner.py ├── utils.py └── validator_console.py ├── mytonctrl.py ├── mytonctrl ├── __init__.py ├── __main__.py ├── migrate.py ├── migrations │ ├── migration_001.sh │ └── roll_back_001.sh ├── mytonctrl.py ├── progressbar.py ├── resources │ └── translate.json ├── scripts │ ├── benchmark.sh │ ├── create_backup.sh │ ├── etabar.py │ ├── restore_backup.sh │ ├── update.sh │ ├── upgrade.sh │ ├── validator-desync.sh │ └── xrestart.py └── utils.py ├── mytoninstaller ├── __init__.py ├── __main__.py ├── config.py ├── mytoninstaller.py ├── node_args.py ├── scripts │ ├── __init__.py │ ├── add2systemd.sh │ ├── jsonrpcinstaller.sh │ ├── ls_proxy_installer.sh │ ├── process_hardforks.py │ ├── pytonv3installer.sh │ ├── set_node_argument.py │ ├── ton_http_api_installer.sh │ ├── ton_storage_installer.sh │ └── utils.sh ├── settings.py └── utils.py ├── requirements.txt ├── screens ├── manual-ubuntu_mytoncore-log.png ├── manual-ubuntu_mytonctrl-help_ru.png ├── manual-ubuntu_mytonctrl-set_ru.png ├── manual-ubuntu_mytonctrl-status_ru.png ├── manual-ubuntu_mytonctrl-vas-aw_ru.png ├── manual-ubuntu_mytonctrl-wl_ru.png ├── manual-ubuntu_wget-ls_ru.png ├── mytonctrl-awl.jpeg ├── mytonctrl-ewl.jpeg ├── mytonctrl-inst.jpeg ├── mytonctrl-mg.png ├── mytonctrl-nw.png ├── mytonctrl-status.png ├── mytonctrl-status_ru.png └── mytonctrl-wl.png ├── scripts ├── install.py ├── install.sh ├── ton_installer.sh └── uninstall.sh ├── setup.py ├── tests ├── __init__.py ├── blocksScanner.py ├── bounce.py ├── mg.py ├── tpsLoad.py └── tpsLoad2.py └── tools ├── extract_backup_node_keys.sh └── inject_backup_node_keys.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | #PyCharm 107 | .idea/ 108 | .idea/$CACHE_FILE$ 109 | .idea/.gitignore 110 | .idea/encodings.xml 111 | .idea/inspectionProfiles/ 112 | .idea/misc.xml 113 | .idea/modules.xml 114 | .idea/ton_client.iml 115 | .idea/vcs.xml 116 | 117 | *.DS_Store 118 | .idea/ 119 | __pycache__/ 120 | test.py 121 | .vscode/ 122 | venv/ 123 | venv38/ 124 | sandbox/ 125 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "mypylib"] 2 | path = mypylib 3 | url = https://github.com/igroman787/mypylib 4 | [submodule "mypyconsole"] 5 | path = mypyconsole 6 | url = https://github.com/igroman787/mypyconsole 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub stars](https://img.shields.io/github/stars/ton-blockchain/mytonctrl?style=flat-square&logo=github) ![GitHub forks](https://img.shields.io/github/forks/ton-blockchain/mytonctrl?style=flat-square&logo=github) ![GitHub issues](https://img.shields.io/github/issues/ton-blockchain/mytonctrl?style=flat-square&logo=github) ![GitHub pull requests](https://img.shields.io/github/issues-pr/ton-blockchain/mytonctrl?style=flat-square&logo=github) ![GitHub last commit](https://img.shields.io/github/last-commit/ton-blockchain/mytonctrl?style=flat-square&logo=github) ![GitHub license](https://img.shields.io/github/license/ton-blockchain/mytonctrl?style=flat-square&logo=github) 2 | 3 | 4 | # MyTonCtrl 5 | 6 | 7 | ## Contents 8 | 9 | - [What is MyTonCtrl?](#what-is-myttonctrl) 10 | - [MyTonCtrl Documentation](#mytonctrl-documentation) 11 | - [Functionality](#functionality) 12 | - [List of tested operating systems](#list-of-tested-operating-systems) 13 | - [Installation](#installation) 14 | - [Installation scripts overview](#installation-scripts-overview) 15 | - [Installation modes](#installation-modes) 16 | - [Installation for Ubuntu](#installation-for-ubuntu) 17 | - [Installation for Debian](#installation-for-debian) 18 | - [Telemetry](#telemetry) 19 | - [MyTonCtrl installer mode](#mytonctrl-installer-mode) 20 | - [Web admin panel](#web-admin-panel) 21 | - [Local copy of toncenter](#local-copy-of-toncenter) 22 | - [Useful links](#useful-links) 23 | 24 | 25 | # What is MyTonCtrl? 26 | 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. 27 | 28 | ![MyTonCtrl Status](screens/mytonctrl-status.png) 29 | 30 | # MyTonCtrl Documentation 31 | 32 | Mytonctrl's documentation can be found at https://docs.ton.org/participate/run-nodes/mytonctrl. 33 | 34 | # Functionality 35 | - [x] Show TON network status 36 | - [x] Management of local wallets 37 | - [x] Create local wallet 38 | - [x] Activate local wallet 39 | - [x] Show local wallets 40 | - [x] Import wallet from file (.pk) 41 | - [x] Save wallet address to file (.addr) 42 | - [x] Delete local wallet 43 | - [x] Show account status 44 | - [x] Show account balance 45 | - [x] Show account history 46 | - [x] Show account status from bookmarks 47 | - [x] Transferring funds to the wallet 48 | - [x] Transfer of a fixed amount 49 | - [x] Transfer of the entire amount (all) 50 | - [x] Transfer of the entire amount with wallet deactivation (alld) 51 | - [x] Transferring funds to the wallet from bookmarks 52 | - [x] Transferring funds to a wallet through a chain of self-deleting wallets 53 | - [x] Manage bookmarks 54 | - [x] Add account to bookmarks 55 | - [x] Show bookmarks 56 | - [x] Delete bookmark 57 | - [x] Offer management 58 | - [x] Show offers 59 | - [x] Vote for the proposal 60 | - [x] Automatic voting for previously voted proposals 61 | - [x] Controlling the validator 62 | - [x] Participate in the election of a validator 63 | - [x] Return bet + reward 64 | - [x] Autostart validator on abnormal termination (systemd) 65 | - [x] Send validator statistics to https://toncenter.com 66 | 67 | ## List of tested operating systems 68 | | Operating System | Status | 69 | |-------------------------------|----------------------------| 70 | | Ubuntu 16.04 LTS (Xenial Xerus) | Error: TON compilation error | 71 | | Ubuntu 18.04 LTS (Bionic Beaver) | OK | 72 | | Ubuntu 20.04 LTS (Focal Fossa) | OK | 73 | | Ubuntu 22.04 LTS (Jammy Jellyfish) | OK | 74 | | Debian 8 | Error: Unable to locate package libgsl-dev | 75 | | Debian 9 | Error: TON compilation error | 76 | | Debian 10 | OK | 77 | 78 | # Installation 79 | ## Installation scripts overview 80 | - `toninstaller.sh`: clones `TON` and` mytonctrl` sources to `/usr/src/ton` and`/usr/src/mytonctrl` folders, compiles programs from sources and writes them to `/usr/bin/`. 81 | - `mytoninstaller.py`: configures the validator and `mytonctrl`; generates validator connection keys. 82 | 83 | ## Installation modes 84 | 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. 85 | Use `validator` mode if you want to participate in the validator elections (you still can use that node as Liteserver). 86 | 87 | Learn more about node types: https://docs.ton.org/participate/nodes/node-types 88 | 89 | ## Installation for Ubuntu 90 | 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. 91 | ```sh 92 | wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh 93 | sudo bash install.sh -m 94 | ``` 95 | 96 | 2. Done. You can try to run the `mytonctrl` console now. 97 | ```sh 98 | mytonctrl 99 | ``` 100 | 101 | 102 | ## Installation for Debian 103 | 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. 104 | ```sh 105 | wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh 106 | su root -c 'bash install.sh -m ' 107 | ``` 108 | 109 | 2. Done. You can try to run the `mytonctrl` console now. 110 | ```sh 111 | mytonctrl 112 | ``` 113 | 114 | # Telemetry 115 | By default, `mytonctrl` sends validator statistics to the https://toncenter.com server. 116 | It is necessary to identify network abnormalities, as well as to quickly give feedback to developers. 117 | To disable telemetry during installation, use the `-t` flag: 118 | ```sh 119 | sudo bash install.sh -m -t 120 | ``` 121 | 122 | To disable telemetry after installation, do the following: 123 | ```sh 124 | MyTonCtrl> set sendTelemetry false 125 | ``` 126 | 127 | # MyTonCtrl installer mode 128 | 129 | ## Web admin panel 130 | To control the node/validator through the browser, you need to install an additional module: 131 | `mytonctrl` -> `installer` -> `enable JR` 132 | 133 | Next, you need to create a password for connection: 134 | `mytonctrl` -> `installer` -> `setwebpass` 135 | 136 | Ready. Now you can go to https://tonadmin.org site and log in with your credentials. 137 | git: https://github.com/igroman787/mtc-jsonrpc 138 | 139 | ## Local copy of toncenter 140 | To set up a local https://toncenter.com copy on your server, install an additional module: 141 | `mytonctrl` ->`installer` -> `enable PT` 142 | 143 | Ready. A local copy of toncenter is available at `http://:8000` 144 | git: https://github.com/igroman787/pytonv3 145 | 146 | # Useful links 147 | * https://docs.ton.org/ 148 | -------------------------------------------------------------------------------- /functions/session_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*- 3 | 4 | import os 5 | import sys 6 | import time 7 | import json 8 | sys.path.append("/usr/src/mytonctrl/") 9 | from mypylib.mypylib import Dict 10 | 11 | def read_session_stats(need_time_period): 12 | #print(f"read_session_stats: {need_time_period}") 13 | file_path = "/var/ton-work/log.session-stats" 14 | average_block_time = calculate_average_block_time(file_path) 15 | need_blocks = int(need_time_period/average_block_time) 16 | 17 | lines = read_last_lines(file_path, need_lines=need_blocks) 18 | #lines = read_all_lines(file_path) 19 | data = lines2data(lines) 20 | 21 | result = Dict() 22 | result.my_blocks = 0 23 | result.my_need_blocks = 0 24 | result.all_master_blocks = 0 25 | result.all_blocks = len(data) 26 | for buff in data: 27 | if buff.id.workchain == -1: 28 | result.all_master_blocks += 1 29 | first_producer = buff.rounds[0].producers[0] 30 | if buff.self != first_producer.id: 31 | continue 32 | result.my_need_blocks += 1 33 | if buff.self == buff.creator: 34 | result.my_blocks += 1 35 | #end for 36 | return result 37 | #end define 38 | 39 | def calculate_average_block_time(file_path): 40 | blocks = 100 41 | lines = read_last_lines(file_path, need_lines=blocks) 42 | data = lines2data(lines) 43 | first_block, last_block = get_first_last_block(data) 44 | 45 | diff = int(last_block.timestamp) - int(first_block.timestamp) 46 | average_block_time = round(diff/blocks, 2) 47 | #print("average_block_time:", average_block_time) 48 | return average_block_time 49 | #end define 50 | 51 | def get_first_last_block(data, last_index=-1): 52 | first_block = data[0] 53 | last_block = data[last_index] 54 | if first_block.id.workchain == last_block.id.workchain: 55 | blocks = int(last_block.id.seqno) - int(first_block.id.seqno) 56 | else: 57 | first_block, last_block = get_first_last_block(data, last_index=last_index-1) 58 | return first_block, last_block 59 | #end define 60 | 61 | def lines2data(lines): 62 | #start = time.time() 63 | data = list() 64 | for line in lines: 65 | try: 66 | buff = json.loads(line) 67 | data.append(Dict(buff)) 68 | except json.decoder.JSONDecodeError: 69 | pass 70 | #end for 71 | 72 | #end = time.time() 73 | #diff = round(end - start, 2) 74 | #print(f"lines2data completed in {diff} seconds") 75 | return data 76 | #end define 77 | 78 | def read_all_lines(file_path): 79 | #start = time.time() 80 | with open(file_path, 'rt') as file: 81 | text = file.read() 82 | #end with 83 | lines = text.split('\n') 84 | 85 | #end = time.time() 86 | #diff = round(end - start, 2) 87 | #print(f"read_all_lines completed in {diff} seconds") 88 | return lines 89 | #end define 90 | 91 | def read_last_lines(file_path, need_lines): 92 | lines = list() 93 | max_lines = 30000 94 | if need_lines < 1: 95 | return lines 96 | elif need_lines > max_lines: 97 | need_lines = max_lines 98 | #print(f"read_last_lines: {need_lines}") 99 | #start = time.time() 100 | with open(file_path, 'rb') as file: 101 | find_last_lines(file, need_lines) 102 | for i in range(need_lines): 103 | line = file.readline().decode() 104 | lines.append(line) 105 | #end with 106 | 107 | 108 | #end = time.time() 109 | #diff = round(end - start, 2) 110 | #print(f"read_last_lines {len(lines)} completed in {diff} seconds") 111 | return lines 112 | #end define 113 | 114 | def find_last_lines(file, need_lines): 115 | # catch OSError in case of a one line file 116 | try: 117 | find_lines = 0 118 | #file.seek(-2, os.SEEK_END) 119 | #while find_lines != need_lines: 120 | # if file.read(1) == b'\n': 121 | # find_lines += 1 122 | # file.seek(-2, os.SEEK_CUR) 123 | 124 | file.seek(-100, os.SEEK_END) 125 | while find_lines != need_lines: 126 | if b'\n' in file.read(100): 127 | find_lines += 1 128 | file.seek(-200, os.SEEK_CUR) 129 | except OSError: 130 | file.seek(0) 131 | #end define 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from dataclasses import dataclass 3 | 4 | from modules.module import MtcModule 5 | from modules.pool import PoolModule 6 | from modules.nominator_pool import NominatorPoolModule 7 | from modules.single_pool import SingleNominatorModule 8 | from modules.validator import ValidatorModule 9 | from modules.controller import ControllerModule 10 | from modules.liteserver import LiteserverModule 11 | from modules.alert_bot import AlertBotModule 12 | from modules.prometheus import PrometheusModule 13 | 14 | 15 | MODES = { 16 | 'validator': ValidatorModule, 17 | 'nominator-pool': NominatorPoolModule, 18 | 'single-nominator': SingleNominatorModule, 19 | 'liquid-staking': ControllerModule, 20 | 'liteserver': LiteserverModule, 21 | 'alert-bot': AlertBotModule, 22 | 'prometheus': PrometheusModule 23 | } 24 | 25 | 26 | def get_mode(mode_name: str) -> typing.Optional[MtcModule]: 27 | return MODES.get(mode_name) 28 | 29 | 30 | @dataclass 31 | class Setting: 32 | mode: typing.Optional[str] 33 | default_value: typing.Any 34 | description: str 35 | 36 | 37 | SETTINGS = { 38 | 'stake': Setting('validator', None, 'Stake amount'), 39 | 'stakePercent': Setting('validator', 99, 'Stake percent if `stake` is null'), 40 | 'isSlashing': Setting('validator', None, 'Create complaints to validators'), 41 | 'validatorWalletName': Setting('validator', 'wallet_001', 'Validator\'s wallet name'), 42 | 'maxFactor': Setting('validator', None, 'Param send to Elector. if null will be taken from 17 config param'), 43 | 'participateBeforeEnd': Setting('validator', None, 'Amount of seconds before start of round to participate'), 44 | 'liquid_pool_addr': Setting('liquid-staking', None, 'Liquid staking pool address'), 45 | 'min_loan': Setting('liquid-staking', 41000, 'Min loan amount'), 46 | 'max_loan': Setting('liquid-staking', 43000, 'Max loan amount'), 47 | 'max_interest_percent': Setting('liquid-staking', 10, 'Max interest percent'), 48 | 'duplicateSendfile': Setting(None, True, 'Duplicate external to public Liteservers'), 49 | 'sendTelemetry': Setting(None, True, 'Send node telemetry'), 50 | 'telemetryLiteUrl': Setting(None, 'https://telemetry.toncenter.com/report_status', 'Telemetry url'), 51 | 'overlayTelemetryUrl': Setting(None, 'https://telemetry.toncenter.com/report_overlays', 'Overlay telemetry url'), 52 | 'duplicateApi': Setting(None, 'sendTelemetry', 'Duplicate external to Toncenter'), 53 | 'duplicateApiUrl': Setting(None, 'https://[testnet.]toncenter.com/api/v2/sendBoc', 'Toncenter api url for duplicate'), 54 | 'checkAdnl': Setting(None, 'sendTelemetry', 'Check local udp port and adnl connection'), 55 | 'liteclient_timeout': Setting(None, 3, 'Liteclient default timeout'), 56 | 'console_timeout': Setting(None, 3, 'Validator console default timeout'), 57 | 'fift_timeout': Setting(None, 3, 'Fift default timeout'), 58 | 'useDefaultCustomOverlays': Setting(None, True, 'Participate in default custom overlays node eligible to'), 59 | 'defaultCustomOverlaysUrl': Setting(None, 'https://ton-blockchain.github.io/fallback_custom_overlays.json', 'Default custom overlays config url'), 60 | 'debug': Setting(None, False, 'Debug mtc console mode. Prints Traceback on errors'), 61 | 'subscribe_tg_channel': Setting('validator', False, 'Disables warning about subscribing to the `TON STATUS` channel'), 62 | 'auto_backup': Setting('validator', None, 'Make validator backup every election'), 63 | 'auto_backup_path': Setting('validator', '/tmp/mytoncore/auto_backups/', 'Path to store auto-backups'), 64 | 'prometheus_url': Setting('prometheus', None, 'Prometheus pushgateway url'), 65 | 'onlyNode': Setting(None, None, 'MyTonCtrl will work only for collecting validator telemetry (if `sendTelemetry` is True), without participating in Elections and etc.'), 66 | 'importGc': Setting(None, None, 'Delete imported archive blocks files. Restart mytoncore to apply this setting'), 67 | } 68 | 69 | 70 | def get_setting(name: str) -> typing.Optional[Setting]: 71 | return SETTINGS.get(name) 72 | 73 | 74 | def get_mode_settings(name: str): 75 | return {k: v for k, v in SETTINGS.items() if v.mode == name} 76 | 77 | -------------------------------------------------------------------------------- /modules/backups.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | import time 5 | 6 | import pkg_resources 7 | 8 | from modules.module import MtcModule 9 | from mypylib.mypylib import color_print, ip2int, run_as_root, parse 10 | from mytoninstaller.config import get_own_ip 11 | 12 | 13 | class BackupModule(MtcModule): 14 | 15 | def create_keyring(self, dir_name): 16 | keyring_dir = dir_name + '/keyring' 17 | self.ton.validatorConsole.Run(f'exportallprivatekeys {keyring_dir}') 18 | 19 | def create_tmp_ton_dir(self): 20 | result = self.ton.validatorConsole.Run("getconfig") 21 | text = parse(result, "---------", "--------") 22 | dir_name = self.ton.tempDir + f'/ton_backup_{int(time.time() * 1000)}' 23 | dir_name_db = dir_name + '/db' 24 | os.makedirs(dir_name_db) 25 | with open(dir_name_db + '/config.json', 'w') as f: 26 | f.write(text) 27 | self.create_keyring(dir_name_db) 28 | return dir_name 29 | 30 | @staticmethod 31 | def run_create_backup(args): 32 | backup_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/create_backup.sh') 33 | return subprocess.run(["bash", backup_script_path] + args, timeout=5) 34 | 35 | def create_backup(self, args): 36 | if len(args) > 1: 37 | color_print("{red}Bad args. Usage:{endc} create_backup [filename]") 38 | return 39 | tmp_dir = self.create_tmp_ton_dir() 40 | command_args = ["-m", self.ton.local.buffer.my_work_dir, "-t", tmp_dir] 41 | if len(args) == 1: 42 | command_args += ["-d", args[0]] 43 | process = self.run_create_backup(command_args) 44 | 45 | if process.returncode == 0: 46 | color_print("create_backup - {green}OK{endc}") 47 | else: 48 | color_print("create_backup - {red}Error{endc}") 49 | shutil.rmtree(tmp_dir) 50 | return process.returncode 51 | # end define 52 | 53 | @staticmethod 54 | def run_restore_backup(args): 55 | restore_script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/restore_backup.sh') 56 | return run_as_root(["bash", restore_script_path] + args) 57 | 58 | def restore_backup(self, args): 59 | if len(args) == 0 or len(args) > 3: 60 | color_print("{red}Bad args. Usage:{endc} restore_backup [-y] [--skip-create-backup]") 61 | return 62 | if '-y' not in args: 63 | res = input( 64 | f'This action will overwrite existing configuration with contents of backup archive, please make sure that donor node is not in operation prior to this action. Proceed [y/n]') 65 | if res.lower() != 'y': 66 | print('aborted.') 67 | return 68 | else: 69 | args.pop(args.index('-y')) 70 | if '--skip-create-backup' in args: 71 | args.pop(args.index('--skip-create-backup')) 72 | else: 73 | print('Before proceeding, mtc will create a backup of current configuration.') 74 | try: 75 | self.create_backup([]) 76 | except: 77 | color_print("{red}Could not create backup{endc}") 78 | 79 | ip = str(ip2int(get_own_ip())) 80 | command_args = ["-m", self.ton.local.buffer.my_work_dir, "-n", args[0], "-i", ip] 81 | 82 | if self.run_restore_backup(command_args) == 0: 83 | color_print("restore_backup - {green}OK{endc}") 84 | self.local.exit() 85 | else: 86 | color_print("restore_backup - {red}Error{endc}") 87 | # end define 88 | 89 | def add_console_commands(self, console): 90 | console.AddItem("create_backup", self.create_backup, self.local.translate("create_backup_cmd")) 91 | console.AddItem("restore_backup", self.restore_backup, self.local.translate("restore_backup_cmd")) 92 | -------------------------------------------------------------------------------- /modules/collator_config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | from mypylib.mypylib import color_print 5 | from modules.module import MtcModule 6 | 7 | 8 | class CollatorConfigModule(MtcModule): 9 | 10 | @staticmethod 11 | def check_config_url(url): 12 | try: 13 | r = requests.get(url, timeout=3) 14 | if r.status_code != 200: 15 | print(f'Failed to get config from {url}: {r.status_code} code; {r.text}') 16 | return 17 | return r.json() 18 | except Exception as e: 19 | print(f'Failed to get config from {url}: {e}') 20 | return 21 | 22 | @staticmethod 23 | def check_config_file(path): 24 | try: 25 | with open(path, 'r') as f: 26 | return json.load(f) 27 | except Exception as e: 28 | print(f'Failed to read config from {path}: {e}') 29 | return 30 | 31 | @staticmethod 32 | def get_config(path): 33 | if 'http' in path: 34 | config = CollatorConfigModule.check_config_url(path) 35 | else: 36 | config = CollatorConfigModule.check_config_file(path) 37 | if config is None: 38 | raise Exception(f'Failed to get config') 39 | return config 40 | 41 | def add_collator_config_to_vc(self, config: dict): 42 | self.local.add_log(f"Adding collator options config to validator console", "debug") 43 | path = self.ton.tempDir + f'/collator_config.json' 44 | with open(path, 'w') as f: 45 | json.dump(config, f) 46 | result = self.ton.validatorConsole.Run(f"setcollatoroptionsjson {path}") 47 | return 'success' in result, result 48 | 49 | def set_collator_config(self, args): 50 | if len(args) != 1: 51 | color_print("{red}Bad args. Usage:{endc} set_collator_config ") 52 | return 53 | location = args[0] 54 | config = self.get_config(location) 55 | self.ton.set_collator_config(location) 56 | added, msg = self.add_collator_config_to_vc(config) 57 | if not added: 58 | print(f'Failed to add collator config to validator console: {msg}') 59 | color_print("set_collator_config - {red}ERROR{endc}") 60 | return 61 | color_print("set_collator_config - {green}OK{endc}") 62 | 63 | def get_collator_config(self, args): 64 | location = self.ton.get_collator_config_location() 65 | print(f'Collator config location: {location}') 66 | path = self.ton.tempDir + f'/current_collator_config.json' 67 | output = self.ton.validatorConsole.Run(f'getcollatoroptionsjson {path}') 68 | if 'saved config to' not in output: 69 | print(f'Failed to get collator config: {output}') 70 | color_print("get_collator_config - {red}ERROR{endc}") 71 | return 72 | with open(path, 'r') as f: 73 | config = json.load(f) 74 | print(f'Collator config:') 75 | print(json.dumps(config, indent=4)) 76 | color_print("get_collator_config - {green}OK{endc}") 77 | 78 | def update_collator_config(self, args): 79 | location = self.ton.get_collator_config_location() 80 | config = self.get_config(location) 81 | added, msg = self.add_collator_config_to_vc(config) 82 | if not added: 83 | print(f'Failed to add collator config to validator console: {msg}') 84 | color_print("update_collator_config - {red}ERROR{endc}") 85 | return 86 | color_print("update_collator_config - {green}OK{endc}") 87 | 88 | def add_console_commands(self, console): 89 | console.AddItem("set_collator_config", self.set_collator_config, self.local.translate("set_collator_config_cmd")) 90 | console.AddItem("update_collator_config", self.update_collator_config, self.local.translate("update_collator_config_cmd")) 91 | console.AddItem("get_collator_config", self.get_collator_config, self.local.translate("get_collator_config_cmd")) 92 | -------------------------------------------------------------------------------- /modules/custom_overlays.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | 4 | from mypylib.mypylib import color_print 5 | from modules.module import MtcModule 6 | from mytoncore.utils import hex2base64 7 | 8 | 9 | class CustomOverlayModule(MtcModule): 10 | 11 | @staticmethod 12 | def parse_config(name: str, config: dict, vset: list = None): 13 | """ 14 | Converts config to validator-console friendly format 15 | :param name: custom overlay name 16 | :param config: config 17 | :param vset: list of validators adnl addresses, can be None if `@validators` not in config 18 | :return: 19 | """ 20 | result = { 21 | "name": name, 22 | "nodes": [] 23 | } 24 | for k, v in config.items(): 25 | if k == '@validators' and v: 26 | if vset is None: 27 | raise Exception("Validators set is not defined but @validators is in config") 28 | for v_adnl in vset: 29 | result["nodes"].append({ 30 | "adnl_id": hex2base64(v_adnl), 31 | "msg_sender": False, 32 | }) 33 | else: 34 | if "block_sender" in v: 35 | result["nodes"].append({ 36 | "adnl_id": hex2base64(k), 37 | "block_sender": v["block_sender"], 38 | }) 39 | elif "msg_sender" in v: 40 | result["nodes"].append({ 41 | "adnl_id": hex2base64(k), 42 | "msg_sender": v["msg_sender"], 43 | }) 44 | if v["msg_sender"]: 45 | result["nodes"][-1]["msg_sender_priority"] = v["msg_sender_priority"] 46 | else: 47 | raise Exception("Unknown node type") 48 | return result 49 | 50 | def add_custom_overlay(self, args): 51 | if len(args) != 2: 52 | color_print("{red}Bad args. Usage:{endc} add_custom_overlay ") 53 | return 54 | path = args[1] 55 | with open(path, 'r') as f: 56 | config = json.load(f) 57 | self.ton.set_custom_overlay(args[0], config) 58 | if '@validators' in config: 59 | print('Dynamic overlay will be added within 1 minute') 60 | else: 61 | result = self.add_custom_overlay_to_vc(self.parse_config(args[0], config)) 62 | if not result: 63 | print('Failed to add overlay to validator console') 64 | color_print("add_custom_overlay - {red}ERROR{endc}") 65 | return 66 | color_print("add_custom_overlay - {green}OK{endc}") 67 | 68 | def list_custom_overlays(self, args): 69 | if not self.ton.get_custom_overlays(): 70 | color_print("{red}No custom overlays{endc}") 71 | return 72 | for k, v in self.ton.get_custom_overlays().items(): 73 | color_print(f"Custom overlay {{bold}}{k}{{endc}}:") 74 | print(json.dumps(v, indent=4)) 75 | 76 | def delete_custom_overlay(self, args): 77 | if len(args) != 1: 78 | color_print("{red}Bad args. Usage:{endc} delete_custom_overlay ") 79 | return 80 | if '@validators' in self.ton.get_custom_overlays().get(args[0], {}): 81 | self.ton.delete_custom_overlay(args[0]) 82 | print('Dynamic overlay will be deleted within 1 minute') 83 | else: 84 | self.ton.delete_custom_overlay(args[0]) 85 | result = self.delete_custom_overlay_from_vc(args[0]) 86 | if not result: 87 | print('Failed to delete overlay from validator console') 88 | color_print("delete_custom_overlay - {red}ERROR{endc}") 89 | return 90 | color_print("delete_custom_overlay - {green}OK{endc}") 91 | 92 | def check_node_eligible_for_custom_overlay(self, config: dict): 93 | vconfig = self.ton.GetValidatorConfig() 94 | my_adnls = vconfig.adnl 95 | node_adnls = [i["adnl_id"] for i in config["nodes"]] 96 | for adnl in my_adnls: 97 | if adnl.id in node_adnls: 98 | return True 99 | return False 100 | 101 | def delete_custom_overlay_from_vc(self, name: str): 102 | result = self.ton.validatorConsole.Run(f"delcustomoverlay {name}") 103 | return 'success' in result 104 | 105 | def add_custom_overlay_to_vc(self, config: dict): 106 | if not self.check_node_eligible_for_custom_overlay(config): 107 | self.ton.local.add_log(f"Node has no adnl address required for custom overlay {config.get('name')}", "debug") 108 | return False 109 | self.ton.local.add_log(f"Adding custom overlay {config.get('name')}", "debug") 110 | path = self.ton.tempDir + f'/custom_overlay_{config["name"]}.json' 111 | with open(path, 'w') as f: 112 | json.dump(config, f) 113 | result = self.ton.validatorConsole.Run(f"addcustomoverlay {path}") 114 | return 'success' in result 115 | 116 | def custom_overlays(self): 117 | config = self.get_default_custom_overlay() 118 | if config is not None: 119 | self.ton.set_custom_overlay('default', config) 120 | self.deploy_custom_overlays() 121 | 122 | def deploy_custom_overlays(self): 123 | result = self.ton.validatorConsole.Run("showcustomoverlays") 124 | if 'unknown command' in result: 125 | return # node old version 126 | names = [] 127 | for line in result.split('\n'): 128 | if line.startswith('Overlay'): 129 | names.append(line.split(' ')[1].replace('"', '').replace(':', '')) 130 | 131 | config34 = self.ton.GetConfig34() 132 | current_el_id = config34['startWorkTime'] 133 | current_vset = [i["adnlAddr"] for i in config34['validators']] 134 | 135 | config36 = self.ton.GetConfig36() 136 | next_el_id = config36['startWorkTime'] if config36['validators'] else 0 137 | next_vset = [i["adnlAddr"] for i in config36['validators']] 138 | 139 | for name in names: 140 | # check that overlay still exists in mtc db 141 | pure_name = name 142 | suffix = name.split('_')[-1] 143 | if suffix.startswith('elid') and suffix.split('elid')[-1].isdigit(): # probably election id 144 | pure_name = '_'.join(name.split('_')[:-1]) 145 | el_id = int(suffix.split('elid')[-1]) 146 | if el_id not in (current_el_id, next_el_id): 147 | self.ton.local.add_log(f"Overlay {name} is not in current or next election, deleting", "debug") 148 | self.delete_custom_overlay_from_vc(name) # delete overlay if election id is not in current or next election 149 | continue 150 | 151 | if pure_name not in self.ton.get_custom_overlays(): 152 | self.ton.local.add_log(f"Overlay {name} ({pure_name}) is not in mtc db, deleting", "debug") 153 | self.delete_custom_overlay_from_vc(name) # delete overlay if it's not in mtc db 154 | 155 | for name, config in self.ton.get_custom_overlays().items(): 156 | if name in names: 157 | continue 158 | if '@validators' in config: 159 | new_name = name + '_elid' + str(current_el_id) 160 | if new_name not in names: 161 | node_config = self.parse_config(new_name, config, current_vset) 162 | self.add_custom_overlay_to_vc(node_config) 163 | 164 | if next_el_id != 0: 165 | new_name = name + '_elid' + str(next_el_id) 166 | if new_name not in names: 167 | node_config = self.parse_config(new_name, config, next_vset) 168 | self.add_custom_overlay_to_vc(node_config) 169 | else: 170 | node_config = self.parse_config(name, config) 171 | self.add_custom_overlay_to_vc(node_config) 172 | 173 | def get_default_custom_overlay(self): 174 | if not self.ton.local.db.get('useDefaultCustomOverlays', True): 175 | return None 176 | network = self.ton.GetNetworkName() 177 | default_url = 'https://ton-blockchain.github.io/fallback_custom_overlays.json' 178 | url = self.ton.local.db.get('defaultCustomOverlaysUrl', default_url) 179 | resp = requests.get(url, timeout=3) 180 | if resp.status_code != 200: 181 | self.ton.local.add_log(f"Failed to get default custom overlays from {url}", "error") 182 | return None 183 | config = resp.json() 184 | return config.get(network) 185 | 186 | def add_console_commands(self, console): 187 | console.AddItem("add_custom_overlay", self.add_custom_overlay, self.local.translate("add_custom_overlay_cmd")) 188 | console.AddItem("list_custom_overlays", self.list_custom_overlays, self.local.translate("list_custom_overlays_cmd")) 189 | console.AddItem("delete_custom_overlay", self.delete_custom_overlay, self.local.translate("delete_custom_overlay_cmd")) 190 | -------------------------------------------------------------------------------- /modules/liteserver.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | 3 | from modules.module import MtcModule 4 | 5 | 6 | class LiteserverModule(MtcModule): 7 | 8 | description = 'For liteserver usage only without validator.' 9 | default_value = False 10 | 11 | def add_console_commands(self, console): 12 | ... 13 | -------------------------------------------------------------------------------- /modules/module.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class MtcModule(ABC): 5 | 6 | description = '' # module text description 7 | default_value = True # is module enabled by default 8 | 9 | def __init__(self, ton, local, *args, **kwargs): 10 | from mytoncore.mytoncore import MyTonCore 11 | self.ton: MyTonCore = ton 12 | self.local = local 13 | 14 | @abstractmethod 15 | def add_console_commands(self, console): ... 16 | -------------------------------------------------------------------------------- /modules/nominator_pool.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from mypylib.mypylib import color_print 5 | from modules.pool import PoolModule 6 | 7 | 8 | class NominatorPoolModule(PoolModule): 9 | 10 | description = 'Standard nominator pools.' 11 | default_value = False 12 | 13 | def do_create_pool(self, pool_name, validator_reward_share_percent, max_nominators_count, min_validator_stake, 14 | min_nominator_stake): 15 | self.ton.local.add_log("start CreatePool function", "debug") 16 | validator_reward_share = int(validator_reward_share_percent * 100) 17 | 18 | self.check_download_pool_contract_scripts() 19 | 20 | file_path = self.ton.poolsDir + pool_name 21 | if os.path.isfile(file_path + ".addr"): 22 | self.ton.local.add_log("CreatePool warning: Pool already exists: " + file_path, "warning") 23 | return 24 | # end if 25 | 26 | fift_script = self.ton.contractsDir + "nominator-pool/func/new-pool.fif" 27 | wallet = self.ton.GetValidatorWallet() 28 | args = [fift_script, wallet.addrB64, validator_reward_share, max_nominators_count, min_validator_stake, 29 | min_nominator_stake, file_path] 30 | result = self.ton.fift.Run(args) 31 | if "Saved pool" not in result: 32 | raise Exception("CreatePool error: " + result) 33 | # end if 34 | 35 | pools = self.ton.GetPools() 36 | new_pool = self.ton.GetLocalPool(pool_name) 37 | for pool in pools: 38 | if pool.name != new_pool.name and pool.addrB64 == new_pool.addrB64: 39 | new_pool.Delete() 40 | raise Exception("CreatePool error: Pool with the same parameters already exists.") 41 | # end for 42 | # end define 43 | 44 | def new_pool(self, args): 45 | try: 46 | pool_name = args[0] 47 | validator_reward_share_percent = float(args[1]) 48 | max_nominators_count = int(args[2]) 49 | min_validator_stake = int(args[3]) 50 | min_nominator_stake = int(args[4]) 51 | except: 52 | color_print("{red}Bad args. Usage:{endc} new_pool ") 53 | return 54 | self.do_create_pool(pool_name, validator_reward_share_percent, max_nominators_count, min_validator_stake, min_nominator_stake) 55 | color_print("NewPool - {green}OK{endc}") 56 | 57 | def do_activate_pool(self, pool, ex=True): 58 | self.ton.local.add_log("start ActivatePool function", "debug") 59 | account = self.ton.GetAccount(pool.addrB64) 60 | if account.status == "empty": 61 | raise Exception("do_activate_pool error: account status is empty") 62 | elif account.status == "active": 63 | self.local.add_log("do_activate_pool warning: account status is active", "warning") 64 | else: 65 | validator_wallet = self.ton.GetValidatorWallet() 66 | self.ton.check_account_active(validator_wallet.addrB64) 67 | self.ton.SendFile(pool.bocFilePath, pool, timeout=False, remove=False) 68 | #end define 69 | 70 | def activate_pool(self, args): 71 | try: 72 | pool_name = args[0] 73 | except: 74 | color_print("{red}Bad args. Usage:{endc} activate_pool ") 75 | return 76 | pool = self.ton.GetLocalPool(pool_name) 77 | self.do_activate_pool(pool) 78 | color_print("ActivatePool - {green}OK{endc}") 79 | 80 | def update_validator_set(self, args): 81 | try: 82 | pool_addr = args[0] 83 | except: 84 | color_print("{red}Bad args. Usage:{endc} update_validator_set ") 85 | return 86 | wallet = self.ton.GetValidatorWallet() 87 | self.ton.PoolUpdateValidatorSet(pool_addr, wallet) 88 | color_print("UpdateValidatorSet - {green}OK{endc}") 89 | 90 | def do_deposit_to_pool(self, pool_addr, amount): 91 | wallet = self.ton.GetValidatorWallet() 92 | bocPath = self.ton.local.buffer.my_temp_dir + wallet.name + "validator-deposit-query.boc" 93 | fiftScript = self.ton.contractsDir + "nominator-pool/func/validator-deposit.fif" 94 | args = [fiftScript, bocPath] 95 | result = self.ton.fift.Run(args) 96 | resultFilePath = self.ton.SignBocWithWallet(wallet, bocPath, pool_addr, amount) 97 | self.ton.SendFile(resultFilePath, wallet) 98 | 99 | def deposit_to_pool(self, args): 100 | try: 101 | poll_addr = args[0] 102 | amount = float(args[1]) 103 | except: 104 | color_print("{red}Bad args. Usage:{endc} deposit_to_pool ") 105 | return 106 | self.do_deposit_to_pool(poll_addr, amount) 107 | color_print("DepositToPool - {green}OK{endc}") 108 | 109 | def do_withdraw_from_pool(self, pool_addr, amount): 110 | pool_data = self.ton.GetPoolData(pool_addr) 111 | if pool_data["state"] == 0: 112 | self.ton.WithdrawFromPoolProcess(pool_addr, amount) 113 | else: 114 | self.ton.PendWithdrawFromPool(pool_addr, amount) 115 | 116 | def withdraw_from_pool(self, args): 117 | try: 118 | pool_addr = args[0] 119 | amount = float(args[1]) 120 | except: 121 | color_print("{red}Bad args. Usage:{endc} withdraw_from_pool ") 122 | return 123 | self.do_withdraw_from_pool(pool_addr, amount) 124 | color_print("WithdrawFromPool - {green}OK{endc}") 125 | 126 | def add_console_commands(self, console): 127 | console.AddItem("new_pool", self.new_pool, self.local.translate("new_pool_cmd")) 128 | console.AddItem("activate_pool", self.activate_pool, self.local.translate("activate_pool_cmd")) 129 | console.AddItem("update_validator_set", self.update_validator_set, self.local.translate("update_validator_set_cmd")) 130 | console.AddItem("withdraw_from_pool", self.withdraw_from_pool, self.local.translate("withdraw_from_pool_cmd")) 131 | console.AddItem("deposit_to_pool", self.deposit_to_pool, self.local.translate("deposit_to_pool_cmd")) 132 | -------------------------------------------------------------------------------- /modules/pool.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from mypylib.mypylib import color_print, print_table 4 | from modules.module import MtcModule 5 | 6 | 7 | class PoolModule(MtcModule): 8 | 9 | description = 'Basic pools functions.' 10 | default_value = False 11 | 12 | def print_pools_list(self, args): 13 | table = list() 14 | table += [["Name", "Status", "Balance", "Version", "Address"]] 15 | data = self.ton.GetPools() 16 | if data is None or len(data) == 0: 17 | print("No data") 18 | return 19 | for pool in data: 20 | account = self.ton.GetAccount(pool.addrB64) 21 | if account.status != "active": 22 | pool.addrB64 = pool.addrB64_init 23 | version = self.ton.GetVersionFromCodeHash(account.codeHash) 24 | table += [[pool.name, account.status, account.balance, version, pool.addrB64]] 25 | print_table(table) 26 | 27 | def delete_pool(self, args): 28 | try: 29 | pool_name = args[0] 30 | except: 31 | color_print("{red}Bad args. Usage:{endc} delete_pool ") 32 | return 33 | pool = self.ton.GetLocalPool(pool_name) 34 | pool.Delete() 35 | color_print("DeletePool - {green}OK{endc}") 36 | # end define 37 | 38 | def do_import_pool(self, pool_name, addr_b64): 39 | self.check_download_pool_contract_scripts() 40 | addr_bytes = self.ton.addr_b64_to_bytes(addr_b64) 41 | pool_path = self.ton.poolsDir + pool_name 42 | with open(pool_path + ".addr", 'wb') as file: 43 | file.write(addr_bytes) 44 | # end define 45 | 46 | def import_pool(self, args): 47 | try: 48 | pool_name = args[0] 49 | pool_addr = args[1] 50 | except: 51 | color_print("{red}Bad args. Usage:{endc} import_pool ") 52 | return 53 | self.do_import_pool(pool_name, pool_addr) 54 | color_print("import_pool - {green}OK{endc}") 55 | 56 | def check_download_pool_contract_scripts(self): 57 | contract_path = self.ton.contractsDir + "nominator-pool/" 58 | if not os.path.isdir(contract_path): 59 | self.ton.DownloadContract("https://github.com/ton-blockchain/nominator-pool") 60 | 61 | def add_console_commands(self, console): 62 | console.AddItem("pools_list", self.print_pools_list, self.local.translate("pools_list_cmd")) 63 | console.AddItem("delete_pool", self.delete_pool, self.local.translate("delete_pool_cmd")) 64 | console.AddItem("import_pool", self.import_pool, self.local.translate("import_pool_cmd")) 65 | -------------------------------------------------------------------------------- /modules/prometheus.py: -------------------------------------------------------------------------------- 1 | from modules.module import MtcModule 2 | import dataclasses 3 | import requests 4 | 5 | 6 | @dataclasses.dataclass 7 | class Metric: 8 | name: str 9 | description: str 10 | type: str 11 | 12 | def to_format(self, value): 13 | return f""" 14 | # HELP {self.name} {self.description} 15 | # TYPE {self.name} {self.type} 16 | {self.name} {value} 17 | """ 18 | 19 | 20 | METRICS = { 21 | 'master_out_of_sync': Metric('validator_masterchain_out_of_sync_seconds', 'Time difference between current time and timestamp of the last known block', 'gauge'), 22 | 'shard_out_of_sync': Metric('validator_shardchain_out_of_sync_blocks', 'Number of blocks node\'s shardclient is behind the last known block', 'gauge'), 23 | 'out_of_ser': Metric('validator_out_of_serialization', 'Number of blocks last state serialization was ago', 'gauge'), 24 | 'vc_up': Metric('validator_console_up', 'Is `validator-console` up', 'gauge'), 25 | 'validator_id': Metric('validator_index', 'Validator index', 'gauge'), 26 | 'stake': Metric('validator_stake', 'Validator stake', 'gauge'), 27 | 'celldb_gc_block': Metric('validator_celldb_gc_block', 'Celldb GC block latency', 'gauge'), 28 | 'celldb_gc_state': Metric('validator_celldb_gc_state', 'Celldb GC queue size', 'gauge'), 29 | 'collated_master_ok': Metric('validator_blocks_collated_master_ok', 'Number of masterchain blocks successfully collated', 'gauge'), 30 | 'collated_master_err': Metric('validator_blocks_collated_master_err', 'Number of masterchain blocks failed to collate', 'gauge'), 31 | 'collated_shard_ok': Metric('validator_blocks_collated_shard_ok', 'Number of shardchain blocks successfully collated', 'gauge'), 32 | 'collated_shard_err': Metric('validator_blocks_collated_shard_err', 'Number of shardchain blocks failed to collate', 'gauge'), 33 | 'validated_master_ok': Metric('validator_blocks_validated_master_ok', 'Number of masterchain blocks successfully validated', 'gauge'), 34 | 'validated_master_err': Metric('validator_blocks_validated_master_err', 'Number of masterchain blocks failed to validate', 'gauge'), 35 | 'validated_shard_ok': Metric('validator_blocks_validated_shard_ok', 'Number of shardchain blocks successfully validated', 'gauge'), 36 | 'validated_shard_err': Metric('validator_blocks_validated_shard_err', 'Number of shardchain blocks failed to validate', 'gauge'), 37 | 'validator_groups_master': Metric('validator_active_groups_master', 'Number of masterchain validation groups validator participates in', 'gauge'), 38 | 'validator_groups_shard': Metric('validator_active_groups_shard', 'Number of shardchain validation groups validator participates in', 'gauge'), 39 | 'ls_queries_ok': Metric('validator_ls_queries_ok', 'Number of Liteserver successful queries', 'gauge'), 40 | 'ls_queries_err': Metric('validator_ls_queries_err', 'Number of Liteserver failed queries', 'gauge'), 41 | } 42 | 43 | 44 | class PrometheusModule(MtcModule): 45 | 46 | description = 'Prometheus format data exporter' 47 | default_value = False 48 | 49 | def __init__(self, ton, local, *args, **kwargs): 50 | super().__init__(ton, local, *args, **kwargs) 51 | 52 | def get_validator_status_metrics(self, result: list): 53 | status = self.ton.GetValidatorStatus() 54 | is_working = status.is_working or (status.unixtime is not None) 55 | if status.masterchain_out_of_sync is not None: 56 | result.append(METRICS['master_out_of_sync'].to_format(status.masterchain_out_of_sync)) 57 | if status.shardchain_out_of_sync is not None: 58 | result.append(METRICS['shard_out_of_sync'].to_format(status.shardchain_out_of_sync)) 59 | if status.stateserializerenabled and status.masterchain_out_of_ser is not None and status.stateserializermasterchainseqno != 0: 60 | result.append(METRICS['out_of_ser'].to_format(status.masterchain_out_of_ser)) 61 | if status.masterchainblock is not None and status.gcmasterchainblock is not None: 62 | result.append(METRICS['celldb_gc_block'].to_format(status.masterchainblock - status.gcmasterchainblock)) 63 | if status.gcmasterchainblock is not None and status.last_deleted_mc_state is not None: 64 | if status.last_deleted_mc_state != 0: 65 | result.append(METRICS['celldb_gc_state'].to_format(status.gcmasterchainblock - status.last_deleted_mc_state)) 66 | else: 67 | result.append(METRICS['celldb_gc_state'].to_format(-1)) 68 | if status.validator_groups_master is not None: 69 | result.append(METRICS['validator_groups_master'].to_format(status.validator_groups_master)) 70 | result.append(METRICS['validator_groups_shard'].to_format(status.validator_groups_shard)) 71 | result.append(METRICS['vc_up'].to_format(int(is_working))) 72 | 73 | def get_validator_validation_metrics(self, result: list): 74 | index = self.ton.GetValidatorIndex() 75 | result.append(METRICS['validator_id'].to_format(index)) 76 | config = self.ton.GetConfig34() 77 | save_elections = self.ton.GetSaveElections() 78 | elections = save_elections.get(str(config["startWorkTime"])) 79 | if elections is not None: 80 | adnl = self.ton.GetAdnlAddr() 81 | stake = elections.get(adnl, {}).get('stake') 82 | if stake: 83 | result.append(METRICS['stake'].to_format(round(stake, 2))) 84 | 85 | def get_node_stats_metrics(self, result: list): 86 | stats = self.ton.get_node_statistics() 87 | if stats and 'ls_queries' in stats: 88 | if stats['ls_queries']['time'] < 50: 89 | self.local.add_log(f'Liteserver queries time is too low: {stats}') 90 | return 91 | result.append(METRICS['ls_queries_ok'].to_format(stats['ls_queries']['ok'])) 92 | result.append(METRICS['ls_queries_err'].to_format(stats['ls_queries']['error'])) 93 | if stats and 'collated' in stats: 94 | result.append(METRICS['collated_master_ok'].to_format(stats['collated']['master']['ok'])) 95 | result.append(METRICS['collated_master_err'].to_format(stats['collated']['master']['error'])) 96 | result.append(METRICS['collated_shard_ok'].to_format(stats['collated']['shard']['ok'])) 97 | result.append(METRICS['collated_shard_err'].to_format(stats['collated']['shard']['error'])) 98 | if stats and 'validated' in stats: 99 | result.append(METRICS['validated_master_ok'].to_format(stats['validated']['master']['ok'])) 100 | result.append(METRICS['validated_master_err'].to_format(stats['validated']['master']['error'])) 101 | result.append(METRICS['validated_shard_ok'].to_format(stats['validated']['shard']['ok'])) 102 | result.append(METRICS['validated_shard_err'].to_format(stats['validated']['shard']['error'])) 103 | 104 | def push_metrics(self): 105 | if not self.ton.using_prometheus(): 106 | return 107 | 108 | url = self.ton.local.db.get('prometheus_url') 109 | if url is None: 110 | raise Exception('Prometheus url is not set') 111 | metrics = [] 112 | self.local.try_function(self.get_validator_status_metrics, args=[metrics]) 113 | self.local.try_function(self.get_validator_validation_metrics, args=[metrics]) 114 | self.local.try_function(self.get_node_stats_metrics, args=[metrics]) 115 | requests.post(url, data='\n'.join(metrics).encode()) 116 | 117 | def add_console_commands(self, console): 118 | ... 119 | -------------------------------------------------------------------------------- /modules/single_pool.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pkg_resources 4 | 5 | from mypylib.mypylib import color_print 6 | from modules.pool import PoolModule 7 | 8 | 9 | class SingleNominatorModule(PoolModule): 10 | 11 | description = 'Orbs\'s single nominator pools.' 12 | default_value = False 13 | 14 | def do_create_single_pool(self, pool_name, owner_address): 15 | self.ton.local.add_log("start create_single_pool function", "debug") 16 | 17 | self.check_download_pool_contract_scripts() 18 | 19 | file_path = self.ton.poolsDir + pool_name 20 | if os.path.isfile(file_path + ".addr"): 21 | self.ton.local.add_log("create_single_pool warning: Pool already exists: " + file_path, "warning") 22 | return 23 | 24 | fift_script = pkg_resources.resource_filename('mytoncore', 'contracts/single-nominator-pool/init.fif') 25 | code_boc = pkg_resources.resource_filename('mytoncore', 26 | 'contracts/single-nominator-pool/single-nominator-code.hex') 27 | validator_wallet = self.ton.GetValidatorWallet() 28 | args = [fift_script, code_boc, owner_address, validator_wallet.addrB64, file_path] 29 | result = self.ton.fift.Run(args) 30 | if "Saved single nominator pool" not in result: 31 | raise Exception("create_single_pool error: " + result) 32 | 33 | pools = self.ton.GetPools() 34 | new_pool = self.ton.GetLocalPool(pool_name) 35 | for pool in pools: 36 | if pool.name != new_pool.name and pool.addrB64 == new_pool.addrB64: 37 | new_pool.Delete() 38 | raise Exception("create_single_pool error: Pool with the same parameters already exists.") 39 | 40 | def new_single_pool(self, args): 41 | try: 42 | pool_name = args[0] 43 | owner_address = args[1] 44 | except: 45 | color_print("{red}Bad args. Usage:{endc} new_single_pool ") 46 | return 47 | self.do_create_single_pool(pool_name, owner_address) 48 | color_print("new_single_pool - {green}OK{endc}") 49 | 50 | def do_activate_single_pool(self, pool): 51 | self.local.add_log("start activate_single_pool function", "debug") 52 | boc_mode = "--with-init" 53 | validator_wallet = self.ton.GetValidatorWallet() 54 | self.ton.check_account_active(validator_wallet.addrB64) 55 | result_file_path = self.ton.SignBocWithWallet(validator_wallet, pool.bocFilePath, pool.addrB64_init, 1, boc_mode=boc_mode) 56 | self.ton.SendFile(result_file_path, validator_wallet) 57 | 58 | def activate_single_pool(self, args): 59 | try: 60 | pool_name = args[0] 61 | except: 62 | color_print("{red}Bad args. Usage:{endc} activate_single_pool ") 63 | return 64 | pool = self.ton.GetLocalPool(pool_name) 65 | if not os.path.isfile(pool.bocFilePath): 66 | self.local.add_log(f"Pool {pool_name} already activated", "warning") 67 | return 68 | self.do_activate_single_pool(pool) 69 | color_print("activate_single_pool - {green}OK{endc}") 70 | 71 | def withdraw_from_single_pool(self, args): 72 | try: 73 | pool_addr = args[0] 74 | amount = float(args[1]) 75 | except: 76 | color_print("{red}Bad args. Usage:{endc} withdraw_from_single_pool ") 77 | return 78 | self.ton.WithdrawFromPoolProcess(pool_addr, amount) 79 | color_print("withdraw_from_single_pool - {green}OK{endc}") 80 | #end define 81 | 82 | def add_console_commands(self, console): 83 | console.AddItem("new_single_pool", self.new_single_pool, self.local.translate("new_single_pool_cmd")) 84 | console.AddItem("activate_single_pool", self.activate_single_pool, self.local.translate("activate_single_pool_cmd")) 85 | console.AddItem("withdraw_from_single_pool", self.withdraw_from_single_pool, self.local.translate("withdraw_from_single_pool_cmd")) 86 | -------------------------------------------------------------------------------- /modules/validator.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from mypylib.mypylib import color_print, get_timestamp 4 | from modules.module import MtcModule 5 | from mytonctrl.utils import timestamp2utcdatetime, GetColorInt 6 | 7 | 8 | class ValidatorModule(MtcModule): 9 | 10 | description = ('Validator functions. Activates participating in elections and staking. ' 11 | 'If pools and l/s modes are disabled stakes from validator wallet.') 12 | 13 | default_value = True 14 | 15 | def vote_offer(self, args): 16 | if len(args) == 0: 17 | color_print("{red}Bad args. Usage:{endc} vo ") 18 | return 19 | for offerHash in args: 20 | self.ton.VoteOffer(offerHash) 21 | color_print("VoteOffer - {green}OK{endc}") 22 | 23 | def vote_election_entry(self, args): 24 | from mytoncore.functions import Elections 25 | Elections(self.ton.local, self.ton) 26 | color_print("VoteElectionEntry - {green}OK{endc}") 27 | 28 | def vote_complaint(self, args): 29 | try: 30 | election_id = args[0] 31 | complaint_hash = args[1] 32 | except: 33 | color_print("{red}Bad args. Usage:{endc} vc ") 34 | return 35 | self.ton.VoteComplaint(election_id, complaint_hash) 36 | color_print("VoteComplaint - {green}OK{endc}") 37 | 38 | def find_myself(self, validators: list) -> dict: 39 | adnl_addr = self.ton.GetAdnlAddr() 40 | for validator in validators: 41 | if validator.get("adnlAddr") == adnl_addr: 42 | return validator 43 | return None 44 | 45 | def check_efficiency(self, args): 46 | self.local.add_log("start GetValidatorEfficiency function", "debug") 47 | previous_validators = self.ton.GetValidatorsList(past=True) 48 | validators = self.ton.GetValidatorsList() 49 | validator = self.find_myself(previous_validators) 50 | config32 = self.ton.GetConfig32() 51 | config34 = self.ton.GetConfig34() 52 | color_print("{cyan}===[ Validator efficiency ]==={endc}") 53 | start_time = timestamp2utcdatetime(config32.startWorkTime) 54 | end_time = timestamp2utcdatetime(config32.endWorkTime) 55 | color_print(f"Previous round time: {{yellow}}from {start_time} to {end_time}{{endc}}") 56 | if validator: 57 | if validator.get('efficiency') is None: 58 | print('Failed to get efficiency for the previous round') 59 | elif validator.is_masterchain is False and validator.get('efficiency') != 0: 60 | print(f"Validator index is greater than {config32['mainValidators']} in the previous round - no efficiency data.") 61 | else: 62 | efficiency = 100 if validator.efficiency > 100 else validator.efficiency 63 | color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%") 64 | created = validator.master_blocks_created 65 | expected = validator.master_blocks_expected 66 | if created is None: # there is no updated prev round info in cache 67 | created = validator.blocks_created 68 | expected = validator.blocks_expected 69 | color_print(f"Previous round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}") 70 | else: 71 | print("Couldn't find this validator in the previous round") 72 | validator = self.find_myself(validators) 73 | start_time = timestamp2utcdatetime(config34.startWorkTime) 74 | end_time = timestamp2utcdatetime(int(get_timestamp())) 75 | color_print(f"Current round time: {{green}}from {start_time} to {end_time}{{endc}}") 76 | if validator: 77 | if validator.is_masterchain is False and validator.efficiency != 0: 78 | print(f"Validator index is greater than {config34['mainValidators']} in the current round - no efficiency data.") 79 | elif (time.time() - config34.startWorkTime) / (config34.endWorkTime - config34.startWorkTime) < 0.8: 80 | print("The validation round has started recently, there is not enough data yet. " 81 | "The efficiency evaluation will become more accurate towards the end of the round.") 82 | elif validator.get('efficiency') is None: 83 | print('Failed to get efficiency for the current round') 84 | else: 85 | efficiency = 100 if validator.efficiency > 100 else validator.efficiency 86 | color_efficiency = GetColorInt(efficiency, 90, logic="more", ending="%") 87 | created = validator.master_blocks_created 88 | expected = validator.master_blocks_expected 89 | color_print(f"Current round efficiency: {color_efficiency} {{yellow}}({created} blocks created / {round(expected, 1)} blocks expected){{endc}}") 90 | else: 91 | print("Couldn't find this validator in the current round") 92 | # end define 93 | 94 | def get_my_complaint(self): 95 | config32 = self.ton.GetConfig32() 96 | save_complaints = self.ton.GetSaveComplaints() 97 | complaints = save_complaints.get(str(config32['startWorkTime'])) 98 | if not complaints: 99 | return 100 | for c in complaints.values(): 101 | if c["adnl"] == self.ton.GetAdnlAddr() and c["isPassed"]: 102 | return c 103 | # end define 104 | 105 | def add_console_commands(self, console): 106 | console.AddItem("vo", self.vote_offer, self.local.translate("vo_cmd")) 107 | console.AddItem("ve", self.vote_election_entry, self.local.translate("ve_cmd")) 108 | console.AddItem("vc", self.vote_complaint, self.local.translate("vc_cmd")) 109 | console.AddItem("check_ef", self.check_efficiency, self.local.translate("check_ef_cmd")) 110 | -------------------------------------------------------------------------------- /modules/wallet.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import os 3 | 4 | from modules.module import MtcModule 5 | from mypylib.mypylib import color_print, print_table 6 | 7 | 8 | class WalletModule(MtcModule): 9 | 10 | description = '' 11 | default_value = False 12 | 13 | def create_new_wallet(self, args): 14 | version = "v1" 15 | try: 16 | if len(args) == 0: 17 | walletName = self.ton.GenerateWalletName() 18 | workchain = 0 19 | else: 20 | workchain = int(args[0]) 21 | walletName = args[1] 22 | if len(args) > 2: 23 | version = args[2] 24 | if len(args) == 4: 25 | subwallet = int(args[3]) 26 | else: 27 | subwallet = 698983191 + workchain # 0x29A9A317 + workchain 28 | except: 29 | color_print("{red}Bad args. Usage:{endc} nw [ ]") 30 | return 31 | wallet = self.ton.CreateWallet(walletName, workchain, version, subwallet=subwallet) 32 | table = list() 33 | table += [["Name", "Workchain", "Address"]] 34 | table += [[wallet.name, wallet.workchain, wallet.addrB64_init]] 35 | print_table(table) 36 | # end define 37 | 38 | def _wallets_check(self): 39 | self.local.add_log("start WalletsCheck function", "debug") 40 | wallets = self.get_wallets() 41 | for wallet in wallets: 42 | if os.path.isfile(wallet.bocFilePath): 43 | account = self.ton.GetAccount(wallet.addrB64) 44 | if account.balance > 0: 45 | self.ton.SendFile(wallet.bocFilePath, wallet) 46 | # end define 47 | 48 | def activate_wallet(self, args): 49 | try: 50 | walletName = args[0] 51 | except Exception as err: 52 | walletName = "all" 53 | if walletName == "all": 54 | self._wallets_check() 55 | else: 56 | wallet = self.ton.GetLocalWallet(walletName) 57 | self.ton.ActivateWallet(wallet) 58 | color_print("ActivateWallet - {green}OK{endc}") 59 | # end define 60 | 61 | def get_wallets(self): 62 | self.local.add_log("start GetWallets function", "debug") 63 | wallets = list() 64 | wallets_name_list = self.ton.GetWalletsNameList() 65 | for walletName in wallets_name_list: 66 | wallet = self.ton.GetLocalWallet(walletName) 67 | wallets.append(wallet) 68 | return wallets 69 | # end define 70 | 71 | def print_wallets_list(self, args): 72 | table = list() 73 | table += [["Name", "Status", "Balance", "Ver", "Wch", "Address"]] 74 | data = self.get_wallets() 75 | if data is None or len(data) == 0: 76 | print("No data") 77 | return 78 | for wallet in data: 79 | account = self.ton.GetAccount(wallet.addrB64) 80 | if account.status != "active": 81 | wallet.addrB64 = wallet.addrB64_init 82 | table += [[wallet.name, account.status, account.balance, wallet.version, wallet.workchain, wallet.addrB64]] 83 | print_table(table) 84 | # end define 85 | 86 | def do_import_wallet(self, addr_b64, key): 87 | addr_bytes = self.ton.addr_b64_to_bytes(addr_b64) 88 | pk_bytes = base64.b64decode(key) 89 | wallet_name = self.ton.GenerateWalletName() 90 | wallet_path = self.ton.walletsDir + wallet_name 91 | with open(wallet_path + ".addr", 'wb') as file: 92 | file.write(addr_bytes) 93 | with open(wallet_path + ".pk", 'wb') as file: 94 | file.write(pk_bytes) 95 | return wallet_name 96 | # end define 97 | 98 | def import_wallet(self, args): 99 | try: 100 | addr = args[0] 101 | key = args[1] 102 | except: 103 | color_print("{red}Bad args. Usage:{endc} iw ") 104 | return 105 | name = self.do_import_wallet(addr, key) 106 | print("Wallet name:", name) 107 | # end define 108 | 109 | def set_wallet_version(self, args): 110 | try: 111 | addr = args[0] 112 | version = args[1] 113 | except: 114 | color_print("{red}Bad args. Usage:{endc} swv ") 115 | return 116 | self.ton.SetWalletVersion(addr, version) 117 | color_print("SetWalletVersion - {green}OK{endc}") 118 | # end define 119 | 120 | def do_export_wallet(self, wallet_name): 121 | wallet = self.ton.GetLocalWallet(wallet_name) 122 | with open(wallet.privFilePath, 'rb') as file: 123 | data = file.read() 124 | key = base64.b64encode(data).decode("utf-8") 125 | return wallet.addrB64, key 126 | # end define 127 | 128 | def export_wallet(self, args): 129 | try: 130 | name = args[0] 131 | except: 132 | color_print("{red}Bad args. Usage:{endc} ew ") 133 | return 134 | addr, key = self.do_export_wallet(name) 135 | print("Wallet name:", name) 136 | print("Address:", addr) 137 | print("Secret key:", key) 138 | # end define 139 | 140 | def delete_wallet(self, args): 141 | try: 142 | wallet_name = args[0] 143 | except: 144 | color_print("{red}Bad args. Usage:{endc} dw ") 145 | return 146 | if input("Are you sure you want to delete this wallet (yes/no): ") != "yes": 147 | print("Cancel wallet deletion") 148 | return 149 | wallet = self.ton.GetLocalWallet(wallet_name) 150 | wallet.Delete() 151 | color_print("DeleteWallet - {green}OK{endc}") 152 | # end define 153 | 154 | def move_coins(self, args): 155 | try: 156 | wallet_name = args[0] 157 | destination = args[1] 158 | amount = args[2] 159 | flags = args[3:] 160 | except: 161 | color_print("{red}Bad args. Usage:{endc} mg ") 162 | return 163 | wallet = self.ton.GetLocalWallet(wallet_name) 164 | destination = self.ton.get_destination_addr(destination) 165 | self.ton.MoveCoins(wallet, destination, amount, flags=flags) 166 | color_print("MoveCoins - {green}OK{endc}") 167 | # end define 168 | 169 | def do_move_coins_through_proxy(self, wallet, dest, coins): 170 | self.local.add_log("start MoveCoinsThroughProxy function", "debug") 171 | wallet1 = self.ton.CreateWallet("proxy_wallet1", 0) 172 | wallet2 = self.ton.CreateWallet("proxy_wallet2", 0) 173 | self.ton.MoveCoins(wallet, wallet1.addrB64_init, coins) 174 | self.ton.ActivateWallet(wallet1) 175 | self.ton.MoveCoins(wallet1, wallet2.addrB64_init, "alld") 176 | self.ton.ActivateWallet(wallet2) 177 | self.ton.MoveCoins(wallet2, dest, "alld", flags=["-n"]) 178 | wallet1.Delete() 179 | wallet2.Delete() 180 | # end define 181 | 182 | def move_coins_through_proxy(self, args): 183 | try: 184 | wallet_name = args[0] 185 | destination = args[1] 186 | amount = args[2] 187 | except: 188 | color_print("{red}Bad args. Usage:{endc} mgtp ") 189 | return 190 | wallet = self.ton.GetLocalWallet(wallet_name) 191 | destination = self.ton.get_destination_addr(destination) 192 | self.do_move_coins_through_proxy(wallet, destination, amount) 193 | color_print("MoveCoinsThroughProxy - {green}OK{endc}") 194 | # end define 195 | 196 | def add_console_commands(self, console): 197 | console.AddItem("nw", self.create_new_wallet, self.local.translate("nw_cmd")) 198 | console.AddItem("aw", self.activate_wallet, self.local.translate("aw_cmd")) 199 | console.AddItem("wl", self.print_wallets_list, self.local.translate("wl_cmd")) 200 | console.AddItem("iw", self.import_wallet, self.local.translate("iw_cmd")) 201 | console.AddItem("swv", self.set_wallet_version, self.local.translate("swv_cmd")) 202 | console.AddItem("ew", self.export_wallet, self.local.translate("ex_cmd")) 203 | console.AddItem("dw", self.delete_wallet, self.local.translate("dw_cmd")) 204 | console.AddItem("mg", self.move_coins, self.local.translate("mg_cmd")) 205 | console.AddItem("mgtp", self.move_coins_through_proxy, self.local.translate("mgtp_cmd")) 206 | -------------------------------------------------------------------------------- /mytoncore/__init__.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | from .mytoncore import * 3 | from mypylib.mypylib import MyPyClass 4 | from mypyconsole.mypyconsole import MyPyConsole 5 | -------------------------------------------------------------------------------- /mytoncore/__main__.py: -------------------------------------------------------------------------------- 1 | from mytoncore.functions import mytoncore 2 | 3 | 4 | if __name__ == '__main__': 5 | mytoncore() 6 | -------------------------------------------------------------------------------- /mytoncore/complaints/remove-proofs-v2.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." " cr 5 | ."Removes proof cells from complaint." cr cr 6 | 7 | ." is a filename of the serialized TL-B ValidatorComplaint boc." cr 8 | ."Saves the result boc into ``." cr 1 halt 9 | } : usage 10 | $# 2 = { cr } { usage } cond 11 | 12 | $1 =: filename 13 | $2 =: savefile 14 | 15 | filename file>B dup 16 | 8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if 17 | B>boc =: complaint 18 | 19 | ."got: " cr 20 | complaint ref, 33 | ref, 34 | b> // s, c 35 | } : clear_producer_info 36 | 37 | { // c 38 | // c 47 | } : clean_descr 48 | 49 | { // c 50 | // c 61 | } : clean_descr_with_diff 62 | 63 | 64 | // prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block) 65 | // prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo; 66 | // 67 | // no_blk_gen#450e8bd9 from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr; 68 | // no_blk_gen_diff#c737b0ca prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr; 69 | // 70 | // 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; 71 | 72 | complaint =: result_cell 84 | 85 | "result: " type cr 86 | result_cell B 89 | savefile tuck B>file 90 | ."(Saved to file " type .")" cr 91 | -------------------------------------------------------------------------------- /mytoncore/contracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/mytoncore/contracts/__init__.py -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/HOWTO-deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy single-nominator-pool 2 | 3 | ### 1. Generate state-init 4 | Command: 5 | ``` 6 | ./init.fif 7 | ``` 8 | 9 | Example: 10 | ``` 11 | ./init.fif snominator-code.hex EQDYDK1NivLsfSVxYE1aUt5xU-behhWSin29vgE7M6wzLMjN Ef8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAU 12 | ``` 13 | 14 | ### 2. Sign and send a message 15 | 16 | Command: 17 | ``` 18 | ./wallet-v3.fif 2 -n -I snominator-init.boc 19 | ``` 20 | 21 | Example: 22 | ``` 23 | ./wallet-v3.fif mywallet Ef9rfl-0S4wuAs6-rwl6RgjXznkhQaZNvlq9jMDHBlDpMe8h 698983191 7 1 -n -I snominator-init.boc 24 | ``` 25 | Expects to have `mywallet.addr` `mywallet.pk` files. 26 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/README.md: -------------------------------------------------------------------------------- 1 | # Fift scripts for single-validator contract 2 | 3 | ### Init 4 | 5 | usage: `./init.fif ` \ 6 | Creates a state-init to deploy a single-nominator-pool contract. 7 | 8 | `` is a filename of the compiled contract code BoC bytes or HEX. \ 9 | Saves the contract address in `snominator.addr`. \ 10 | Saves the init boc into `snominator-state-init.boc`. 11 | 12 | ### Wallet V3 (Modded) 13 | 14 | > 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. 15 | 16 | usage: 17 | ``` 18 | ./wallet-v3.fif 19 | 20 | [-x *] 21 | [-n|-b] [-t] [-B ] 22 | [-C ] [-I ] [] 23 | ``` 24 | 25 | Creates a request to advanced wallet created by `new-wallet-v3.fif`, \ 26 | with private key loaded from file `.pk` \ 27 | and address from `.addr`, and saves it \ 28 | into `.boc` (`wallet-query.boc` by default). 29 | 30 | ### Withdraw 31 | 32 | usage: `./withdraw.fif ` \ 33 | Creates a message body to withdraw from a single-nominator pool. 34 | 35 | ### Upgrade 36 | 37 | usage: `./upgrade.fif ` \ 38 | Creates a message body to update the nominator's code. \ 39 | Takes `` - BoC file path as argument. \ 40 | Saves the result into `upgrade.boc`. 41 | 42 | ### Change Validator Address 43 | 44 | usage: `./change-validator.fif ` \ 45 | Takes user friendly address as parameter - not file. \ 46 | Creates change validator action msg body BoC. \ 47 | Saves it into `change-validator.boc`. 48 | 49 | ### Send Raw Message 50 | 51 | usage: `./send-raw-msg.fif ` \ 52 | Creates a request to send a message through single-nominator-poll. \ 53 | `` - BoC full msg file path. \ 54 | Saves the result msg body into `send-raw-msg.boc`. 55 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/build.sh: -------------------------------------------------------------------------------- 1 | /usr/bin/ton/crypto/func -APS /usr/src/ton/crypto/smartcont/stdlib.fc snominator-code.fc -W snominator-code.boc -o snominator-code.fif 2 | /usr/bin/ton/crypto/fift snominator-code.fif 3 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/change-validator.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | "GetOpt.fif" include 5 | 6 | { ."usage: " @' $0 type ." " cr 7 | ."Takes user friendly address as parameter - not file." cr 8 | ."Creates change validator action msg body BoC." cr 9 | ."Saves it into `change-validator.boc`." cr 1 halt 10 | } : usage 11 | $# 1 = { } { usage } cond 12 | 13 | true constant bounce 14 | true =: allow-bounce 15 | false =: force-bounce 16 | 17 | $1 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: to_addr 18 | 19 | =: body_boc 20 | body_boc B 22 | "change-validator.boc" tuck B>file 23 | ."(Saved query to file " type .")" cr 24 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/init.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." " cr 5 | ."Creates a state-init to deploy a single-nominator-pool contract." cr cr 6 | 7 | ." is a filename of the compiled contract code BoC bytes or HEX." cr 8 | ."Saves the contract address in `.addr`." cr 9 | ."Saves the init boc into `-init.boc`." cr 1 halt 10 | } : usage 11 | $# 4 = { cr } { usage } cond 12 | 13 | $1 =: filename 14 | $2 parse-smc-addr drop 2=: owner-addr 15 | $3 parse-smc-addr drop 2=: validator-addr 16 | $4 =: file-base 17 | 18 | filename file>B dup 19 | 8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if 20 | B>boc =: new-code-boc 21 | 22 | -1 =: wc // masterchain 23 | 1 Gram* =: ton-amount 24 | 25 | // data 27 | dup =: init-boc 28 | 2 boc+>B 29 | dup ."StateInit: " B>base64 type cr cr 30 | dup ."HEX: " Bx. cr 31 | file-base +"-query.boc" tuck B>file 32 | ."(Saved single nominator pool init into " type .")" cr cr 33 | 34 | init-boc hashu wc swap 2dup 2=: dest-addr 35 | ."New pool address = " 2dup .addr cr 36 | 37 | 2dup file-base +".addr" save-address-verbose cr 38 | 39 | ."Non-bounceable address (for init): " 2dup 7 .Addr cr 40 | ."Bounceable address (for later access): " 6 .Addr cr cr 41 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/recover-stake.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." []" cr 5 | ."Creates the message body to be sent from a validator controlling smart contract (wallet) to recover its share of unfrozen stakes and bonuses." cr 6 | ."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 7 | } : usage 8 | 9 | $# dup 0 < swap 1 > or ' usage if 10 | def? $1 { @' $1 } { "recover-query.boc" } cond constant output_fname 11 | now constant query_id 12 | ."query_id for stake recovery message is set to " query_id . cr 13 | 14 | 15 | cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr 18 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/send-raw-msg.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." " cr cr 5 | ."Creates a request to send a message through single-nominator-poll." cr 6 | ." - BoC full msg file path." cr cr 7 | ."Saves the result msg body into `send-raw-msg.boc`." cr 1 halt 8 | } : usage 9 | $# 2 = { } { usage } cond 10 | 11 | $1 file>B B>boc =: msg 12 | $2 (number) 1 <> abort"not an integer: check your send-mode" =: mode 13 | 14 | 2 boc+>B 15 | ."Message body is " dup B>base64 type cr cr 16 | ."HEX: " dup Bx. cr cr 17 | 18 | "send-raw-msg.boc" tuck B>file ."Saved to " type cr 19 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/single-nominator-code.hex: -------------------------------------------------------------------------------- 1 | b5ee9c7241020d010001f0000114ff00f4a413f4bcf2c80b01020162050202012004030015bfe5076a2687d207d2068c0027bdf8cb938b82a38002a380036b6aa39152988b6c02bcd0ed44d0fa40fa40d122c700925f06e003d0d3030171b0925f06e0fa403002d31f7022c000228b1778c705b022d74ac000b08e136c21830bc85376a182103b9aca00a1fa02c9d09430d33f12e25343c7059133e30d5235c705925f06e30d0b0604f22382104e73744bba8fe102fa4430f828fa443081200302c0ff12f2f4830c01c0fff2f481200122f2f481200524821047868c00bef2f4fa0020db3c300581200405a182103b9aca00a15210bb14f2f4db3c82104e73744bc8cb1f5220cb3f5005cf16c9443080188040db3c9410356c41e201821047657424ba0a080c0702368f16821047657424c8cb1fcb3fc9db3c705880188040db3c9130e2080c011671f833d0d70bff7f01db3c09001674c8cb0212ca07cbffc9d0001cd3ff31d31fd31f31d3ff31d431d101c421830bba8ea0fa005387a182103b9aca00a112b60881200421c200f2f452406d80188040db3cde21811001ba9efa405044c858cf1601cf16c9ed549133e220817702ba9802d307d402fb0002de2082009903ba9d02d4812002226ef2f201fb0402de0c0048226eb32091719170e203c8cb055006cf165004fa02cb6a039358cc019130e201c901fb00fb5470d1 2 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/single-nominator.tlb: -------------------------------------------------------------------------------- 1 | bit$_ (## 1) = Bit; 2 | bool_false$0 = Bool; 3 | bool_true$1 = Bool; 4 | 5 | left$0 {X:Type} {Y:Type} value:X = Either X Y; 6 | right$1 {X:Type} {Y:Type} value:Y = Either X Y; 7 | 8 | // 9 | 10 | hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n) 11 | {n = (~m) + l} node:(HashmapNode m X) = Hashmap n X; 12 | 13 | hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X; 14 | hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X) 15 | right:^(Hashmap n X) = HashmapNode (n + 1) X; 16 | 17 | hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m; 18 | hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m; 19 | hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m; 20 | 21 | unary_zero$0 = Unary ~0; 22 | unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1); 23 | 24 | hme_empty$0 {n:#} {X:Type} = HashmapE n X; 25 | hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X; 26 | 27 | // 28 | 29 | nothing$0 {X:Type} = Maybe X; 30 | just$1 {X:Type} value:X = Maybe X; 31 | 32 | anycast_info$_ depth:(#<= 30) { depth >= 1 } 33 | rewrite_pfx:(bits depth) = Anycast; 34 | 35 | addr_std$10 anycast:(Maybe Anycast) 36 | workchain_id:int8 address:bits256 = MsgAddressInt; 37 | _ _:MsgAddressInt = MsgAddress; 38 | 39 | _ address:MsgAddress = Addr; 40 | 41 | // 42 | 43 | var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) 44 | = VarUInteger n; 45 | var_int$_ {n:#} len:(#< n) value:(int (len * 8)) 46 | = VarInteger n; 47 | nanograms$_ amount:(VarUInteger 16) = Grams; 48 | 49 | _ grams:Grams = Coins; 50 | 51 | // 52 | 53 | extra_currencies$_ dict:(HashmapE 32 (VarUInteger 32)) 54 | = ExtraCurrencyCollection; 55 | currencies$_ grams:Grams other:ExtraCurrencyCollection 56 | = CurrencyCollection; 57 | 58 | // 59 | 60 | tick_tock$_ tick:Bool tock:Bool = TickTock; 61 | 62 | _ split_depth:(Maybe (## 5)) special:(Maybe TickTock) 63 | code:(Maybe ^Cell) data:(Maybe ^Cell) 64 | library:(Maybe ^Cell) = StateInit; 65 | 66 | int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool 67 | src:MsgAddressInt dest:MsgAddressInt 68 | value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams 69 | created_lt:uint64 created_at:uint32 = CommonMsgInfo; 70 | 71 | message$_ {X:Type} info:CommonMsgInfo 72 | init:(Maybe (Either StateInit ^StateInit)) 73 | body:(Either X ^X) = Message X; 74 | 75 | _ (Message Any) = MessageAny; 76 | 77 | // 78 | 79 | storage#_ owner_address:MsgAddress validator_address:MsgAddress = Storage; 80 | 81 | // owner ops 82 | withdraw#1000 query_id:uint64 amount:Coins = InternalMsgBody; 83 | change_validator_address#1001 query_id:uint64 new_validator_address:MsgAddress = InternalMsgBody; 84 | send_raw_msg#7702 query_id:uint64 mode:uint8 msg:^MessageAny = InternalMsgBody; 85 | upgrade#9903 query_id:uint64 code:^Cell = InternalMsgBody; 86 | 87 | // elector ops 88 | new_stakedata#_ validator_pubkey:bits256 stake_at:uint32 max_factor:uint32 andl_addr:bits256 signature:^bits512 = NewStakeData; 89 | 90 | // 2 opcodes respond for 2 InternalMsgBody schemes 91 | // so to avoid errors - here is OutMsgBody 92 | new_stake_to_validator#4e73744b query_id:uint64 stake_data:NewStakeData = OutMsgBody; 93 | new_stake#4e73744b query_id:uint64 stake_amount:Coins new_stake_msg:NewStakeData = InternalMsgBody; 94 | recover_stake#47657424 query_id:uint64 = InternalMsgBody; 95 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/upgrade.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." " cr cr 5 | ."Creates a message body to update the nominator's code." cr 6 | ."Takes - BoC file path as argument." cr cr 7 | ."Saves the result into `upgrade.boc`." cr 1 halt 8 | } : usage 9 | $# 1 = { } { usage } cond 10 | 11 | $1 file>B dup 12 | 8 B| drop B>$ "b5ee9c72" $= { B>$ x>B? drop } if 13 | B>boc =: new-code-boc 14 | 15 | 2 boc+>B 16 | ."Message body is " dup B>base64 type cr 17 | 18 | "upgrade.boc" tuck B>file ."Saved to " type cr 19 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/validator-elect-signed.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." " cr 5 | ."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 6 | ." 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 7 | ."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 8 | } : usage 9 | 10 | $# dup 8 < swap 8 > or ' usage if 11 | $1 true parse-load-address drop swap 1+ abort"only masterchain smartcontracts may participate in validator elections" 12 | constant src_addr 13 | $2 (number) 1 <> { 0 } if dup 0<= abort" must be a positive integer" 14 | constant elect_time 15 | $3 (number) dup 0= abort" must be a real number 1..100" 16 | 1 = { 16 << } { 16 < or abort" must be a real number 1..100" 18 | constant max_factor 19 | $4 dup $len 1 > { parse-adnl-address } { drop 0 } cond constant adnl_addr 20 | $5 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long" 21 | 32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number" 22 | constant pubkey 23 | $6 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long" 24 | constant signature 25 | $7 constant output_fname 26 | $8 $>GR =: amount 27 | 28 | ."Creating a request to participate in validator elections at time " elect_time . 29 | ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr 30 | ." with maximal stake factor with respect to the minimal stake " max_factor ._ 31 | ."/65536 and validator ADNL address " adnl_addr 64x. cr 32 | 33 | 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+ 34 | ."String to sign is: " dup Bx. cr constant to_sign 35 | 36 | to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid" 37 | ."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr 38 | now dup constant query_id ."query_id set to " . cr 39 | 40 | ref, b> 42 | cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr 45 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/validator-withdraw.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | 4 | { ."usage: " @' $0 type ." []" cr 5 | ."" cr 6 | ."" cr 1 halt 7 | } : usage 8 | 9 | $# dup 1 < swap 2 > or ' usage if 10 | $1 $>GR =: amount 11 | def? $2 { @' $2 } { "validator-withdraw-query.boc" } cond constant output_fname 12 | now constant query_id 13 | ."query_id for stake recovery message is set to " query_id . ."amount=" amount .GR cr 14 | 15 | 16 | cr ."Message body is " dup B output_fname tuck B>file ."Saved to file " type cr 19 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/wallet-v3.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/fift -s 2 | "TonUtil.fif" include 3 | "GetOpt.fif" include 4 | 5 | { show-options-help 1 halt } : usage 6 | 7 | "" =: comment // comment for simple transfers 8 | true =: allow-bounce 9 | false =: force-bounce 10 | 3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors 11 | 60 =: timeout // external message expires in 60 seconds 12 | variable extra-currencies 13 | { extra-currencies @ cc+ extra-currencies ! } : extra-cc+! 14 | 15 | begin-options 16 | " [-x *] [-n|-b] [-t] [-B ] [-C ] [-I ] []" +cr +tab 17 | +"Creates a request to advanced wallet created by new-wallet-v3.fif, with private key loaded from file .pk " 18 | +"and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" 19 | disable-digit-options generic-help-setopt 20 | "n" "--no-bounce" { false =: allow-bounce } short-long-option 21 | "Clears bounce flag" option-help 22 | "b" "--force-bounce" { true =: force-bounce } short-long-option 23 | "Forces bounce flag" option-help 24 | "x" "--extra" { $>xcc extra-cc+! } short-long-option-arg 25 | "Indicates the amount of extra currencies to be transfered" option-help 26 | "t" "--timeout" { parse-int =: timeout } short-long-option-arg 27 | "Sets expiration timeout in seconds (" timeout (.) $+ +" by default)" option-help 28 | "B" "--body" { =: body-boc-file } short-long-option-arg 29 | "Sets the payload of the transfer message" option-help 30 | "C" "--comment" { =: comment } short-long-option-arg 31 | "Sets the comment to be sent in the transfer message" option-help 32 | "I" "--with-init" { =: init-file } short-long-option-arg 33 | "Indicates filename with BoC containing StateInit for internal message" option-help 34 | "m" "--mode" { parse-int =: send-mode } short-long-option-arg 35 | "Sets transfer mode (0..255) for SENDRAWMSG (" send-mode (.) $+ +" by default)" 36 | option-help 37 | "h" "--help" { usage } short-long-option 38 | "Shows a help message" option-help 39 | parse-options 40 | 41 | $# dup 5 < swap 6 > or ' usage if 42 | 6 :$1..n 43 | 44 | true constant bounce 45 | $1 =: file-base 46 | $2 bounce parse-load-address force-bounce or allow-bounce and =: bounce 2=: dest_addr 47 | $3 parse-int =: subwallet_id 48 | $4 parse-int =: seqno 49 | $5 $>cc extra-cc+! extra-currencies @ 2=: amount 50 | $6 "wallet-query" replace-if-null =: savefile 51 | subwallet_id (.) 1 ' $+ does : +subwallet 52 | 53 | file-base +subwallet +".addr" dup file-exists? { drop file-base +".addr" } ifnot 54 | load-address 55 | 2dup 2constant wallet_addr 56 | ."Source wallet address = " 2dup .addr cr 6 .Addr cr 57 | file-base +".pk" load-keypair nip constant wallet_pk 58 | 59 | def? body-boc-file { @' body-boc-file file>B B>boc } { comment simple-transfer-body } cond 60 | constant body-cell 61 | 62 | def? init-file { @' init-file file>B B>boc 76 | 77 | dup ."signing message: " 81 | dup ."resulting external message: " B dup Bx. cr 83 | savefile +".boc" tuck B>file 84 | ."Query expires in " timeout . ."seconds" cr 85 | ."(Saved to file " type .")" cr 86 | -------------------------------------------------------------------------------- /mytoncore/contracts/single-nominator-pool/withdraw.fif: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fift -s 2 | "TonUtil.fif" include 3 | "Asm.fif" include 4 | 5 | { ."usage: " @' $0 type ." " cr 6 | ."Creates a message body to withdraw from a single-nominator pool." cr 1 halt 7 | } : usage 8 | $# 1 = { } { usage } cond 9 | 10 | $1 $>GR =: amount 11 | 12 | =: body_boc 13 | body_boc B 15 | "withdraw.boc" tuck B>file 16 | ."(Saved witdhraw query to file to file " type .")" cr 17 | -------------------------------------------------------------------------------- /mytoncore/fift.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | class Fift: 5 | def __init__(self, local): 6 | self.local = local 7 | self.appPath = None 8 | self.libsPath = None 9 | self.smartcontsPath = None 10 | #end define 11 | 12 | def Run(self, args, **kwargs): 13 | fift_timeout = self.local.db.fift_timeout if self.local.db.fift_timeout else 3 14 | timeout = kwargs.get("timeout", fift_timeout) 15 | for i in range(len(args)): 16 | args[i] = str(args[i]) 17 | includePath = self.libsPath + ':' + self.smartcontsPath 18 | args = [self.appPath, "-I", includePath, "-s"] + args 19 | process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) 20 | output = process.stdout.decode("utf-8") 21 | err = process.stderr.decode("utf-8") 22 | if process.returncode != 0 and len(err) > 0: 23 | self.local.add_log("args: {args}".format(args=args), "error") 24 | raise Exception("Fift error: {err}".format(err=err)) 25 | return output 26 | #end define 27 | #end class 28 | -------------------------------------------------------------------------------- /mytoncore/liteclient.py: -------------------------------------------------------------------------------- 1 | import random 2 | import subprocess 3 | 4 | 5 | class LiteClient: 6 | def __init__(self, local): 7 | self.local = local 8 | self.appPath = None 9 | self.configPath = None 10 | self.pubkeyPath = None 11 | self.addr = None 12 | self.ton = None # magic 13 | #end define 14 | 15 | def Run(self, cmd, **kwargs): 16 | index = kwargs.get("index") 17 | liteclient_timeout = self.local.db.liteclient_timeout if self.local.db.liteclient_timeout else 3 18 | timeout = kwargs.get("timeout", liteclient_timeout) 19 | useLocalLiteServer = kwargs.get("useLocalLiteServer", True) 20 | validator_status = self.ton.GetValidatorStatus() 21 | args = [self.appPath, "--global-config", self.configPath, "--verbosity", "0", "--cmd", cmd] 22 | if index is not None: 23 | index = str(index) 24 | args += ["-i", index] 25 | elif useLocalLiteServer and self.pubkeyPath and validator_status.out_of_sync and validator_status.out_of_sync < 20: 26 | args = [self.appPath, "--addr", self.addr, "--pub", self.pubkeyPath, "--verbosity", "0", "--cmd", cmd] 27 | else: 28 | liteServers = self.local.db.get("liteServers") 29 | if liteServers is not None and len(liteServers): 30 | index = random.choice(liteServers) 31 | index = str(index) 32 | args += ["-i", index] 33 | #end if 34 | 35 | process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) 36 | output = process.stdout.decode("utf-8") 37 | err = process.stderr.decode("utf-8") 38 | if len(err) > 0: 39 | self.local.add_log("args: {args}".format(args=args), "error") 40 | raise Exception("LiteClient error: {err}".format(err=err)) 41 | return output 42 | #end define 43 | #end class 44 | -------------------------------------------------------------------------------- /mytoncore/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class Wallet: 5 | def __init__(self, name, path, version): 6 | self.name = name 7 | self.path = path 8 | self.addrFilePath = f"{path}.addr" 9 | self.privFilePath = f"{path}.pk" 10 | self.bocFilePath = f"{path}-query.boc" 11 | self.addrFull = None 12 | self.workchain = None 13 | self.addr = None 14 | self.addrB64 = None 15 | self.addrB64_init = None 16 | self.oldseqno = None 17 | self.account = None 18 | self.subwallet = None 19 | self.version = version 20 | #end define 21 | 22 | def Delete(self): 23 | os.remove(self.addrFilePath) 24 | os.remove(self.privFilePath) 25 | #end define 26 | #end class 27 | 28 | 29 | class Account: 30 | def __init__(self, workchain, addr): 31 | self.workchain = workchain 32 | self.addr = addr 33 | self.addrB64 = None 34 | self.addrFull = None 35 | self.status = "empty" 36 | self.balance = 0 37 | self.lt = None 38 | self.hash = None 39 | self.codeHash = None 40 | #end define 41 | #end class 42 | 43 | 44 | class Block(): 45 | def __init__(self, str=None): 46 | self.workchain = None 47 | self.shardchain = None 48 | self.seqno = None 49 | self.rootHash = None 50 | self.fileHash = None 51 | self.ParsBlock(str) 52 | #end define 53 | 54 | def ParsBlock(self, str): 55 | if str is None: 56 | return 57 | buff = str.split(':') 58 | self.rootHash = buff[1] 59 | self.fileHash = buff[2] 60 | buff = buff[0] 61 | buff = buff.replace('(', '') 62 | buff = buff.replace(')', '') 63 | buff = buff.split(',') 64 | self.workchain = int(buff[0]) 65 | self.shardchain = buff[1] 66 | self.seqno = int(buff[2]) 67 | #end define 68 | 69 | def __str__(self): 70 | result = f"({self.workchain},{self.shardchain},{self.seqno}):{self.rootHash}:{self.fileHash}" 71 | return result 72 | #end define 73 | 74 | def __repr__(self): 75 | return self.__str__() 76 | #end define 77 | 78 | def __eq__(self, other): 79 | if other is None: 80 | return False 81 | return self.rootHash == other.rootHash and self.fileHash == other.fileHash 82 | #end define 83 | #end class 84 | 85 | 86 | class Trans(): 87 | def __init__(self, block, addr=None, lt=None, hash=None): 88 | self.block = block 89 | self.addr = addr 90 | self.lt = lt 91 | self.hash = hash 92 | #end define 93 | 94 | def __str__(self): 95 | return str(self.__dict__) 96 | #end define 97 | 98 | def __repr__(self): 99 | return self.__str__() 100 | #end define 101 | 102 | def __eq__(self, other): 103 | if other is None: 104 | return False 105 | return self.hash == other.hash 106 | #end define 107 | #end class 108 | 109 | 110 | class Message(): 111 | def __init__(self): 112 | self.trans = None 113 | self.type = None 114 | self.time = None 115 | self.srcWorkchain = None 116 | self.destWorkchain = None 117 | self.srcAddr = None 118 | self.destAddr = None 119 | self.value = None 120 | self.body = None 121 | self.comment = None 122 | self.ihr_fee = None 123 | self.fwd_fee = None 124 | self.total_fees = None 125 | self.ihr_disabled = None 126 | self.hash = None 127 | #end define 128 | 129 | def GetFullAddr(self, workchain, addr): 130 | if addr is None: 131 | return 132 | return f"{workchain}:{addr}" 133 | #end define 134 | 135 | def __str__(self): 136 | return str(self.__dict__) 137 | #end define 138 | 139 | def __repr__(self): 140 | return self.__str__() 141 | #end define 142 | 143 | def __eq__(self, other): 144 | if other is None: 145 | return False 146 | return self.hash == other.hash 147 | #end define 148 | #end class 149 | 150 | 151 | class Pool: 152 | def __init__(self, name, path): 153 | self.name = name 154 | self.path = path 155 | self.addrFilePath = f"{path}.addr" 156 | self.bocFilePath = f"{path}-query.boc" 157 | self.addrFull = None 158 | self.workchain = None 159 | self.addr = None 160 | self.addrB64 = None 161 | self.addrB64_init = None 162 | self.account = None 163 | #end define 164 | 165 | def Delete(self): 166 | os.remove(self.addrFilePath) 167 | #end define 168 | #end class 169 | -------------------------------------------------------------------------------- /mytoncore/telemetry.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | import psutil 5 | 6 | from mytoncore.utils import parse_db_stats 7 | from mypylib.mypylib import get_service_pid 8 | 9 | 10 | def GetUname(): 11 | data = os.uname() 12 | result = dict( 13 | zip('sysname nodename release version machine'.split(), data)) 14 | result.pop("nodename") 15 | return result 16 | # end define 17 | 18 | 19 | def GetMemoryInfo(): 20 | result = dict() 21 | data = psutil.virtual_memory() 22 | result["total"] = round(data.total / 10**9, 2) 23 | result["usage"] = round(data.used / 10**9, 2) 24 | result["usagePercent"] = data.percent 25 | return result 26 | # end define 27 | 28 | 29 | def GetSwapInfo(): 30 | result = dict() 31 | data = psutil.swap_memory() 32 | result["total"] = round(data.total / 10**9, 2) 33 | result["usage"] = round(data.used / 10**9, 2) 34 | result["usagePercent"] = data.percent 35 | return result 36 | # end define 37 | 38 | 39 | def GetValidatorProcessInfo(): 40 | pid = get_service_pid("validator") 41 | if pid == None or pid == 0: 42 | return 43 | p = psutil.Process(pid) 44 | mem = p.memory_info() 45 | result = dict() 46 | result["cpuPercent"] = p.cpu_percent() 47 | memory = dict() 48 | memory["rss"] = mem.rss 49 | memory["vms"] = mem.vms 50 | memory["shared"] = mem.shared 51 | memory["text"] = mem.text 52 | memory["lib"] = mem.lib 53 | memory["data"] = mem.data 54 | memory["dirty"] = mem.dirty 55 | result["memory"] = memory 56 | # io = p.io_counters() # Permission denied: '/proc/{pid}/io' 57 | return result 58 | # end define 59 | 60 | 61 | def get_db_stats(): 62 | result = { 63 | 'rocksdb': { 64 | 'ok': True, 65 | 'message': '', 66 | 'data': {} 67 | }, 68 | 'celldb': { 69 | 'ok': True, 70 | 'message': '', 71 | 'data': {} 72 | }, 73 | } 74 | rocksdb_stats_path = '/var/ton-work/db/db_stats.txt' 75 | celldb_stats_path = '/var/ton-work/db/celldb/db_stats.txt' 76 | if os.path.exists(rocksdb_stats_path): 77 | try: 78 | result['rocksdb']['data'] = parse_db_stats(rocksdb_stats_path) 79 | except Exception as e: 80 | result['rocksdb']['ok'] = False 81 | result['rocksdb']['message'] = f'failed to fetch db stats: {e}' 82 | else: 83 | result['rocksdb']['ok'] = False 84 | result['rocksdb']['message'] = 'db stats file is not exists' 85 | # end if 86 | 87 | if os.path.exists(celldb_stats_path): 88 | try: 89 | result['celldb']['data'] = parse_db_stats(celldb_stats_path) 90 | except Exception as e: 91 | result['celldb']['ok'] = False 92 | result['celldb']['message'] = f'failed to fetch db stats: {e}' 93 | else: 94 | result['celldb']['ok'] = False 95 | result['celldb']['message'] = 'db stats file is not exists' 96 | # end if 97 | 98 | return result 99 | # end define 100 | 101 | 102 | def get_cpu_name(): 103 | with open('/proc/cpuinfo') as f: 104 | for line in f: 105 | if line.strip(): 106 | if line.rstrip('\n').startswith('model name'): 107 | return line.rstrip('\n').split(':')[1].strip() 108 | return None 109 | 110 | 111 | def is_host_virtual(): 112 | try: 113 | with open('/sys/class/dmi/id/product_name') as f: 114 | product_name = f.read().strip().lower() 115 | if 'virtual' in product_name or 'kvm' in product_name or 'qemu' in product_name or 'vmware' in product_name: 116 | return {'virtual': True, 'product_name': product_name} 117 | return {'virtual': False, 'product_name': product_name} 118 | except FileNotFoundError: 119 | return {'virtual': None, 'product_name': None} 120 | 121 | 122 | def do_beacon_ping(host, count, timeout): 123 | args = ['ping', '-c', str(count), '-W', str(timeout), host] 124 | process = subprocess.run(args, stdin=subprocess.PIPE, 125 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) 126 | output = process.stdout.decode("utf-8") 127 | avg = output.split('\n')[-2].split('=')[1].split('/')[1] 128 | return float(avg) 129 | 130 | 131 | def get_pings_values(): 132 | return { 133 | 'beacon-eu-01.toncenter.com': do_beacon_ping('beacon-eu-01.toncenter.com', 5, 10), 134 | 'beacon-apac-01.toncenter.com': do_beacon_ping('beacon-apac-01.toncenter.com', 5, 10) 135 | } 136 | 137 | 138 | def get_validator_disk_name(): 139 | process = subprocess.run("df -h /var/ton-work/ | sed -n '2 p' | awk '{print $1}'", stdin=subprocess.PIPE, 140 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3, shell=True) 141 | output = process.stdout.decode("utf-8") 142 | return output.strip() 143 | 144 | -------------------------------------------------------------------------------- /mytoncore/tonblocksscanner.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import time 3 | 4 | from mytoncore.models import Block 5 | 6 | 7 | class TonBlocksScanner(): 8 | def __init__(self, ton, **kwargs): 9 | self.ton = ton 10 | self.prevMasterBlock = None 11 | self.prevShardsBlock = dict() 12 | self.blocksNum = 0 13 | self.transNum = 0 14 | self.nbr = kwargs.get("nbr") #NewBlockReaction 15 | self.ntr = kwargs.get("ntr") #NewTransReaction 16 | self.nmr = kwargs.get("nmr") #NewMessageReaction 17 | self.local = kwargs.get("local") 18 | self.sync = kwargs.get("sync", False) 19 | self.delay = 0 20 | self.working = False 21 | self.closing = False 22 | #end define 23 | 24 | def Run(self): 25 | self.StartThread(self.ScanBlocks, args=()) 26 | self.StartThread(self.ThreadBalancing, args=()) 27 | self.StartThread(self.StatusReading, args=()) 28 | #end define 29 | 30 | def StartThread(self, func, args): 31 | threading.Thread(target=func, args=args, name=func.__name__, daemon=True).start() 32 | #end define 33 | 34 | def StartWithMode(self, func, args): 35 | if self.sync: 36 | func(*args) 37 | else: 38 | self.StartThread(func, args) 39 | #end define 40 | 41 | def AddLog(self, text, type): 42 | if self.local: 43 | self.local.AddLog(text, type) 44 | else: 45 | print(text) 46 | #end define 47 | 48 | def Try(self, func, **kwargs): 49 | args = kwargs.get("args", tuple()) 50 | for step in range(10): 51 | time.sleep(step) 52 | try: 53 | result = func(*args) 54 | return result 55 | except Exception as ex: 56 | err = ex 57 | text = f"{func.__name__} step: {step}, error: {err}" 58 | self.AddLog(text, "error") 59 | raise Exception(err) 60 | #end define 61 | 62 | def SetStartBlock(self, workchain, shardchain, seqno): 63 | workchainType = type(workchain) 64 | shardchainType = type(shardchain) 65 | seqnoType = type(seqno) 66 | if workchainType != int: 67 | raise Exception(f"SetStartBlock error: workchain type mast be int, not {workchainType}") 68 | if shardchainType != str: 69 | raise Exception(f"SetStartBlock error: shardchain type mast be str, not {shardchainType}") 70 | if seqnoType != int: 71 | raise Exception(f"SetStartBlock error: seqno type mast be int, not {seqnoType}") 72 | #end if 73 | 74 | block = Block() 75 | block.workchain = workchain 76 | block.shardchain = shardchain 77 | block.seqno = seqno 78 | if workchain == -1: 79 | self.prevMasterBlock = block 80 | else: 81 | self.SetShardPrevBlock(block) 82 | self.sync = True 83 | #end define 84 | 85 | def ThreadBalancing(self): 86 | while True: 87 | tnum = threading.active_count() 88 | if tnum > 100: 89 | self.delay += 0.1 90 | elif tnum > 50: 91 | self.delay += 0.01 92 | elif tnum < 50: 93 | self.delay -= 0.1 94 | elif tnum < 100: 95 | self.delay -= 0.01 96 | if self.delay < 0: 97 | self.delay = 0 98 | if self.closing is True: 99 | exit() 100 | time.sleep(0.1) 101 | #end define 102 | 103 | def StatusReading(self): 104 | while True: 105 | validatorStatus = self.ton.GetValidatorStatus() 106 | validatorOutOfSync = validatorStatus.get("outOfSync") 107 | if self.ton.liteClient.pubkeyPath is None: 108 | self.working = False 109 | self.closing = True 110 | text = "TonBlocksScanner error: local liteserver is not configured, stop thread." 111 | self.AddLog(text, "error") 112 | exit() 113 | if validatorOutOfSync > 20: 114 | self.working = False 115 | text = f"TonBlocksScanner warning: local liteserver is out of sync: {validatorOutOfSync}." 116 | self.AddLog(text, "warning") 117 | else: 118 | self.working = True 119 | time.sleep(10) 120 | #end define 121 | 122 | def ScanBlocks(self): 123 | while True: 124 | if self.working is True: 125 | self.ScanBlock() 126 | if self.closing is True: 127 | exit() 128 | time.sleep(1) 129 | #end define 130 | 131 | def ScanBlock(self): 132 | block = self.Try(self.ton.GetLastBlock) 133 | self.StartThread(self.SearchMissBlocks, args=(block, self.prevMasterBlock)) 134 | if block != self.prevMasterBlock: 135 | self.StartWithMode(self.ReadBlock, args=(block,)) 136 | self.prevMasterBlock = block 137 | #end define 138 | 139 | def ReadBlock(self, block): 140 | self.StartWithMode(self.NewBlockReaction, args=(block,)) 141 | shards = self.Try(self.ton.GetShards, args=(block,)) 142 | for shard in shards: 143 | self.StartThread(self.ReadShard, args=(shard,)) 144 | #end define 145 | 146 | def ReadShard(self, shard): 147 | block = shard.get("block") 148 | prevBlock = self.GetShardPrevBlock(block.shardchain) 149 | self.StartThread(self.SearchMissBlocks, args=(block, prevBlock)) 150 | if block != prevBlock: 151 | self.StartWithMode(self.NewBlockReaction, args=(block,)) 152 | self.SetShardPrevBlock(block) 153 | #end define 154 | 155 | def SearchMissBlocks(self, block, prevBlock): 156 | if prevBlock is None: 157 | return 158 | diff = block.seqno - prevBlock.seqno 159 | #for i in range(1, diff): 160 | for i in range(diff-1, 0, -1): 161 | workchain = block.workchain 162 | shardchain = block.shardchain 163 | seqno = block.seqno - i 164 | self.StartWithMode(self.SearchBlock, args=(workchain, shardchain, seqno)) 165 | #end define 166 | 167 | def SearchBlock(self, workchain, shardchain, seqno): 168 | if self.delay != 0: 169 | time.sleep(self.delay) 170 | block = self.Try(self.ton.GetBlock, args=(workchain, shardchain, seqno)) 171 | self.StartWithMode(self.NewBlockReaction, args=(block,)) 172 | #end define 173 | 174 | def GetShardPrevBlock(self, shardchain): 175 | prevBlock = self.prevShardsBlock.get(shardchain) 176 | return prevBlock 177 | #end define 178 | 179 | def SetShardPrevBlock(self, prevBlock): 180 | self.prevShardsBlock[prevBlock.shardchain] = prevBlock 181 | #end define 182 | 183 | def NewBlockReaction(self, block): 184 | #print(f"{bcolors.green} block: {bcolors.endc} {block}") 185 | self.blocksNum += 1 186 | if self.nbr: 187 | self.StartThread(self.nbr, args=(block,)) 188 | transactions = self.Try(self.ton.GetTransactions, args=(block,)) 189 | for trans in transactions: 190 | self.StartWithMode(self.NewTransReaction, args=(trans,)) 191 | #end define 192 | 193 | def NewTransReaction(self, trans): 194 | #print(f"{bcolors.magenta} trans: {bcolors.endc} {self.transNum}", "debug") 195 | self.transNum += 1 196 | if self.ntr: 197 | self.StartThread(self.ntr, args=(trans,)) 198 | messageList = self.Try(self.ton.GetTrans, args=(trans,)) 199 | for message in messageList: 200 | self.NewMessageReaction(message) 201 | #end define 202 | 203 | def NewMessageReaction(self, message): 204 | if self.nmr: 205 | self.StartThread(self.nmr, args=(message,)) 206 | #print(f"{bcolors.yellow} message: {bcolors.endc} {message}") 207 | #end define 208 | #end class 209 | -------------------------------------------------------------------------------- /mytoncore/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import re 4 | import subprocess 5 | 6 | 7 | def str2b64(s): 8 | b = s.encode("utf-8") 9 | b64 = base64.b64encode(b) 10 | b64 = b64.decode("utf-8") 11 | return b64 12 | # end define 13 | 14 | 15 | def b642str(b64): 16 | b64 = b64.encode("utf-8") 17 | b = base64.b64decode(b64) 18 | s = b.decode("utf-8") 19 | return s 20 | # end define 21 | 22 | 23 | def dict2b64(d): 24 | s = json.dumps(d) 25 | b64 = str2b64(s) 26 | return b64 27 | # end define 28 | 29 | 30 | def b642dict(b64): 31 | s = b642str(b64) 32 | d = json.loads(s) 33 | return d 34 | # end define 35 | 36 | 37 | def hex2b64(input): # TODO: remove duplicates 38 | hexBytes = bytes.fromhex(input) 39 | b64Bytes = base64.b64encode(hexBytes) 40 | b64String = b64Bytes.decode() 41 | return b64String 42 | # end define 43 | 44 | 45 | def b642hex(input): 46 | b64Bytes = input.encode() 47 | hexBytes = base64.b64decode(b64Bytes) 48 | hexString = hexBytes.hex() 49 | return hexString 50 | # end define 51 | 52 | 53 | def xhex2hex(x): 54 | try: 55 | b = x[1:] 56 | h = b.lower() 57 | return h 58 | except: 59 | return None 60 | #end define 61 | 62 | def hex2base64(h): # TODO: remove duplicates 63 | b = bytes.fromhex(h) 64 | b64 = base64.b64encode(b) 65 | s = b64.decode("utf-8") 66 | return s 67 | #end define 68 | 69 | 70 | def str2bool(str): 71 | if str == "true": 72 | return True 73 | return False 74 | # end define 75 | 76 | 77 | def ng2g(ng): 78 | if ng is None: 79 | return 80 | return int(ng)/10**9 81 | #end define 82 | 83 | 84 | def parse_db_stats(path: str): 85 | with open(path) as f: 86 | lines = f.readlines() 87 | result = {} 88 | for line in lines: 89 | s = line.strip().split(maxsplit=1) 90 | items = re.findall(r"(\S+)\s:\s(\S+)", s[1]) 91 | if len(items) == 1: 92 | item = items[0] 93 | if float(item[1]) > 0: 94 | result[s[0]] = float(item[1]) 95 | else: 96 | if any(float(v) > 0 for k, v in items): 97 | result[s[0]] = {} 98 | result[s[0]] = {k: float(v) for k, v in items} 99 | return result 100 | # end define 101 | 102 | def get_hostname(): 103 | return subprocess.run(["hostname", "-f"], stdout=subprocess.PIPE).stdout.decode().strip() 104 | -------------------------------------------------------------------------------- /mytoncore/validator_console.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | class ValidatorConsole: 5 | def __init__(self, local): 6 | self.local = local 7 | self.appPath = None 8 | self.privKeyPath = None 9 | self.pubKeyPath = None 10 | self.addr = None 11 | #end define 12 | 13 | def Run(self, cmd, **kwargs): 14 | console_timeout = self.local.db.console_timeout if self.local.db.console_timeout else 3 15 | timeout = kwargs.get("timeout", console_timeout) 16 | if self.appPath is None or self.privKeyPath is None or self.pubKeyPath is None: 17 | raise Exception("ValidatorConsole error: Validator console is not settings") 18 | args = [self.appPath, "-k", self.privKeyPath, "-p", self.pubKeyPath, "-a", self.addr, "-v", "0", "--cmd", cmd] 19 | process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout) 20 | output = process.stdout.decode("utf-8") 21 | err = process.stderr.decode("utf-8") 22 | if len(err) > 0: 23 | self.local.add_log("args: {args}".format(args=args), "error") 24 | raise Exception("ValidatorConsole error: {err}".format(err=err)) 25 | return output 26 | #end define 27 | #end class 28 | -------------------------------------------------------------------------------- /mytonctrl.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | 3 | # 4 | # This is a migration script to update from legacy version of MTC 5 | # 6 | import os 7 | import sys 8 | import subprocess 9 | 10 | requirements_path = "/usr/src/mytonctrl/requirements.txt" 11 | if os.path.isfile(requirements_path): 12 | args = ["pip3", "install", "-r", requirements_path] 13 | subprocess.run(args) 14 | #end if 15 | 16 | sys.path.insert(0, '/usr/bin/mytonctrl') # Add path to mytonctrl module 17 | 18 | 19 | from mytonctrl.mytonctrl import run_migrations 20 | 21 | if __name__ == '__main__': 22 | print('Found new version of mytonctrl! Migrating!') 23 | run_migrations() 24 | -------------------------------------------------------------------------------- /mytonctrl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/mytonctrl/__init__.py -------------------------------------------------------------------------------- /mytonctrl/__main__.py: -------------------------------------------------------------------------------- 1 | from mytonctrl.mytonctrl import mytonctrl 2 | 3 | if __name__ == '__main__': 4 | mytonctrl() 5 | -------------------------------------------------------------------------------- /mytonctrl/migrate.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pkg_resources 3 | 4 | from mypylib.mypylib import MyPyClass 5 | from mytoncore.mytoncore import MyTonCore 6 | 7 | from mypylib.mypylib import ( 8 | run_as_root 9 | ) 10 | 11 | from typing import Optional 12 | 13 | 14 | def migrate_to_version_1(local: MyPyClass, ton: MyTonCore): 15 | # get script path 16 | migrate_script_path = pkg_resources.resource_filename('mytonctrl', 'migrations/migration_001.sh') 17 | args = ["/bin/bash", migrate_script_path] 18 | exit_code = run_as_root(args) 19 | if exit_code != 0: 20 | raise RuntimeError(f'Failed to run migration error. Exit code: {exit_code}') 21 | return 22 | 23 | 24 | def migrate(version: 0, local: MyPyClass, ton: MyTonCore): 25 | restart = False 26 | if version < 1: 27 | local.add_log(f'Running migration {version} -> 1', 'info') 28 | restart_required = migrate_to_version_1(local, ton) 29 | restart = restart or restart_required 30 | return 1, restart 31 | 32 | 33 | def run_migrations(local: Optional[MyPyClass]=None, ton: Optional[MyTonCore]=None): 34 | if local is None: 35 | local = MyPyClass('mytonctrl.py') 36 | if ton is None: 37 | ton = MyTonCore(MyPyClass('mytoncore.py')) 38 | 39 | # migrations 40 | version = 0 41 | 42 | workdir = local.buffer.my_work_dir 43 | version_file_path = os.path.join(workdir, 'VERSION') 44 | if os.path.exists(version_file_path): 45 | with open(version_file_path, 'r') as f: 46 | version = int(f.read()) 47 | 48 | new_version, restart = migrate(version, local, ton) 49 | 50 | with open(version_file_path, 'w') as f: 51 | f.write(f'{new_version}') 52 | return restart 53 | #end define 54 | -------------------------------------------------------------------------------- /mytonctrl/migrations/migration_001.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # installing pip package 4 | if [ -f "setup.py" ]; then 5 | workdir=$(pwd) 6 | else 7 | workdir=/usr/src/mytonctrl 8 | fi 9 | 10 | cd $workdir 11 | pip3 install -U pip . 12 | 13 | # update /usr/bin/mytonctrl 14 | echo " Updating /usr/bin/mytonctrl" 15 | cat < /usr/bin/mytonctrl 16 | #!/bin/bash 17 | /usr/bin/python3 -m mytonctrl \$@ 18 | EOF 19 | chmod +x /usr/bin/mytonctrl 20 | 21 | # update /etc/systemd/system/mytoncore.service 22 | echo " Updating mytoncore service" 23 | sed -i 's\/usr/src/mytonctrl/mytoncore.py\-m mytoncore\g' /etc/systemd/system/mytoncore.service 24 | systemctl daemon-reload 25 | -------------------------------------------------------------------------------- /mytonctrl/migrations/roll_back_001.sh: -------------------------------------------------------------------------------- 1 | pip3 uninstall -y mytonctrl 2 | 3 | cd /usr/src 4 | rm -rf mytonctrl 5 | git clone --recursive -b mytonctrl1 https://github.com/ton-blockchain/mytonctrl 6 | 7 | echo "Updating /usr/bin/mytonctrl" 8 | echo "/usr/bin/python3 /usr/src/mytonctrl/mytonctrl.py $@" > /usr/bin/mytonctrl 9 | chmod +x /usr/bin/mytonctrl 10 | 11 | echo "Updating mytoncore service" 12 | sed -i 's\-m mytoncore\/usr/src/mytonctrl/mytoncore.py\g' /etc/systemd/system/mytoncore.service 13 | systemctl daemon-reload 14 | systemctl restart mytoncore 15 | 16 | echo "Done" 17 | -------------------------------------------------------------------------------- /mytonctrl/progressbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from os import popen 4 | from time import sleep 5 | from sys import stdout 6 | from threading import Thread 7 | 8 | class EtaBar: 9 | def __init__(self, **kwargs): 10 | self.toolbar_width = kwargs.get("toolbar_width", 10) 11 | self.snake_width = kwargs.get("snake_width", 4) 12 | self.timeout = kwargs.get("timeout", 60) 13 | self.sleep_time = 0.1 14 | 15 | self.square_symbol = '\u25A0' 16 | self.new_line_symbol = '\r' 17 | self.indent_symbol = ' ' 18 | 19 | self.tty_height, self.tty_width = self.get_tty_size() 20 | stdout.reconfigure(encoding="utf-8") 21 | #end define 22 | 23 | def run(self, func=None, *args, **kwargs): 24 | if func is None: 25 | func = self.stub 26 | self.start_thread(func, args=args, kwargs=kwargs) 27 | self.snake_process() 28 | return self.thread_result 29 | #end define 30 | 31 | def stub(self): 32 | sleep(self.timeout) 33 | #end define 34 | 35 | def get_tty_size(self): 36 | with popen("stty size", 'r') as file: 37 | tty_height, tty_width = file.read().split() 38 | tty_height = int(tty_height) 39 | tty_width = int(tty_width) 40 | return tty_height, tty_width 41 | #end define 42 | 43 | def start_thread(self, func, **kwargs): 44 | self.thread_result = None 45 | self.thread = Thread(target=self.thread_process, name=func.__name__, args=(func,), kwargs=kwargs, daemon=True) 46 | self.thread.start() 47 | #end define 48 | 49 | def thread_process(self, func, **kwargs): 50 | args = kwargs.get("args") 51 | kwargs = kwargs.get("kwargs") 52 | self.thread_result = func(*args, **kwargs) 53 | #end define 54 | 55 | def snake_process(self): 56 | snake_len = 0 57 | indent_len = 0 58 | cycles = int(self.timeout / self.sleep_time) 59 | for cycle in range(cycles): 60 | if self.thread.is_alive() == False: 61 | break 62 | sleep(self.sleep_time) 63 | if indent_len == self.toolbar_width: 64 | indent_len = 0 65 | elif indent_len == self.toolbar_width - snake_len: 66 | snake_len -= 1 67 | indent_len += 1 68 | elif snake_len == self.snake_width: 69 | indent_len += 1 70 | elif snake_len < self.snake_width: 71 | snake_len += 1 72 | snake = indent_len * self.indent_symbol + snake_len * self.square_symbol 73 | filling_len = self.toolbar_width - indent_len - snake_len 74 | filling = self.indent_symbol * filling_len 75 | eta = int(self.timeout - cycle * self.sleep_time) 76 | eta_text = f" ETA <= {eta} seconds" 77 | ending_len = self.tty_width - self.toolbar_width - 2 - len(eta_text) 78 | ending = ending_len * self.indent_symbol 79 | text = self.new_line_symbol + '[' + snake + filling + ']' + eta_text + ending 80 | stdout.write(text) 81 | #end for 82 | 83 | stdout.write(self.new_line_symbol + self.indent_symbol * self.tty_width) 84 | stdout.write(self.new_line_symbol) 85 | #end define 86 | #end class 87 | -------------------------------------------------------------------------------- /mytonctrl/scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | file_path=/var/ton-work/db/test.img 11 | db_path=/var/ton-work/db/bench 12 | 13 | function get_fio_json { 14 | read_iops=$(echo "$1" | grep "read:" | awk '{print $2}' | awk -F '=' '{print $2}') 15 | write_iops=$(echo "$1" | grep "write:" | awk '{print $2}' | awk -F '=' '{print $2}') 16 | read_iops=$(echo "${read_iops//,/}") 17 | write_iops=$(echo "${write_iops//,/}") 18 | read_speed=$(echo "$1" | grep "read:" | awk '{print $3}' | awk -F '=' '{print $2}') 19 | write_speed=$(echo "$1" | grep "write:" | awk '{print $3}' | awk -F '=' '{print $2}') 20 | json=$(cat <<-END 21 | { 22 | "read_iops": "${read_iops}", 23 | "write_iops": "${write_iops}", 24 | "read_speed": "${read_speed}", 25 | "write_speed": "${write_speed}" 26 | } 27 | END 28 | ) 29 | echo $json 30 | } 31 | 32 | function get_rocksd_bench_json { 33 | random_ops=$(echo "$1" | grep "randomtransaction" | awk '{print $5}') 34 | json=$(cat <<-END 35 | { 36 | "random_ops": "${random_ops}" 37 | } 38 | END 39 | ) 40 | echo $json 41 | } 42 | 43 | function print_json_result { 44 | json=$(cat <<-END 45 | { 46 | "lite": ${lite_json_result}, 47 | "hard": ${hard_json_result}, 48 | "full": ${full_json_result} 49 | } 50 | END 51 | ) 52 | echo $json 53 | } 54 | 55 | # https://superuser.com/questions/1049382/ssd-4k-random-read-write-qd1-32-and-iops-values 56 | # lite 57 | 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) 58 | lite_json_result=$(get_fio_json "$lite_result") 59 | 60 | # hard 61 | 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) 62 | hard_json_result=$(get_fio_json "$hard_result") 63 | 64 | # full 65 | 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) 66 | full_json_result=$(get_rocksd_bench_json "$full_result") 67 | 68 | # clear temp files 69 | rm ${file_path} 70 | rm -rf ${db_path} 71 | 72 | print_json_result 73 | -------------------------------------------------------------------------------- /mytonctrl/scripts/create_backup.sh: -------------------------------------------------------------------------------- 1 | dest="mytonctrl_backup_$(hostname)_$(date +%s).tar.gz" 2 | mtc_dir="$HOME/.local/share/mytoncore" 3 | user=$(logname) 4 | ton_dir="/var/ton-work" 5 | keys_dir="/var/ton-work/keys" 6 | # Get arguments 7 | while getopts d:m:t:k: flag 8 | do 9 | case "${flag}" in 10 | d) dest=${OPTARG};; 11 | m) mtc_dir=${OPTARG};; 12 | t) ton_dir=${OPTARG};; 13 | k) keys_dir=${OPTARG};; 14 | *) 15 | echo "Flag -${flag} is not recognized. Aborting" 16 | exit 1 ;; 17 | esac 18 | done 19 | 20 | COLOR='\033[92m' 21 | ENDC='\033[0m' 22 | 23 | tmp_dir="/tmp/mytoncore/backupv2" 24 | rm -rf $tmp_dir 25 | mkdir $tmp_dir 26 | mkdir $tmp_dir/db 27 | 28 | cp $ton_dir/db/config.json ${tmp_dir}/db 29 | cp -r $ton_dir/db/keyring ${tmp_dir}/db 30 | cp -r $keys_dir ${tmp_dir} 31 | cp -r $mtc_dir $tmp_dir 32 | 33 | python3 -c "import json;f=open('${tmp_dir}/db/config.json');json.load(f);f.close()" || exit 1 # Check if config.json is copied correctly 34 | python3 -c "import json;f=open('${tmp_dir}/mytoncore/mytoncore.db');json.load(f);f.close()" || exit 2 # Check if mytoncore.db is copied correctly 35 | 36 | echo -e "${COLOR}[1/2]${ENDC} Copied files to ${tmp_dir}" 37 | 38 | tar -zcf $dest -C $tmp_dir . 39 | 40 | chown $user:$user $dest 41 | 42 | echo -e "${COLOR}[2/2]${ENDC} Backup successfully created in ${dest}!" 43 | 44 | rm -rf $tmp_dir 45 | 46 | echo -e "If you wish to use archive package to migrate node to different machine please make sure to stop validator and mytoncore on donor (this) host prior to migration." 47 | -------------------------------------------------------------------------------- /mytonctrl/scripts/etabar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from sys import exit, argv 4 | from subprocess import run, PIPE 5 | from mytonctrl.progressbar import EtaBar 6 | 7 | 8 | timeout = int(argv[1]) 9 | script = argv[2] 10 | result = argv[3] 11 | 12 | bar = EtaBar(timeout=timeout) 13 | args = ["bash", script] 14 | process = bar.run(run, args, stdin=PIPE, stdout=PIPE, stderr=PIPE, timeout=timeout) 15 | exit_code = -1 16 | output = "process is None" 17 | if process != None: 18 | exit_code = process.returncode 19 | stdout = process.stdout.decode("utf-8") 20 | stderr = process.stderr.decode("utf-8") 21 | if exit_code == 0: 22 | output = stdout 23 | else: 24 | output = stderr 25 | with open(result, 'wt') as file: 26 | file.write(output) 27 | exit(exit_code) 28 | -------------------------------------------------------------------------------- /mytonctrl/scripts/restore_backup.sh: -------------------------------------------------------------------------------- 1 | name="backup.tar.gz" 2 | mtc_dir="$HOME/.local/share/mytoncore" 3 | ip=0 4 | user=$(logname) 5 | # Get arguments 6 | while getopts n:m:i:u: flag 7 | do 8 | case "${flag}" in 9 | n) name=${OPTARG};; 10 | m) mtc_dir=${OPTARG};; 11 | i) ip=${OPTARG};; 12 | u) user=${OPTARG};; 13 | *) 14 | echo "Flag -${flag} is not recognized. Aborting" 15 | exit 1 ;; 16 | esac 17 | done 18 | 19 | if [ ! -f "$name" ]; then 20 | echo "Backup file not found, aborting." 21 | exit 1 22 | fi 23 | 24 | COLOR='\033[92m' 25 | ENDC='\033[0m' 26 | 27 | systemctl stop validator 28 | systemctl stop mytoncore 29 | 30 | echo -e "${COLOR}[1/4]${ENDC} Stopped validator and mytoncore" 31 | 32 | 33 | tmp_dir="/tmp/mytoncore/backup" 34 | rm -rf $tmp_dir 35 | mkdir $tmp_dir 36 | tar -xvzf $name -C $tmp_dir 37 | 38 | if [ ! -d ${tmp_dir}/db ]; then 39 | echo "Old version of backup detected" 40 | mkdir ${tmp_dir}/db 41 | mv ${tmp_dir}/config.json ${tmp_dir}/db 42 | mv ${tmp_dir}/keyring ${tmp_dir}/db 43 | 44 | fi 45 | 46 | rm -rf /var/ton-work/db/keyring 47 | 48 | chown -R $user:$user ${tmp_dir}/mytoncore 49 | chown -R $user:$user ${tmp_dir}/keys 50 | chown validator:validator ${tmp_dir}/keys 51 | chown -R validator:validator ${tmp_dir}/db 52 | 53 | cp -rfp ${tmp_dir}/db /var/ton-work 54 | cp -rfp ${tmp_dir}/keys /var/ton-work 55 | cp -rfpT ${tmp_dir}/mytoncore $mtc_dir 56 | 57 | chown -R validator:validator /var/ton-work/db/keyring 58 | 59 | echo -e "${COLOR}[2/4]${ENDC} Extracted files from archive" 60 | 61 | rm -r /var/ton-work/db/dht-* 62 | 63 | if [ $ip -ne 0 ]; then 64 | echo "Replacing IP in node config" 65 | python3 -c "import json;path='/var/ton-work/db/config.json';f=open(path);d=json.load(f);f.close();d['addrs'][0]['ip']=int($ip);f=open(path, 'w');f.write(json.dumps(d, indent=4));f.close()" 66 | else 67 | echo "IP is not provided, skipping IP replacement" 68 | fi 69 | 70 | echo -e "${COLOR}[3/4]${ENDC} Deleted DHT files" 71 | 72 | systemctl start validator 73 | systemctl start mytoncore 74 | 75 | echo -e "${COLOR}[4/4]${ENDC} Started validator and mytoncore" 76 | -------------------------------------------------------------------------------- /mytonctrl/scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | # Set default arguments 11 | author="ton-blockchain" 12 | repo="mytonctrl" 13 | branch="master" 14 | srcdir="/usr/src/" 15 | tmpdir="/tmp/mytonctrl_src/" 16 | 17 | # Get arguments 18 | while getopts a:r:b: flag 19 | do 20 | case "${flag}" in 21 | a) author=${OPTARG};; 22 | r) repo=${OPTARG};; 23 | b) branch=${OPTARG};; 24 | esac 25 | done 26 | 27 | # Цвета 28 | COLOR='\033[92m' 29 | ENDC='\033[0m' 30 | 31 | mkdir -p ${tmpdir} 32 | cd ${tmpdir} 33 | rm -rf ${tmpdir}/${repo} 34 | echo "https://github.com/${author}/${repo}.git -> ${branch}" 35 | git clone --recursive https://github.com/${author}/${repo}.git || exit 1 36 | 37 | rm -rf ${srcdir}/${repo} 38 | pip3 uninstall -y mytonctrl 39 | 40 | # Update code 41 | cd ${srcdir} 42 | cp -rf ${tmpdir}/${repo} ${srcdir} 43 | cd ${repo} && git checkout ${branch} 44 | pip3 install -U . 45 | 46 | systemctl daemon-reload 47 | systemctl restart mytoncore 48 | 49 | # Конец 50 | echo -e "${COLOR}[1/1]${ENDC} MyTonCtrl components update completed" 51 | exit 0 52 | -------------------------------------------------------------------------------- /mytonctrl/scripts/upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | # Set default arguments 11 | author="ton-blockchain" 12 | repo="ton" 13 | branch="master" 14 | srcdir="/usr/src/" 15 | bindir="/usr/bin/" 16 | tmpdir="/tmp/ton_src/" 17 | 18 | # Get arguments 19 | while getopts a:r:b: flag 20 | do 21 | case "${flag}" in 22 | a) author=${OPTARG};; 23 | r) repo=${OPTARG};; 24 | b) branch=${OPTARG};; 25 | esac 26 | done 27 | 28 | # Цвета 29 | COLOR='\033[92m' 30 | ENDC='\033[0m' 31 | 32 | # Установить дополнительные зависимости 33 | apt-get install -y libsecp256k1-dev libsodium-dev ninja-build fio rocksdb-tools liblz4-dev libjemalloc-dev automake libtool 34 | 35 | # bugfix if the files are in the wrong place 36 | wget "https://ton-blockchain.github.io/global.config.json" -O global.config.json 37 | if [ -f "/var/ton-work/keys/liteserver.pub" ]; then 38 | echo "Ok" 39 | else 40 | echo "bugfix" 41 | mkdir /var/ton-work/keys 42 | cp /usr/bin/ton/validator-engine-console/client /var/ton-work/keys/client 43 | cp /usr/bin/ton/validator-engine-console/client.pub /var/ton-work/keys/client.pub 44 | cp /usr/bin/ton/validator-engine-console/server.pub /var/ton-work/keys/server.pub 45 | cp /usr/bin/ton/validator-engine-console/liteserver.pub /var/ton-work/keys/liteserver.pub 46 | 47 | # fix validator.service 48 | sed -i 's/validator-engine\/ton-global.config.json/global.config.json/' /etc/systemd/system/validator.service 49 | systemctl daemon-reload 50 | fi 51 | 52 | if [ ! -d "${bindir}/openssl_3" ]; then 53 | git clone https://github.com/openssl/openssl ${bindir}/openssl_3 54 | cd ${bindir}/openssl_3 55 | git checkout openssl-3.1.4 56 | ./config 57 | make build_libs -j$(nproc) 58 | opensslPath=`pwd` 59 | else 60 | opensslPath=${bindir}/openssl_3 61 | fi 62 | 63 | rm -rf ${tmpdir}/${repo} 64 | mkdir -p ${tmpdir}/${repo} 65 | cd ${tmpdir}/${repo} 66 | echo "https://github.com/${author}/${repo}.git -> ${branch}" 67 | git clone --recursive https://github.com/${author}/${repo}.git . || exit 1 68 | 69 | # Go to work dir 70 | cd ${srcdir}/${repo} 71 | ls -A1 | xargs rm -rf 72 | 73 | # Update code 74 | cp -rfT ${tmpdir}/${repo} . 75 | git checkout ${branch} 76 | 77 | git submodule sync --recursive 78 | git submodule update 79 | 80 | export CC=/usr/bin/clang 81 | export CXX=/usr/bin/clang++ 82 | export CCACHE_DISABLE=1 83 | 84 | # Update binary 85 | cd ${bindir}/${repo} 86 | ls --hide=global.config.json | xargs -d '\n' rm -rf 87 | rm -rf .ninja_* 88 | memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') 89 | cpuNumber=$(cat /proc/cpuinfo | grep "processor" | wc -l) 90 | 91 | 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 92 | ninja -j ${cpuNumber} fift validator-engine lite-client pow-miner validator-engine-console generate-random-id dht-server func tonlibjson rldp-http-proxy 93 | systemctl restart validator 94 | 95 | # Конец 96 | echo -e "${COLOR}[1/1]${ENDC} TON components update completed" 97 | exit 0 98 | -------------------------------------------------------------------------------- /mytonctrl/scripts/validator-desync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | stats=$(validator-console -c getstats) 5 | unixtime=$(echo "$stats" | grep unixtime | awk '{print $2}') 6 | mastertime=$(echo "$stats" | grep masterchainblocktime | awk '{print $2}') 7 | if [ -z $mastertime ] 8 | then 9 | result="mastertime is None" 10 | else 11 | result=$(($unixtime-$mastertime)) 12 | fi 13 | 14 | echo $result 15 | -------------------------------------------------------------------------------- /mytonctrl/scripts/xrestart.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import pwd 4 | import time 5 | import threading 6 | import subprocess 7 | 8 | def Xguard(): 9 | timestamp = int(sys.argv[1]) 10 | args = sys.argv[2:] 11 | while True: 12 | time.sleep(1) 13 | timenow = int(time.time()) 14 | if timenow > timestamp: 15 | Xcmd(args) 16 | print("exit") 17 | sys.exit(0) 18 | #end define 19 | 20 | def Xcmd(inputArgs): 21 | print("inputArgs:", inputArgs) 22 | 23 | # stop validator 24 | args = ["systemctl", "stop", "validator"] 25 | subprocess.run(args) 26 | 27 | file = open("/etc/systemd/system/validator.service", 'rt') 28 | text = file.read() 29 | file.close() 30 | lines = text.split('\n') 31 | 32 | for line in lines: 33 | if "ExecStart" not in line: 34 | continue 35 | ExecStart = line.replace("ExecStart = ", '') 36 | args = ExecStart.split(' ') 37 | print("ExecStart args:", args) 38 | args += inputArgs 39 | #end for 40 | 41 | pw_record = pwd.getpwnam("validator") 42 | user_uid = pw_record.pw_uid 43 | user_gid = pw_record.pw_gid 44 | 45 | # start with args 46 | print("args:", args) 47 | process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=demote(user_uid, user_gid)) 48 | process.wait() 49 | text = process.stdout.read().decode() 50 | print("text:", text) 51 | 52 | # Exit program 53 | sys.exit(0) 54 | #end define 55 | 56 | def demote(user_uid, user_gid): 57 | def result(): 58 | os.setgid(user_gid) 59 | os.setuid(user_uid) 60 | os.system("ulimit -n 1") 61 | os.system("ulimit -u 1") 62 | os.system("ulimit -l 1") 63 | return result 64 | #end define 65 | 66 | 67 | ### 68 | ### Start of the program 69 | ### 70 | 71 | if __name__ == "__main__": 72 | Xguard() 73 | #end if 74 | -------------------------------------------------------------------------------- /mytonctrl/utils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | 4 | from mypylib.mypylib import bcolors 5 | 6 | 7 | def timestamp2utcdatetime(timestamp, format="%d.%m.%Y %H:%M:%S"): 8 | datetime = time.gmtime(timestamp) 9 | result = time.strftime(format, datetime) + ' UTC' 10 | return result 11 | 12 | 13 | def GetItemFromList(data, index): 14 | try: 15 | return data[index] 16 | except: 17 | pass 18 | 19 | 20 | def is_hex(s): 21 | try: 22 | int(s, 16) 23 | return True 24 | except ValueError: 25 | return False 26 | 27 | 28 | def fix_git_config(git_path: str): 29 | args = ["git", "status"] 30 | try: 31 | process = subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=git_path, timeout=3) 32 | err = process.stderr.decode("utf-8") 33 | except Exception as e: 34 | err = str(e) 35 | if err: 36 | if 'git config --global --add safe.directory' in err: 37 | args = ["git", "config", "--global", "--add", "safe.directory", git_path] 38 | subprocess.run(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=3) 39 | else: 40 | raise Exception(f'Failed to check git status: {err}') 41 | # end define 42 | 43 | def GetColorInt(data, border, logic, ending=None): 44 | if data is None: 45 | result = "n/a" 46 | elif logic == "more": 47 | if data >= border: 48 | result = bcolors.green_text(data, ending) 49 | else: 50 | result = bcolors.red_text(data, ending) 51 | elif logic == "less": 52 | if data <= border: 53 | result = bcolors.green_text(data, ending) 54 | else: 55 | result = bcolors.red_text(data, ending) 56 | return result 57 | # end define 58 | -------------------------------------------------------------------------------- /mytoninstaller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/mytoninstaller/__init__.py -------------------------------------------------------------------------------- /mytoninstaller/__main__.py: -------------------------------------------------------------------------------- 1 | from mytoninstaller.mytoninstaller import mytoninstaller 2 | 3 | 4 | if __name__ == '__main__': 5 | mytoninstaller() 6 | -------------------------------------------------------------------------------- /mytoninstaller/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import re 4 | import subprocess 5 | import requests 6 | import base64 7 | 8 | from mytoncore.utils import hex2b64, dict2b64 9 | from mytoninstaller.utils import StartMytoncore, GetInitBlock, get_ed25519_pubkey_text 10 | from mypylib.mypylib import ip2int, Dict 11 | 12 | 13 | defaultLocalConfigPath = "/usr/bin/ton/local.config.json" 14 | 15 | 16 | def GetConfig(**kwargs): 17 | path = kwargs.get("path") 18 | file = open(path, 'rt') 19 | text = file.read() 20 | file.close() 21 | config = Dict(json.loads(text)) 22 | return config 23 | #end define 24 | 25 | 26 | def SetConfig(**kwargs): 27 | path = kwargs.get("path") 28 | data = kwargs.get("data") 29 | 30 | # write config 31 | text = json.dumps(data, indent=4) 32 | file = open(path, 'wt') 33 | file.write(text) 34 | file.close() 35 | #end define 36 | 37 | 38 | def backup_config(local, config_path): 39 | backup_path = f"{config_path}.backup" 40 | local.add_log(f"Backup config file '{config_path}' to '{backup_path}'", "debug") 41 | args = ["cp", config_path, backup_path] 42 | subprocess.run(args) 43 | #end define 44 | 45 | 46 | def BackupMconfig(local): 47 | local.add_log("Backup mytoncore config file 'mytoncore.db' to 'mytoncore.db.backup'", "debug") 48 | mconfig_path = local.buffer.mconfig_path 49 | backupPath = mconfig_path + ".backup" 50 | args = ["cp", mconfig_path, backupPath] 51 | subprocess.run(args) 52 | #end define 53 | 54 | 55 | def GetPortsFromVconfig(local): 56 | vconfig_path = local.buffer.vconfig_path 57 | 58 | # read vconfig 59 | local.add_log("read vconfig", "debug") 60 | vconfig = GetConfig(path=vconfig_path) 61 | 62 | # read mconfig 63 | local.add_log("read mconfig", "debug") 64 | mconfig_path = local.buffer.mconfig_path 65 | mconfig = GetConfig(path=mconfig_path) 66 | 67 | # edit mytoncore config file 68 | local.add_log("edit mytoncore config file", "debug") 69 | mconfig.liteClient.liteServer.port = mconfig.liteservers[0].port 70 | mconfig.validatorConsole.addr = f"127.0.0.1:{mconfig.control[0].port}" 71 | 72 | # write mconfig 73 | local.add_log("write mconfig", "debug") 74 | SetConfig(path=mconfig_path, data=mconfig) 75 | 76 | # restart mytoncore 77 | StartMytoncore(local) 78 | #end define 79 | 80 | 81 | def CreateLocalConfig(local, initBlock, localConfigPath=defaultLocalConfigPath): 82 | # dirty hack, but GetInitBlock() function uses the same technique 83 | from mytoncore import hex2base64 84 | 85 | # read global config file 86 | file = open("/usr/bin/ton/global.config.json", 'rt') 87 | text = file.read() 88 | data = json.loads(text) 89 | file.close() 90 | 91 | # edit config 92 | liteServerConfig = GetLiteServerConfig(local) 93 | data["liteservers"] = [liteServerConfig] 94 | data["validator"]["init_block"]["seqno"] = initBlock["seqno"] 95 | data["validator"]["init_block"]["root_hash"] = hex2base64(initBlock["rootHash"]) 96 | data["validator"]["init_block"]["file_hash"] = hex2base64(initBlock["fileHash"]) 97 | text = json.dumps(data, indent=4) 98 | 99 | # write local config file 100 | file = open(localConfigPath, 'wt') 101 | file.write(text) 102 | file.close() 103 | 104 | # chown 105 | user = local.buffer.user 106 | args = ["chown", "-R", user + ':' + user, localConfigPath] 107 | 108 | print("Local config file created:", localConfigPath) 109 | #end define 110 | 111 | 112 | def get_own_ip(): 113 | pat = re.compile(r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$") 114 | requests.packages.urllib3.util.connection.HAS_IPV6 = False 115 | ip = requests.get("https://ifconfig.me/ip").text 116 | if not pat.fullmatch(ip): 117 | ip = requests.get("https://ipinfo.io/ip").text 118 | if not pat.fullmatch(ip): 119 | raise Exception('Cannot get own IP address') 120 | return ip 121 | #end define 122 | 123 | 124 | def GetLiteServerConfig(local): 125 | keys_dir = local.buffer.keys_dir 126 | liteserver_key = keys_dir + "liteserver" 127 | liteserver_pubkey = liteserver_key + ".pub" 128 | result = Dict() 129 | file = open(liteserver_pubkey, 'rb') 130 | data = file.read() 131 | file.close() 132 | key = base64.b64encode(data[4:]) 133 | ip = get_own_ip() 134 | mconfig = GetConfig(path=local.buffer.mconfig_path) 135 | result.ip = ip2int(ip) 136 | result.port = mconfig.liteClient.liteServer.port 137 | result.id = Dict() 138 | result.id["@type"]= "pub.ed25519" 139 | result.id.key= key.decode() 140 | return result 141 | #end define 142 | 143 | def get_ls_proxy_config(local): 144 | ls_proxy_config_path = "/var/ls_proxy/ls-proxy-config.json" 145 | ls_proxy_config = GetConfig(path=ls_proxy_config_path) 146 | ip = get_own_ip() 147 | port = ls_proxy_config.ListenAddr.split(':')[1] 148 | privkey_text = ls_proxy_config.Clients[0].PrivateKey 149 | 150 | result = Dict() 151 | result.ip = ip2int(ip) 152 | result.port = port 153 | result.id = Dict() 154 | result.id["@type"]= "pub.ed25519" 155 | result.id.key= get_ed25519_pubkey_text(privkey_text) 156 | return result 157 | #end define 158 | -------------------------------------------------------------------------------- /mytoninstaller/mytoninstaller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*- 3 | 4 | import os, sys 5 | import inspect 6 | import random 7 | import json 8 | import subprocess 9 | 10 | import pkg_resources 11 | 12 | from mypylib.mypylib import MyPyClass, run_as_root, color_print 13 | from mypyconsole.mypyconsole import MyPyConsole 14 | 15 | from mytoninstaller.config import GetLiteServerConfig, get_ls_proxy_config 16 | from mytoninstaller.node_args import get_node_args 17 | from mytoninstaller.utils import GetInitBlock 18 | from mytoncore.utils import dict2b64, str2bool, b642dict 19 | 20 | from mytoninstaller.settings import ( 21 | FirstNodeSettings, 22 | FirstMytoncoreSettings, 23 | EnableValidatorConsole, 24 | EnableLiteServer, 25 | EnableDhtServer, 26 | EnableJsonRpc, 27 | enable_ton_http_api, 28 | DangerousRecoveryValidatorConfigFile, 29 | CreateSymlinks, 30 | enable_ls_proxy, 31 | enable_ton_storage, 32 | EnableMode, ConfigureFromBackup, ConfigureOnlyNode, SetInitialSync 33 | ) 34 | from mytoninstaller.config import ( 35 | CreateLocalConfig, 36 | BackupMconfig, 37 | ) 38 | 39 | from functools import partial 40 | 41 | 42 | def Init(local, console): 43 | local.db.config.isStartOnlyOneProcess = False 44 | local.db.config.logLevel = "debug" 45 | local.db.config.isIgnorLogWarning = True # disable warning 46 | local.run() 47 | local.db.config.isIgnorLogWarning = False # enable warning 48 | 49 | 50 | # create variables 51 | user = os.environ.get("USER", "root") 52 | local.buffer.user = user 53 | local.buffer.vuser = "validator" 54 | local.buffer.cport = random.randint(2000, 65000) 55 | local.buffer.lport = random.randint(2000, 65000) 56 | 57 | # this funciton injects MyPyClass instance 58 | def inject_globals(func): 59 | args = [] 60 | for arg_name in inspect.getfullargspec(func)[0]: 61 | if arg_name == 'local': 62 | args.append(local) 63 | return partial(func, *args) 64 | 65 | # Create user console 66 | console.name = "MyTonInstaller" 67 | console.color = console.RED 68 | console.AddItem("status", inject_globals(Status), "Print TON component status") 69 | console.AddItem("set_node_argument", inject_globals(set_node_argument), "Set node argument") 70 | console.AddItem("enable", inject_globals(Enable), "Enable some function") 71 | console.AddItem("update", inject_globals(Enable), "Update some function: 'JR' - jsonrpc. Example: 'update JR'") 72 | console.AddItem("plsc", inject_globals(PrintLiteServerConfig), "Print lite-server config") 73 | console.AddItem("clcf", inject_globals(CreateLocalConfigFile), "Create lite-server config file") 74 | console.AddItem("print_ls_proxy_config", inject_globals(print_ls_proxy_config), "Print ls-proxy config") 75 | console.AddItem("create_ls_proxy_config_file", inject_globals(create_ls_proxy_config_file), "Create ls-proxy config file") 76 | console.AddItem("drvcf", inject_globals(DRVCF), "Dangerous recovery validator config file") 77 | console.AddItem("setwebpass", inject_globals(SetWebPassword), "Set a password for the web admin interface") 78 | 79 | Refresh(local) 80 | #end define 81 | 82 | 83 | def Refresh(local): 84 | user = local.buffer.user 85 | local.buffer.mconfig_path = "/home/{user}/.local/share/mytoncore/mytoncore.db".format(user=user) 86 | if user == 'root': 87 | local.buffer.mconfig_path = "/usr/local/bin/mytoncore/mytoncore.db" 88 | #end if 89 | 90 | # create variables 91 | bin_dir = "/usr/bin/" 92 | src_dir = "/usr/src/" 93 | ton_work_dir = "/var/ton-work/" 94 | ton_bin_dir = bin_dir + "ton/" 95 | ton_src_dir = src_dir + "ton/" 96 | mtc_src_dir = src_dir + "mytonctrl/" 97 | local.buffer.bin_dir = bin_dir 98 | local.buffer.src_dir = src_dir 99 | local.buffer.ton_work_dir = ton_work_dir 100 | local.buffer.ton_bin_dir = ton_bin_dir 101 | local.buffer.ton_src_dir = ton_src_dir 102 | local.buffer.mtc_src_dir = mtc_src_dir 103 | ton_db_dir = ton_work_dir + "db/" 104 | keys_dir = ton_work_dir + "keys/" 105 | local.buffer.ton_db_dir = ton_db_dir 106 | local.buffer.keys_dir = keys_dir 107 | local.buffer.ton_log_path = ton_work_dir + "log" 108 | local.buffer.validator_app_path = ton_bin_dir + "validator-engine/validator-engine" 109 | local.buffer.global_config_path = ton_bin_dir + "global.config.json" 110 | local.buffer.vconfig_path = ton_db_dir + "config.json" 111 | #end define 112 | 113 | 114 | def Status(local, args): 115 | keys_dir = local.buffer.keys_dir 116 | server_key = keys_dir + "server" 117 | client_key = keys_dir + "client" 118 | liteserver_key = keys_dir + "liteserver" 119 | liteserver_pubkey = liteserver_key + ".pub" 120 | 121 | statuses = { 122 | 'Full node status': os.path.isfile(local.buffer.vconfig_path), 123 | 'Mytoncore status': os.path.isfile(local.buffer.mconfig_path), 124 | 'V.console status': os.path.isfile(server_key) or os.path.isfile(client_key), 125 | 'Liteserver status': os.path.isfile(liteserver_pubkey) 126 | } 127 | 128 | color_print("{cyan}===[ Services status ]==={endc}") 129 | for item in statuses.items(): 130 | status = '{green}enabled{endc}' if item[1] else '{red}disabled{endc}' 131 | color_print(f"{item[0]}: {status}") 132 | 133 | node_args = get_node_args() 134 | color_print("{cyan}===[ Node arguments ]==={endc}") 135 | for key, value in node_args.items(): 136 | for v in value: 137 | print(f"{key}: {v}") 138 | #end define 139 | 140 | 141 | def set_node_argument(local, args): 142 | if len(args) < 1: 143 | color_print("{red}Bad args. Usage:{endc} set_node_argument [arg-value] [-d (to delete)].\n" 144 | "Examples: 'set_node_argument --archive-ttl 86400' or 'set_node_argument --archive-ttl -d' or 'set_node_argument -M' or 'set_node_argument --add-shard 0:2000000000000000 0:a000000000000000'") 145 | return 146 | arg_name = args[0] 147 | args = [arg_name, " ".join(args[1:])] 148 | script_path = pkg_resources.resource_filename('mytoninstaller.scripts', 'set_node_argument.py') 149 | run_as_root(['python3', script_path] + args) 150 | color_print("set_node_argument - {green}OK{endc}") 151 | #end define 152 | 153 | 154 | def Enable(local, args): 155 | try: 156 | name = args[0] 157 | except: 158 | color_print("{red}Bad args. Usage:{endc} enable ") 159 | print("'FN' - Full node") 160 | print("'VC' - Validator console") 161 | print("'LS' - Lite-Server") 162 | print("'DS' - DHT-Server") 163 | print("'JR' - jsonrpc") 164 | print("'THA' - ton-http-api") 165 | print("'LSP' - ls-proxy") 166 | print("'TS' - ton-storage") 167 | print("Example: 'enable FN'") 168 | return 169 | if name == "THA": 170 | CreateLocalConfigFile(local, args) 171 | args = ["python3", "-m", "mytoninstaller", "-u", local.buffer.user, "-e", f"enable{name}"] 172 | run_as_root(args) 173 | #end define 174 | 175 | 176 | def DRVCF(local, args): 177 | user = local.buffer["user"] 178 | args = ["python3", "-m", "mytoninstaller", "-u", local.buffer.user, "-e", "drvcf"] 179 | run_as_root(args) 180 | #end define 181 | 182 | 183 | def SetWebPassword(args): 184 | args = ["python3", "/usr/src/mtc-jsonrpc/mtc-jsonrpc.py", "-p"] 185 | subprocess.run(args) 186 | #end define 187 | 188 | 189 | def PrintLiteServerConfig(local, args): 190 | liteServerConfig = GetLiteServerConfig(local) 191 | text = json.dumps(liteServerConfig, indent=4) 192 | print(text) 193 | #end define 194 | 195 | 196 | def CreateLocalConfigFile(local, args): 197 | initBlock = GetInitBlock() 198 | initBlock_b64 = dict2b64(initBlock) 199 | user = local.buffer.user or os.environ.get("USER", "root") 200 | args = ["python3", "-m", "mytoninstaller", "-u", user, "-e", "clc", "-i", initBlock_b64] 201 | run_as_root(args) 202 | #end define 203 | 204 | def print_ls_proxy_config(local, args): 205 | ls_proxy_config = get_ls_proxy_config(local) 206 | text = json.dumps(ls_proxy_config, indent=4) 207 | print(text) 208 | #end define 209 | 210 | def create_ls_proxy_config_file(local, args): 211 | print("TODO") 212 | #end define 213 | 214 | def Event(local, name): 215 | if name == "enableFN": 216 | FirstNodeSettings(local) 217 | if name == "enableVC": 218 | EnableValidatorConsole(local) 219 | if name == "enableLS": 220 | EnableLiteServer(local) 221 | if name == "enableDS": 222 | EnableDhtServer(local) 223 | if name == "drvcf": 224 | DangerousRecoveryValidatorConfigFile(local) 225 | if name == "enableJR": 226 | EnableJsonRpc(local) 227 | if name == "enableTHA": 228 | enable_ton_http_api(local) 229 | if name == "enableLSP": 230 | enable_ls_proxy(local) 231 | if name == "enableTS": 232 | enable_ton_storage(local) 233 | if name == "clc": 234 | ix = sys.argv.index("-i") 235 | initBlock_b64 = sys.argv[ix+1] 236 | initBlock = b642dict(initBlock_b64) 237 | CreateLocalConfig(local, initBlock) 238 | local.exit() 239 | #end define 240 | 241 | 242 | def Command(local, args, console): 243 | cmd = args[0] 244 | args = args[1:] 245 | for item in console.menu_items: 246 | if cmd == item.cmd: 247 | console._try(item.func, args) 248 | print() 249 | local.exit() 250 | print(console.unknown_cmd) 251 | local.exit() 252 | #end define 253 | 254 | 255 | def General(local, console): 256 | if "-u" in sys.argv: 257 | ux = sys.argv.index("-u") 258 | user = sys.argv[ux+1] 259 | local.buffer.user = user 260 | Refresh(local) 261 | if "-c" in sys.argv: 262 | cx = sys.argv.index("-c") 263 | args = sys.argv[cx+1].split() 264 | Command(local, args, console) 265 | if "-e" in sys.argv: 266 | ex = sys.argv.index("-e") 267 | name = sys.argv[ex+1] 268 | Event(local, name) 269 | if "-t" in sys.argv: 270 | mx = sys.argv.index("-t") 271 | telemetry = sys.argv[mx+1] 272 | local.buffer.telemetry = str2bool(telemetry) 273 | if "--dump" in sys.argv: 274 | mx = sys.argv.index("--dump") 275 | dump = sys.argv[mx+1] 276 | local.buffer.dump = str2bool(dump) 277 | if "-m" in sys.argv: 278 | mx = sys.argv.index("-m") 279 | mode = sys.argv[mx+1] 280 | local.buffer.mode = mode 281 | if "--only-mtc" in sys.argv: 282 | ox = sys.argv.index("--only-mtc") 283 | local.buffer.only_mtc = str2bool(sys.argv[ox+1]) 284 | if "--only-node" in sys.argv: 285 | ox = sys.argv.index("--only-node") 286 | local.buffer.only_node = str2bool(sys.argv[ox+1]) 287 | if "--backup" in sys.argv: 288 | bx = sys.argv.index("--backup") 289 | backup = sys.argv[bx+1] 290 | if backup != "none": 291 | local.buffer.backup = backup 292 | #end if 293 | 294 | FirstMytoncoreSettings(local) 295 | FirstNodeSettings(local) 296 | EnableValidatorConsole(local) 297 | EnableLiteServer(local) 298 | BackupMconfig(local) 299 | CreateSymlinks(local) 300 | EnableMode(local) 301 | ConfigureFromBackup(local) 302 | ConfigureOnlyNode(local) 303 | SetInitialSync(local) 304 | #end define 305 | 306 | 307 | ### 308 | ### Start of the program 309 | ### 310 | def mytoninstaller(): 311 | local = MyPyClass(__file__) 312 | console = MyPyConsole() 313 | 314 | Init(local, console) 315 | if len(sys.argv) > 1: 316 | General(local, console) 317 | else: 318 | console.Run() 319 | local.exit() 320 | -------------------------------------------------------------------------------- /mytoninstaller/node_args.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def get_validator_service(): 4 | path = '/etc/systemd/system/validator.service' 5 | with open(path, 'r') as file: 6 | return file.read() 7 | #end define 8 | 9 | 10 | def get_node_start_command(): 11 | service = get_validator_service() 12 | for line in service.split('\n'): 13 | if line.startswith('ExecStart'): 14 | return line.split('=')[1].strip() 15 | #end define 16 | 17 | def get_node_args(start_command: str = None): 18 | if start_command is None: 19 | start_command = get_node_start_command() 20 | #end if 21 | 22 | result = dict() # {key: [value1, value2]} 23 | node_args = start_command.split(' ')[1:] 24 | key = None 25 | for item in node_args: 26 | if item.startswith('-'): 27 | key = item 28 | result[key] = list() 29 | else: 30 | result[key].append(item) 31 | return result 32 | #end define 33 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/mytoninstaller/scripts/__init__.py -------------------------------------------------------------------------------- /mytoninstaller/scripts/add2systemd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | post="/bin/echo service down" 11 | user=root 12 | group=root 13 | 14 | while getopts n:s:p:u:g: flag 15 | do 16 | case "${flag}" in 17 | n) name=${OPTARG};; 18 | s) start=${OPTARG};; 19 | p) post=${OPTARG};; 20 | u) user=${OPTARG};; 21 | g) group=${OPTARG};; 22 | esac 23 | done 24 | 25 | if [ -z "$name" ]; then 26 | echo "name is empty" 27 | exit 1 28 | fi 29 | 30 | if [ -z "$start" ]; then 31 | echo "start is empty" 32 | exit 1 33 | fi 34 | 35 | 36 | DAEMON_PATH="/etc/systemd/system/${name}.service" 37 | 38 | cat < $DAEMON_PATH 39 | [Unit] 40 | Description = $name service. Created by https://github.com/igroman787/mypylib. 41 | After = network.target 42 | 43 | [Service] 44 | Type = simple 45 | Restart = always 46 | RestartSec = 30 47 | ExecStart = $start 48 | ExecStopPost = $post 49 | User = $user 50 | Group = $group 51 | LimitNOFILE = infinity 52 | LimitNPROC = infinity 53 | LimitMEMLOCK = infinity 54 | 55 | [Install] 56 | WantedBy = multi-user.target 57 | EOF 58 | 59 | chmod 664 $DAEMON_PATH 60 | chmod +x $DAEMON_PATH 61 | systemctl daemon-reload 62 | systemctl enable ${name} 63 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/jsonrpcinstaller.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | # Get arguments 11 | while getopts u: flag 12 | do 13 | case "${flag}" in 14 | u) user=${OPTARG};; 15 | esac 16 | done 17 | 18 | author=kdimentionaltree 19 | repo=mtc-jsonrpc 20 | branch=master 21 | 22 | echo "User: $user" 23 | echo "Workdir: `pwd`" 24 | 25 | # Цвета 26 | COLOR='\033[95m' 27 | ENDC='\033[0m' 28 | 29 | # Установка компонентов python3 30 | echo -e "${COLOR}[1/4]${ENDC} Installing required packages" 31 | pip3 install Werkzeug json-rpc cloudscraper pyotp jsonpickle # todo: set versions 32 | 33 | # Клонирование репозиториев с github.com 34 | echo -e "${COLOR}[2/4]${ENDC} Cloning github repository" 35 | echo "https://github.com/${author}/${repo}.git -> ${branch}" 36 | 37 | cd /usr/src/ 38 | rm -rf mtc-jsonrpc 39 | git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git 40 | 41 | # Прописать автозагрузку 42 | echo -e "${COLOR}[3/4]${ENDC} Add to startup" 43 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 44 | echo "Script dir: ${SCRIPT_DIR}" 45 | ${SCRIPT_DIR}/add2systemd.sh -n mtc-jsonrpc -s "/usr/bin/python3 /usr/src/mtc-jsonrpc/mtc-jsonrpc.py" -u ${user} -g ${user} 46 | systemctl restart mtc-jsonrpc 47 | 48 | # Выход из программы 49 | echo -e "${COLOR}[4/4]${ENDC} JsonRPC installation complete" 50 | exit 0 51 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/ls_proxy_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # import functions: check_superuser, get_cpu_number, check_go_version 5 | my_dir=$(dirname $(realpath ${0})) 6 | . ${my_dir}/utils.sh 7 | 8 | # Проверить sudo 9 | check_superuser 10 | 11 | # install parameters 12 | src_path=/usr/src 13 | bin_path=/usr/bin 14 | openssl_path=${bin_path}/openssl_3 15 | 16 | # Get arguments 17 | while getopts u:s:b:o: flag 18 | do 19 | case "${flag}" in 20 | u) user=${OPTARG};; 21 | s) src_path=${OPTARG};; 22 | b) bin_path=${OPTARG};; 23 | o) openssl_path=${OPTARG};; 24 | *) 25 | echo "Flag -${flag} is not recognized. Aborting" 26 | exit 1;; 27 | esac 28 | done 29 | 30 | # install parameters 31 | author=xssnick 32 | repo=tonutils-liteserver-proxy 33 | branch=master 34 | bin_name=ls_proxy 35 | 36 | # Цвета 37 | COLOR='\033[95m' 38 | ENDC='\033[0m' 39 | 40 | # Клонирование репозиториев с github.com 41 | echo -e "${COLOR}[1/4]${ENDC} Cloning github repository" 42 | echo "https://github.com/${author}/${repo}.git -> ${branch}" 43 | 44 | 45 | package_src_path=${src_path}/${repo} 46 | rm -rf ${package_src_path} 47 | 48 | cd ${src_path} 49 | git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git 50 | 51 | # Установка компонентов 52 | echo -e "${COLOR}[2/4]${ENDC} Installing required packages" 53 | go_path=/usr/local/go/bin/go 54 | check_go_version "${package_src_path}/go.mod" ${go_path} 55 | 56 | # Компилируем из исходников 57 | cpu_number=$(get_cpu_number) 58 | echo -e "${COLOR}[3/4]${ENDC} Source compilation, use ${cpu_number} cpus" 59 | 60 | ton_src_path=${package_src_path}/ton 61 | proxy_internal_path=${package_src_path}/internal/emulate/lib 62 | 63 | proxy_build_path=${bin_path}/${bin_name} 64 | ton_build_path=${proxy_build_path}/ton 65 | db_path=/var/${bin_name} 66 | lib_path=${db_path}/lib 67 | 68 | mkdir -p ${lib_path} 69 | mkdir -p ${ton_build_path} && cd ${ton_build_path} 70 | cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_FOUND=1 -DOPENSSL_INCLUDE_DIR=${openssl_path}/include -DOPENSSL_CRYPTO_LIBRARY=${openssl_path}/libcrypto.a ${ton_src_path} 71 | make emulator -j ${cpu_number} 72 | cp ${ton_build_path}/emulator/libemulator.so ${lib_path} 73 | cp ${ton_build_path}/emulator/libemulator.so ${proxy_internal_path} 74 | cp ${ton_build_path}/emulator/emulator_export.h ${proxy_internal_path} 75 | 76 | # Компилируем 77 | cd ${package_src_path} 78 | entry_point=$(find ${package_src_path} -name "main.go" | head -n 1) 79 | CGO_ENABLED=1 ${go_path} build -o ${db_path}/${bin_name} ${entry_point} 80 | 81 | # Настроить директорию работы 82 | chown -R ${user}:${user} ${db_path} 83 | 84 | # Выход из программы 85 | echo -e "${COLOR}[4/4]${ENDC} ${bin_name} installation complete" 86 | exit 0 -------------------------------------------------------------------------------- /mytoninstaller/scripts/process_hardforks.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import subprocess 4 | import sys 5 | import time 6 | import logging 7 | 8 | 9 | def run_vc(cmd: str): 10 | if binary: 11 | return subprocess.run([binary, '-k', client, '-p', server, '-a', address, '--cmd', cmd], 12 | stdout=subprocess.PIPE).stdout.decode() 13 | else: # use symlink 14 | return subprocess.run(['bash', 'validator-console', '--cmd', cmd], 15 | stdout=subprocess.PIPE).stdout.decode() 16 | 17 | def get_last_mc_seqno(): 18 | stats = run_vc('getstats') 19 | for line in stats.split('\n'): 20 | if line.startswith('masterchainblock'): 21 | return int(line.split()[1].split(',')[2].split(')')[0]) 22 | 23 | def restart_node(): 24 | return subprocess.run(['systemctl', 'restart', 'validator']).returncode 25 | 26 | def get_config(): 27 | with open(config_path) as f: 28 | return json.load(f) 29 | 30 | def set_config(config: dict): 31 | with open(config_path, 'w') as f: 32 | f.write(json.dumps(config, indent=4)) 33 | 34 | def set_hardforks(hfks: list): 35 | c = get_config() 36 | c['validator']['hardforks'] = hfks 37 | set_config(c) 38 | 39 | def add_hardfork(hfk: dict): 40 | c = get_config() 41 | c['validator']['hardforks'].append(hfk) 42 | set_config(c) 43 | 44 | 45 | logger = logging.getLogger(__name__) 46 | logging.basicConfig( 47 | level=logging.DEBUG, 48 | format='%(asctime)s - %(levelname)s - %(message)s', 49 | datefmt='%Y-%m-%d %H:%M:%S' 50 | ) 51 | 52 | parser = argparse.ArgumentParser(description="Python script to process hardforks when syncing archive node.") 53 | parser.add_argument('--from-seqno', type=int, required=True, help='Global config path') 54 | parser.add_argument('--config-path', type=str, required=True, help='Global config path') 55 | parser.add_argument('--bin', type=str, help='VC bin') 56 | parser.add_argument('--client', type=str, help='VC Client') 57 | parser.add_argument('--server', type=str, help='VC Server') 58 | parser.add_argument('--address', type=str, help='VC Address') 59 | 60 | args = parser.parse_args() 61 | config_path = args.config_path 62 | binary = args.bin 63 | client = args.client 64 | server = args.server 65 | address = args.address 66 | from_seqno = args.from_seqno 67 | 68 | 69 | hardforks = get_config()['validator']['hardforks'] 70 | keep_hardforks = [h for h in hardforks if h['seqno'] <= from_seqno] 71 | hardforks = [h for h in hardforks if h['seqno'] > from_seqno] 72 | if len(hardforks) == 0: 73 | logger.info("No hardforks to process.") 74 | sys.exit(0) 75 | set_hardforks(keep_hardforks) 76 | 77 | while True: 78 | if len(hardforks) == 0: 79 | break 80 | try: 81 | last_mc_seqno = get_last_mc_seqno() 82 | if not last_mc_seqno: 83 | logger.info("Waiting for last mc seqno...") 84 | time.sleep(300) 85 | continue 86 | if last_mc_seqno != hardforks[0]['seqno'] - 1: 87 | logger.info(f"Waiting for hardfork {hardforks[0]['seqno']}...") 88 | time.sleep(300) 89 | continue 90 | hardfork = hardforks.pop(0) 91 | logger.info(f"Processing hardfork {hardfork['seqno']}.") 92 | add_hardfork(hardfork) 93 | logger.info(f"Hardfork {hardfork['seqno']} has been added.") 94 | restart_node() 95 | logger.info(f"Node is restarted") 96 | except Exception as e: 97 | import traceback 98 | logger.error(f"Exception occurred: {e}\n{traceback.format_exc()}") 99 | time.sleep(60) 100 | 101 | logger.info(f"All done.") 102 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/pytonv3installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Проверить sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | # Get arguments 11 | while getopts u: flag 12 | do 13 | case "${flag}" in 14 | u) user=${OPTARG};; 15 | esac 16 | done 17 | 18 | # Цвета 19 | COLOR='\033[92m' 20 | ENDC='\033[0m' 21 | 22 | # Установка компонентов python3 23 | echo -e "${COLOR}[1/4]${ENDC} Installing required packages" 24 | pip3 install pipenv==2022.3.28 25 | 26 | # Клонирование репозиториев с github.com 27 | echo -e "${COLOR}[2/4]${ENDC} Cloning github repository" 28 | cd /usr/src 29 | rm -rf pytonv3 30 | #git clone https://github.com/EmelyanenkoK/pytonv3 31 | git clone https://github.com/igroman787/pytonv3 32 | 33 | # Установка модуля 34 | cd /usr/src/pytonv3 35 | python3 setup.py install 36 | 37 | # Скомпилировать недостающий бинарник 38 | cd /usr/bin/ton && make tonlibjson 39 | 40 | # Прописать автозагрузку 41 | echo -e "${COLOR}[3/4]${ENDC} Add to startup" 42 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 43 | echo "Script dir: ${SCRIPT_DIR}" 44 | ${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} 45 | systemctl restart pytonv3 46 | 47 | # Конец 48 | echo -e "${COLOR}[4/4]${ENDC} pyTONv3 installation complete" 49 | exit 0 50 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/set_node_argument.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | from mytoninstaller.node_args import get_node_args, get_node_start_command, get_validator_service 4 | 5 | 6 | def set_node_arg(arg_name: str, arg_value: str = ''): 7 | """ 8 | :param arg_name: 9 | :param arg_value: arg value. if None, remove the arg; if empty string, argument is set without value 10 | :return: 11 | """ 12 | assert arg_name.startswith('-'), 'arg_name must start with "-" or "--"' 13 | service = get_validator_service() 14 | start_command = get_node_start_command() 15 | if start_command is None: 16 | raise Exception('Cannot find node start command in service file') 17 | first_arg = start_command.split(' ')[0] 18 | if first_arg != '/usr/bin/ton/validator-engine/validator-engine': 19 | raise Exception('Invalid node start command in service file') 20 | #end if 21 | 22 | node_args = get_node_args(start_command) 23 | if arg_value == '-d': 24 | node_args.pop(arg_name, None) 25 | else: 26 | if ' ' in arg_value: 27 | node_args[arg_name] = arg_value.split() 28 | else: 29 | node_args[arg_name] = [arg_value] 30 | #end if 31 | 32 | buffer = list() 33 | buffer.append(first_arg) 34 | for key, value_list in node_args.items(): 35 | if len(value_list) == 0: 36 | buffer.append(f"{key}") 37 | for value in value_list: 38 | buffer.append(f"{key} {value}") 39 | new_start_command = ' '.join(buffer) 40 | new_service = service.replace(start_command, new_start_command) 41 | with open('/etc/systemd/system/validator.service', 'w') as file: 42 | file.write(new_service) 43 | restart_node() 44 | #end define 45 | 46 | 47 | def restart_node(): 48 | exit_code = subprocess.run(["systemctl", "daemon-reload"]).returncode 49 | if exit_code: 50 | raise Exception(f"`systemctl daemon-reload` failed with exit code {exit_code}") 51 | exit_code = subprocess.run(["systemctl", "restart", "validator"]).returncode 52 | if exit_code: 53 | raise Exception(f"`systemctl restart validator` failed with exit code {exit_code}") 54 | #end define 55 | 56 | 57 | if __name__ == '__main__': 58 | name = sys.argv[1] 59 | value = sys.argv[2] if len(sys.argv) > 2 else '' 60 | set_node_arg(name, value) 61 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/ton_http_api_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # check sudo 5 | if [ "$(id -u)" != "0" ]; then 6 | echo "Please run script as root" 7 | exit 1 8 | fi 9 | 10 | # Цвета 11 | COLOR='\033[92m' 12 | ENDC='\033[0m' 13 | 14 | # install python3 packages 15 | pip3 install virtualenv==20.27.1 16 | 17 | # prepare the virtual environment 18 | echo -e "${COLOR}[1/4]${ENDC} Preparing the virtual environment" 19 | venv_path="/opt/virtualenv/ton_http_api" 20 | virtualenv ${venv_path} 21 | 22 | # install python3 packages 23 | echo -e "${COLOR}[2/4]${ENDC} Installing required packages" 24 | user=$(logname) 25 | venv_pip3="${venv_path}/bin/pip3" 26 | ${venv_pip3} install ton-http-api 27 | chown -R ${user}:${user} ${venv_path} 28 | 29 | # add to startup 30 | echo -e "${COLOR}[3/4]${ENDC} Add to startup" 31 | venv_ton_http_api="${venv_path}/bin/ton-http-api" 32 | tonlib_path="/usr/bin/ton/tonlib/libtonlibjson.so" 33 | ls_config="/usr/bin/ton/local.config.json" 34 | cmd="from sys import path; path.append('/usr/src/mytonctrl/'); from mypylib.mypylib import add2systemd; add2systemd(name='ton_http_api', user='${user}', start='${venv_ton_http_api} --logs-level=INFO --host 127.0.0.1 --port 8801 --liteserver-config ${ls_config} --cdll-path ${tonlib_path} --tonlib-keystore /tmp/tonlib_keystore/')" 35 | python3 -c "${cmd}" 36 | systemctl daemon-reload 37 | systemctl restart ton_http_api 38 | 39 | # check connection 40 | echo -e "Requesting masterchain info from local ton http api" 41 | sleep 5 42 | curl http://127.0.0.1:8801/getMasterchainInfo 43 | 44 | # end 45 | echo -e "${COLOR}[4/4]${ENDC} ton_http_api service installation complete" 46 | exit 0 47 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/ton_storage_installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # import functions: check_superuser, check_go_version 5 | my_dir=$(dirname $(realpath ${0})) 6 | . ${my_dir}/utils.sh 7 | 8 | # Проверить sudo 9 | check_superuser 10 | 11 | # install parameters 12 | src_path=/usr/src 13 | bin_path=/usr/bin 14 | 15 | # Get arguments 16 | while getopts u:s:b: flag 17 | do 18 | case "${flag}" in 19 | u) user=${OPTARG};; 20 | s) src_path=${OPTARG};; 21 | b) bin_path=${OPTARG};; 22 | *) 23 | echo "Flag -${flag} is not recognized. Aborting" 24 | exit 1;; 25 | esac 26 | done 27 | 28 | # install parameters 29 | author=xssnick 30 | repo=tonutils-storage 31 | branch=master 32 | bin_name=ton_storage 33 | 34 | # Цвета 35 | COLOR='\033[95m' 36 | ENDC='\033[0m' 37 | 38 | # Клонирование репозиториев с github.com 39 | echo -e "${COLOR}[1/4]${ENDC} Cloning github repository" 40 | echo "https://github.com/${author}/${repo}.git -> ${branch}" 41 | 42 | package_src_path="${src_path}/${repo}" 43 | rm -rf ${package_src_path} 44 | 45 | cd ${src_path} 46 | git clone --branch=${branch} --recursive https://github.com/${author}/${repo}.git 47 | 48 | # Установка компонентов 49 | echo -e "${COLOR}[2/4]${ENDC} Installing required packages" 50 | go_path=/usr/local/go/bin/go 51 | check_go_version "${package_src_path}/go.mod" ${go_path} 52 | 53 | # Компилируем из исходников 54 | echo -e "${COLOR}[3/4]${ENDC} Source compilation" 55 | 56 | # Создать директорию работы 57 | db_path="/var/${bin_name}" 58 | mkdir -p ${db_path} 59 | 60 | # Компилируем 61 | cd ${package_src_path} 62 | #entry_point=$(find ${package_src_path} -name "main.go" | head -n 1) 63 | CGO_ENABLED=1 ${go_path} build -o ${db_path}/${bin_name} ${package_src_path}/cli/main.go 64 | 65 | # Настроить директорию работы 66 | chown -R ${user}:${user} ${db_path} 67 | 68 | # Выход из программы 69 | echo -e "${COLOR}[4/4]${ENDC} ${bin_name} installation complete" 70 | exit 0 71 | -------------------------------------------------------------------------------- /mytoninstaller/scripts/utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_superuser() { 4 | if [ "$(id -u)" != "0" ]; then 5 | echo "Please run script as root" 6 | exit 1 7 | fi 8 | } 9 | 10 | get_cpu_number() { 11 | if [[ "$OSTYPE" =~ darwin.* ]]; then 12 | cpu_number=$(sysctl -n hw.logicalcpu) 13 | else 14 | memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') 15 | cpu_number=$(($memory/2100000)) 16 | max_cpu_number=$(nproc) 17 | if [ ${cpu_number} -gt ${max_cpu_number} ]; then 18 | cpu_number=$((${max_cpu_number}-1)) 19 | fi 20 | if [ ${cpu_number} == 0 ]; then 21 | echo "Warning! insufficient RAM" 22 | cpu_number=1 23 | fi 24 | fi 25 | echo ${cpu_number} 26 | } 27 | 28 | check_go_version() { 29 | go_mod_path=${1} 30 | go_path=${2} 31 | go_mod_text=$(cat ${go_mod_path}) || exit 1 32 | need_version_text=$(echo "${go_mod_text}" | grep "go " | head -n 1 | awk '{print $2}') 33 | current_version_text=$(${go_path} version | awk '{print $3}' | sed 's\go\\g') 34 | echo "start check_go_version function, need_version: ${need_version_text}, current_version: ${current_version_text}" 35 | current_version_1=$(echo ${current_version_text} | cut -d "." -f 1) 36 | current_version_2=$(echo ${current_version_text} | cut -d "." -f 2) 37 | current_version_3=$(echo ${current_version_text} | cut -d "." -f 3) 38 | need_version_1=$(echo ${need_version_text} | cut -d "." -f 1) 39 | need_version_2=$(echo ${need_version_text} | cut -d "." -f 2) 40 | need_version_3=$(echo ${need_version_text} | cut -d "." -f 3) 41 | if (( need_version_1 > current_version_1 )) || ((need_version_2 > current_version_2 )) || ((need_version_3 > current_version_3 )); then 42 | install_go 43 | fi 44 | } 45 | 46 | install_go() { 47 | echo "start install_go function" 48 | arc=$(dpkg --print-architecture) 49 | go_version_url=https://go.dev/VERSION?m=text 50 | go_version=$(curl -s ${go_version_url} | head -n 1) 51 | go_url=https://go.dev/dl/${go_version}.linux-${arc}.tar.gz 52 | rm -rf /usr/local/go 53 | wget -c ${go_url} -O - | tar -C /usr/local -xz 54 | } 55 | -------------------------------------------------------------------------------- /mytoninstaller/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import time 4 | import subprocess 5 | 6 | import requests 7 | from nacl.signing import SigningKey 8 | 9 | 10 | def GetInitBlock(): 11 | from mypylib.mypylib import MyPyClass 12 | from mytoncore import MyTonCore 13 | 14 | mytoncore_local = MyPyClass('mytoncore.py') 15 | ton = MyTonCore(mytoncore_local) 16 | initBlock = ton.GetInitBlock() 17 | return initBlock 18 | #end define 19 | 20 | def start_service(local, service_name:str, sleep:int=1): 21 | local.add_log(f"Start/restart {service_name} service", "debug") 22 | args = ["systemctl", "restart", service_name] 23 | subprocess.run(args) 24 | 25 | local.add_log(f"sleep {sleep} sec", "debug") 26 | time.sleep(sleep) 27 | #end define 28 | 29 | def stop_service(local, service_name:str): 30 | local.add_log(f"Stop {service_name} service", "debug") 31 | args = ["systemctl", "stop", service_name] 32 | subprocess.run(args) 33 | #end define 34 | 35 | def disable_service(local, service_name: str): 36 | local.add_log(f"Disable {service_name} service", "debug") 37 | args = ["systemctl", "disable", service_name] 38 | subprocess.run(args) 39 | 40 | def StartValidator(local): 41 | start_service(local, "validator", sleep=10) 42 | #end define 43 | 44 | def StartMytoncore(local): 45 | start_service(local, "mytoncore") 46 | #end define 47 | 48 | def get_ed25519_pubkey_text(privkey_text): 49 | privkey = base64.b64decode(privkey_text) 50 | pubkey = get_ed25519_pubkey(privkey) 51 | pubkey_text = base64.b64encode(pubkey).decode("utf-8") 52 | return pubkey_text 53 | #end define 54 | 55 | def get_ed25519_pubkey(privkey): 56 | privkey_obj = SigningKey(privkey) 57 | pubkey = privkey_obj.verify_key.encode() 58 | return pubkey 59 | #end define 60 | 61 | 62 | def is_testnet(local): 63 | testnet_zero_state_root_hash = "gj+B8wb/AmlPk1z1AhVI484rhrUpgSr2oSFIh56VoSg=" 64 | with open(local.buffer.global_config_path) as f: 65 | config = json.load(f) 66 | if config['validator']['zero_state']['root_hash'] == testnet_zero_state_root_hash: 67 | return True 68 | return False 69 | 70 | 71 | def get_block_from_toncenter(local, workchain: int, shard: int = -9223372036854775808, seqno: int = None, utime: int = None): 72 | url = f'https://toncenter.com/api/v2/lookupBlock?workchain={workchain}&shard={shard}' 73 | if is_testnet(local): 74 | url = url.replace('toncenter.com', 'testnet.toncenter.com') 75 | if seqno: 76 | url += f'&seqno={seqno}' 77 | if utime: 78 | url += f'&unixtime={utime}' 79 | local.add_log(f"Requesting block information from {url}", "debug") 80 | resp = requests.get(url) 81 | if resp.status_code != 200: 82 | local.add_log(f"Toncenter API returned status code {resp.status_code}", "error") 83 | raise Exception(f"Toncenter API request failed: {resp.text}") 84 | data = resp.json() 85 | if not data['ok']: 86 | local.add_log(f"Toncenter API returned error: {data.get('error', 'Unknown error')}", "error") 87 | raise Exception(f"Toncenter API returned error: {data.get('error', 'Unknown error')}") 88 | return data['result'] 89 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | crc16==0.1.1 2 | requests==2.32.3 3 | psutil==6.1.0 4 | fastcrc==0.3.2 5 | pynacl==1.5.0 6 | -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytoncore-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytoncore-log.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytonctrl-help_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytonctrl-help_ru.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytonctrl-set_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytonctrl-set_ru.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytonctrl-status_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytonctrl-status_ru.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytonctrl-vas-aw_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytonctrl-vas-aw_ru.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_mytonctrl-wl_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_mytonctrl-wl_ru.png -------------------------------------------------------------------------------- /screens/manual-ubuntu_wget-ls_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/manual-ubuntu_wget-ls_ru.png -------------------------------------------------------------------------------- /screens/mytonctrl-awl.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-awl.jpeg -------------------------------------------------------------------------------- /screens/mytonctrl-ewl.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-ewl.jpeg -------------------------------------------------------------------------------- /screens/mytonctrl-inst.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-inst.jpeg -------------------------------------------------------------------------------- /screens/mytonctrl-mg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-mg.png -------------------------------------------------------------------------------- /screens/mytonctrl-nw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-nw.png -------------------------------------------------------------------------------- /screens/mytonctrl-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-status.png -------------------------------------------------------------------------------- /screens/mytonctrl-status_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-status_ru.png -------------------------------------------------------------------------------- /screens/mytonctrl-wl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/screens/mytonctrl-wl.png -------------------------------------------------------------------------------- /scripts/install.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import subprocess 4 | import time 5 | import questionary 6 | import requests 7 | 8 | 9 | def get_archive_ttl_message(answers: dict): 10 | archive_blocks = answers.get('archive-blocks') 11 | if not archive_blocks and answers['archive-blocks'] != 0: 12 | return 'Send the number of seconds to keep the blocks data in the node database. Press Enter to use default: 2592000 (30 days)' 13 | block_from = archive_blocks.split()[0] 14 | # or sent -1 to store downloaded blocks always 15 | if block_from.isdigit(): 16 | seqno = int(block_from) 17 | url = f'https://toncenter.com/api/v2/getBlockHeader?workchain=-1&shard={-2**63}&seqno={seqno}' 18 | if answers['network'] == 'Testnet': 19 | url = url.replace('toncenter.com', 'testnet.toncenter.com') 20 | data = requests.get(url).json() 21 | if not data['ok']: 22 | raise Exception(f'Failed to get block: {data}') 23 | utime = int(data['result']['gen_utime']) 24 | else: 25 | utime = int(datetime.datetime.strptime(block_from, '%Y-%m-%d').timestamp()) 26 | default_archive_ttl = int(time.time() - (utime - datetime.timedelta(days=30).total_seconds())) 27 | answers['archive-ttl-default'] = '-1' 28 | return f'Send the number of seconds to keep blocks in the node database. Press Enter to keep downloaded blocks always (recommended).\nFor your reference you can use TTL `{default_archive_ttl}` to keep blocks from provided block for 30 days (since {datetime.datetime.fromtimestamp(utime - datetime.timedelta(days=30).total_seconds())}).\nNote: in case you want to keep blocks garbage collection the node will sync 8-10 times slower.' 29 | 30 | 31 | def is_valid_date_format(date_str): 32 | try: 33 | datetime.datetime.strptime(date_str, '%Y-%m-%d') 34 | return True 35 | except ValueError: 36 | return False 37 | 38 | 39 | def validate_archive_blocks(value): 40 | if not value: 41 | return True 42 | parts = value.split() 43 | if len(parts) > 2: 44 | return "Too many parameters provided" 45 | 46 | for part in parts: 47 | if part.isdigit() and int(part) < 0: 48 | return "Block number cannot be negative" 49 | elif not part.isdigit() and not is_valid_date_format(part): 50 | return "Incorrect date format, use YYYY-MM-DD" 51 | if len(parts) == 2 and parts[1].isdigit() and parts[0].isdigit(): 52 | if int(parts[1]) < int(parts[0]): 53 | return "End block seqno cannot be less than start block seqno" 54 | return True 55 | 56 | 57 | def validate_http_url(value): 58 | if not value.startswith("http"): 59 | return "URL must start with http" 60 | return True 61 | 62 | 63 | def validate_digits_or_empty(value): 64 | if value == "": 65 | return True 66 | try: 67 | int(value) 68 | if int(value) <= 0: 69 | return "Input must be a positive number" 70 | return True 71 | except ValueError: 72 | return "Input must be a number" 73 | 74 | 75 | def validate_state_ttl(value, archive_ttl): 76 | v = validate_digits_or_empty(value) 77 | if v is not True: return v 78 | if archive_ttl and value and int(value) > int(archive_ttl): 79 | return "State TTL cannot be greater than blocks TTL" 80 | return True 81 | 82 | 83 | 84 | def validate_shard_format(value): 85 | if not value: 86 | return True 87 | for shard in value.split(): 88 | if ":" not in shard: 89 | return "Each shard must be in format :" 90 | return True 91 | 92 | 93 | def run_cli(): 94 | mode = questionary.select( 95 | "Select installation mode (More on https://docs.ton.org/participate/nodes/node-types)", 96 | choices=["validator", "liteserver"], 97 | ).unsafe_ask() 98 | 99 | network = questionary.select( 100 | "Select network", 101 | choices=["Mainnet", "Testnet", "Other"], 102 | ).unsafe_ask() 103 | 104 | config = None 105 | if network == "Other": 106 | config = questionary.text( 107 | "Provide network config uri", 108 | validate=validate_http_url 109 | ).unsafe_ask() 110 | 111 | validator_mode = None 112 | if mode == "validator": 113 | validator_mode = questionary.select( 114 | "Select mode for validator usage. You can set up this later", 115 | choices=["Validator wallet", "Nominator pool", "Single pool", "Liquid Staking", "Skip"], 116 | ).unsafe_ask() 117 | 118 | archive_blocks = None 119 | if mode != "validator": 120 | archive_blocks = questionary.text( 121 | "Do you want to download archive blocks via TON Storage? Press Enter to skip.\n" 122 | "If yes, provide block seqno or date to start from and (optionally) block seqno or date to end with (send `1` to download all blocks and setup full archive node).\n" 123 | "Examples: `30850000`, `10000000 10200000`, `2025-01-01`, `2025-01-01 2025-01-30`", 124 | validate=validate_archive_blocks 125 | ).unsafe_ask() 126 | 127 | archive_ttl = None 128 | state_ttl = None 129 | if mode == "liteserver": 130 | temp_answers = { 131 | 'archive-blocks': archive_blocks, 132 | 'network': network 133 | } 134 | archive_ttl = questionary.text( 135 | get_archive_ttl_message(temp_answers), 136 | validate=validate_digits_or_empty 137 | ).unsafe_ask() 138 | if not archive_ttl and 'archive-ttl-default' in temp_answers: 139 | archive_ttl = temp_answers['archive-ttl-default'] 140 | if archive_ttl != '-1': 141 | state_ttl = questionary.text( 142 | 'Send the number of seconds to keep blocks states in the node database. Press Enter to use default: 86400 (24 hours)', 143 | validate= lambda x: validate_state_ttl(x, archive_ttl) 144 | ).unsafe_ask() 145 | 146 | dump = None 147 | if not archive_blocks: 148 | dump = questionary.confirm( 149 | "Do you want to download blockchain's dump? " 150 | "This reduces synchronization time but requires to download a large file" 151 | ).unsafe_ask() 152 | 153 | add_shard = questionary.text( 154 | "Set shards node will sync. Press Enter to sync all shards.\n" 155 | "Format: :. Divide multiple shards with space.\n" 156 | "Example: `0:2000000000000000 0:6000000000000000`", 157 | validate=validate_shard_format 158 | ).unsafe_ask() 159 | 160 | background = questionary.confirm( 161 | "Do you want to run MyTonCtrl installation in the background?" 162 | ).unsafe_ask() 163 | 164 | answers = { 165 | "mode": mode, 166 | "network": network, 167 | "config": config, 168 | "validator-mode": validator_mode, 169 | "archive-blocks": archive_blocks, 170 | "archive-ttl": archive_ttl, 171 | 'state-ttl': state_ttl, 172 | "dump": dump, 173 | "add-shard": add_shard, 174 | "background": background 175 | } 176 | print(answers) 177 | return answers 178 | 179 | 180 | def run_install(answers: dict): 181 | mode = answers["mode"] 182 | network = answers["network"].lower() 183 | config = answers["config"] 184 | archive_ttl = answers["archive-ttl"] 185 | state_ttl = answers["state-ttl"] 186 | add_shard = answers["add-shard"] 187 | validator_mode = answers["validator-mode"] 188 | archive_blocks = answers["archive-blocks"] 189 | dump = answers["dump"] 190 | background = answers["background"] 191 | 192 | command = ['bash', 'install.sh'] 193 | args = f' -n {network}' 194 | 195 | user = os.environ.get("SUDO_USER") or os.environ.get("USER") 196 | args += f' -u {user}' 197 | 198 | if network not in ('mainnet', 'testnet'): 199 | args += f' -c {config}' 200 | 201 | if archive_ttl: 202 | os.environ['ARCHIVE_TTL'] = archive_ttl # set env variable 203 | if state_ttl: 204 | os.environ['STATE_TTL'] = state_ttl 205 | if add_shard: 206 | os.environ['ADD_SHARD'] = add_shard 207 | if archive_blocks: 208 | os.environ['ARCHIVE_BLOCKS'] = archive_blocks 209 | command += ['-v', 'archive-sync'] 210 | 211 | if validator_mode and validator_mode not in ('Skip', 'Validator wallet'): 212 | if validator_mode == 'Nominator pool': 213 | validator_mode = 'nominator-pool' 214 | elif validator_mode == 'Single pool': 215 | validator_mode = 'single-nominator' 216 | elif validator_mode == 'Liquid Staking': 217 | validator_mode = 'liquid-staking' 218 | args += f' -m {validator_mode}' 219 | else: 220 | args += f' -m {mode}' 221 | 222 | if dump: 223 | args += ' -d' 224 | 225 | log = None 226 | stdin = None 227 | if background: 228 | os.environ['PYTHONUNBUFFERED'] = '1' 229 | log = open("mytonctrl_installation.log", "a") 230 | stdin=subprocess.DEVNULL 231 | command = ['nohup'] + command 232 | command += args.split() 233 | 234 | print(command) 235 | 236 | process = subprocess.Popen( 237 | command, 238 | stdout=log, 239 | stderr=log, 240 | stdin=stdin, 241 | ) 242 | if not background: 243 | process.wait() 244 | if background: 245 | print("="*100 + f"\nRunning installation in the background. Check './mytonctrl_installation.log' for progress. PID: {process.pid}\n" + "="*100) 246 | 247 | 248 | def main(): 249 | try: 250 | answers = run_cli() 251 | except KeyboardInterrupt: 252 | print("\nInstallation cancelled by user") 253 | return 254 | run_install(answers) 255 | 256 | 257 | if __name__ == '__main__': 258 | main() 259 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # colors 5 | COLOR='\033[92m' 6 | ENDC='\033[0m' 7 | mydir=`pwd` 8 | 9 | # check sudo permissions 10 | if [ "$(id -u)" != "0" ]; then 11 | echo "Please run script as root" 12 | exit 1 13 | fi 14 | 15 | author="ton-blockchain" 16 | repo="mytonctrl" 17 | branch="master" 18 | network="mainnet" 19 | ton_node_version="master" # Default version 20 | 21 | 22 | show_help_and_exit() { 23 | echo 'Supported arguments:' 24 | echo ' -c PATH Provide custom config for toninstaller.sh' 25 | echo ' -t Disable telemetry' 26 | echo ' -i Ignore minimum requirements' 27 | echo ' -d Use pre-packaged dump. Reduces duration of initial synchronization.' 28 | echo ' -a Set MyTonCtrl git repo author' 29 | echo ' -r Set MyTonCtrl git repo' 30 | echo ' -b Set MyTonCtrl git repo branch' 31 | echo ' -m MODE Install MyTonCtrl with specified mode (validator or liteserver)' 32 | echo ' -n NETWORK Specify the network (mainnet or testnet)' 33 | echo ' -v VERSION Specify the ton node version (commit, branch, or tag)' 34 | echo ' -u USER Specify the user to be used for MyTonCtrl installation' 35 | echo ' -p PATH Provide backup file for MyTonCtrl installation' 36 | echo ' -o Install only MyTonCtrl. Must be used with -p' 37 | echo ' -l Install only TON node' 38 | echo ' -h Show this help' 39 | exit 40 | } 41 | 42 | if [[ "${1-}" =~ ^-*h(elp)?$ ]]; then 43 | show_help_and_exit 44 | fi 45 | 46 | # node install parameters 47 | config="https://ton-blockchain.github.io/global.config.json" 48 | telemetry=true 49 | ignore=false 50 | dump=false 51 | only_mtc=false 52 | only_node=false 53 | backup=none 54 | mode=none 55 | cpu_required=16 56 | mem_required=64000000 # 64GB in KB 57 | 58 | while getopts ":c:tidola:r:b:m:n:v:u:p:h" flag; do 59 | case "${flag}" in 60 | c) config=${OPTARG};; 61 | t) telemetry=false;; 62 | i) ignore=true;; 63 | d) dump=true;; 64 | a) author=${OPTARG};; 65 | r) repo=${OPTARG};; 66 | b) branch=${OPTARG};; 67 | m) mode=${OPTARG};; 68 | n) network=${OPTARG};; 69 | v) ton_node_version=${OPTARG};; 70 | u) user=${OPTARG};; 71 | o) only_mtc=true;; 72 | l) only_node=true;; 73 | p) backup=${OPTARG};; 74 | h) show_help_and_exit;; 75 | *) 76 | echo "Flag -${flag} is not recognized. Aborting" 77 | exit 1 ;; 78 | esac 79 | done 80 | 81 | 82 | if [ "$only_mtc" = true ] && [ "$backup" = "none" ]; then 83 | echo "Backup file must be provided if only mtc installation" 84 | exit 1 85 | fi 86 | 87 | 88 | if [ "${mode}" = "none" ] && [ "$backup" = "none" ]; then # no mode or backup was provided 89 | echo "Running cli installer" 90 | wget https://raw.githubusercontent.com/${author}/${repo}/${branch}/scripts/install.py 91 | if ! command -v pip3 &> /dev/null; then 92 | echo "pip not found. Installing pip..." 93 | python3 -m pip install --upgrade pip 94 | fi 95 | pip3 install questionary==2.1.0 --break-system-packages 96 | python3 install.py 97 | pip3 uninstall questionary -y 98 | exit 99 | fi 100 | 101 | # Set config based on network argument 102 | if [ "${network}" = "testnet" ]; then 103 | config="https://ton-blockchain.github.io/testnet-global.config.json" 104 | cpu_required=8 105 | mem_required=16000000 # 16GB in KB 106 | fi 107 | 108 | # check machine configuration 109 | echo -e "${COLOR}[1/5]${ENDC} Checking system requirements" 110 | 111 | cpus=$(lscpu | grep "CPU(s)" | head -n 1 | awk '{print $2}') 112 | memory=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}') 113 | 114 | echo "This machine has ${cpus} CPUs and ${memory}KB of Memory" 115 | if [ "$ignore" = false ] && ([ "${cpus}" -lt "${cpu_required}" ] || [ "${memory}" -lt "${mem_required}" ]); then 116 | echo "Insufficient resources. Requires a minimum of "${cpu_required}" processors and "${mem_required}" RAM." 117 | exit 1 118 | fi 119 | 120 | echo -e "${COLOR}[2/5]${ENDC} Checking for required TON components" 121 | SOURCES_DIR=/usr/src 122 | BIN_DIR=/usr/bin 123 | 124 | # create dirs for OSX 125 | if [[ "$OSTYPE" =~ darwin.* ]]; then 126 | SOURCES_DIR=/usr/local/src 127 | BIN_DIR=/usr/local/bin 128 | mkdir -p ${SOURCES_DIR} 129 | fi 130 | 131 | if [ ! -f ~/.config/pip/pip.conf ]; then # create pip config 132 | mkdir -p ~/.config/pip 133 | cat > ~/.config/pip/pip.conf < ${branch}" 153 | 154 | # remove previous installation 155 | cd $SOURCES_DIR 156 | rm -rf $SOURCES_DIR/mytonctrl 157 | pip3 uninstall -y mytonctrl 158 | 159 | git clone --branch ${branch} --recursive https://github.com/${author}/${repo}.git ${repo} # TODO: return --recursive back when fix libraries 160 | git config --global --add safe.directory $SOURCES_DIR/${repo} 161 | cd $SOURCES_DIR/${repo} 162 | 163 | pip3 install -U . # TODO: make installation from git directly 164 | 165 | echo -e "${COLOR}[4/5]${ENDC} Running mytoninstaller" 166 | # DEBUG 167 | 168 | if [ "${user}" = "" ]; then # no user 169 | parent_name=$(ps -p $PPID -o comm=) 170 | user=$(whoami) 171 | if [ "$parent_name" = "sudo" ] || [ "$parent_name" = "su" ] || [ "$parent_name" = "python3" ]; then 172 | user=$(logname) 173 | fi 174 | fi 175 | echo "User: $user" 176 | python3 -m mytoninstaller -u ${user} -t ${telemetry} --dump ${dump} -m ${mode} --only-mtc ${only_mtc} --backup ${backup} --only-node ${only_node} 177 | 178 | # set migrate version 179 | migrate_version=1 180 | version_dir="/home/${user}/.local/share/mytonctrl" 181 | version_path="${version_dir}/VERSION" 182 | mkdir -p ${version_dir} 183 | echo ${migrate_version} > ${version_path} 184 | chown ${user}:${user} ${version_dir} ${version_path} 185 | 186 | # create symbolic link if branch not eq mytonctrl 187 | if [ "${repo}" != "mytonctrl" ]; then 188 | ln -sf ${SOURCES_DIR}/${repo} ${SOURCES_DIR}/mytonctrl 189 | fi 190 | 191 | echo -e "${COLOR}[5/5]${ENDC} Mytonctrl installation completed" 192 | exit 0 193 | -------------------------------------------------------------------------------- /scripts/ton_installer.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | set -e 4 | 5 | # Проверить sudo 6 | if [ "$(id -u)" != "0" ]; then 7 | echo "Please run script as root" 8 | exit 1 9 | fi 10 | 11 | while getopts ":c:v:h" flag; do 12 | case "${flag}" in 13 | c) config=${OPTARG};; 14 | v) ton_node_version=${OPTARG};; 15 | h) show_help_and_exit;; 16 | *) 17 | echo "Flag -${flag} is not recognized. Aborting" 18 | exit 1 ;; 19 | esac 20 | done 21 | 22 | echo "config: ${config}" 23 | echo "checkout to ${ton_node_version}" 24 | 25 | # Цвета 26 | COLOR='\033[95m' 27 | ENDC='\033[0m' 28 | 29 | # На OSX нет такой директории по-умолчанию, поэтому создаем... 30 | SOURCES_DIR=/usr/src 31 | BIN_DIR=/usr/bin 32 | if [[ "$OSTYPE" =~ darwin.* ]]; then 33 | SOURCES_DIR=/usr/local/src 34 | BIN_DIR=/usr/local/bin 35 | mkdir -p $SOURCES_DIR 36 | fi 37 | 38 | # Установка требуемых пакетов 39 | echo -e "${COLOR}[1/6]${ENDC} Installing required packages" 40 | if [ "$OSTYPE" == "linux-gnu" ]; then 41 | if [ hash yum 2>/dev/null ]; then 42 | echo "RHEL-based Linux detected." 43 | yum install -y epel-release 44 | dnf config-manager --set-enabled PowerTools 45 | 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 46 | elif [ -f /etc/SuSE-release ]; then 47 | echo "Suse Linux detected." 48 | echo "This OS is not supported with this script at present. Sorry." 49 | echo "Please refer to https://github.com/ton-blockchain/mytonctrl for setup information." 50 | exit 1 51 | elif [ -f /etc/arch-release ]; then 52 | echo "Arch Linux detected." 53 | pacman -Syuy 54 | pacman -S --noconfirm curl git make cmake clang gflags zlib openssl readline libmicrohttpd python python-pip 55 | elif [ -f /etc/debian_version ]; then 56 | echo "Ubuntu/Debian Linux detected." 57 | apt-get update 58 | apt-get install -y build-essential curl git cmake clang libgflags-dev zlib1g-dev libssl-dev libreadline-dev libmicrohttpd-dev pkg-config libgsl-dev python3 python3-dev python3-pip libsecp256k1-dev libsodium-dev liblz4-dev libjemalloc-dev automake libtool 59 | 60 | # Install ninja 61 | apt-get install -y ninja-build 62 | 63 | # Install for benchmark 64 | apt install -y fio rocksdb-tools 65 | else 66 | echo "Unknown Linux distribution." 67 | echo "This OS is not supported with this script at present. Sorry." 68 | echo "Please refer to https://github.com/ton-blockchain/mytonctrl for setup information." 69 | exit 1 70 | fi 71 | elif [[ "$OSTYPE" =~ darwin.* ]]; then 72 | echo "Mac OS (Darwin) detected." 73 | if [ ! which brew >/dev/null 2>&1 ]; then 74 | $BIN_DIR/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 75 | fi 76 | 77 | echo "Please, write down your username, because brew package manager cannot be run under root user:" 78 | read LOCAL_USERNAME 79 | 80 | su $LOCAL_USERNAME -c "brew update" 81 | su $LOCAL_USERNAME -c "brew install openssl cmake llvm" 82 | elif [ "$OSTYPE" == "freebsd"* ]; then 83 | echo "FreeBSD detected." 84 | echo "This OS is not supported with this script at present. Sorry." 85 | echo "Please refer to https://github.com/paritytech/substrate for setup information." # TODO: remove links 86 | exit 1 87 | else 88 | echo "Unknown operating system." 89 | echo "This OS is not supported with this script at present. Sorry." 90 | echo "Please refer to https://github.com/paritytech/substrate for setup information." # TODO: remove links 91 | exit 1 92 | fi 93 | 94 | # Установка компонентов python3 95 | pip3 install psutil==6.1.0 crc16==0.1.1 requests==2.32.3 96 | 97 | # build openssl 3.0 98 | echo -e "${COLOR}[2/6]${ENDC} Building OpenSSL 3.0" 99 | rm -rf $BIN_DIR/openssl_3 100 | git clone https://github.com/openssl/openssl $BIN_DIR/openssl_3 101 | cd $BIN_DIR/openssl_3 102 | opensslPath=`pwd` 103 | git checkout openssl-3.1.4 104 | ./config 105 | make build_libs -j$(nproc) 106 | 107 | # Клонирование репозиториев с github.com 108 | echo -e "${COLOR}[3/6]${ENDC} Preparing for compilation" 109 | cd $SOURCES_DIR 110 | rm -rf $SOURCES_DIR/ton 111 | git clone --recursive https://github.com/ton-blockchain/ton.git 112 | 113 | echo "checkout to ${ton_node_version}" 114 | 115 | if [ "${ton_node_version}" != "master" ]; then 116 | cd $SOURCES_DIR/ton 117 | git checkout ${ton_node_version} 118 | cd ../ 119 | fi 120 | 121 | cd $SOURCES_DIR/ton 122 | git submodule sync --recursive 123 | git submodule update 124 | cd ../ 125 | 126 | git config --global --add safe.directory $SOURCES_DIR/ton 127 | 128 | # Подготавливаем папки для компиляции 129 | rm -rf $BIN_DIR/ton 130 | mkdir $BIN_DIR/ton 131 | cd $BIN_DIR/ton 132 | 133 | # Подготовиться к компиляции 134 | if [[ "$OSTYPE" =~ darwin.* ]]; then 135 | export CMAKE_C_COMPILER=$(which clang) 136 | export CMAKE_CXX_COMPILER=$(which clang++) 137 | export CCACHE_DISABLE=1 138 | else 139 | export CC=$(which clang) 140 | export CXX=$(which clang++) 141 | export CCACHE_DISABLE=1 142 | fi 143 | 144 | # Подготовиться к компиляции 145 | if [[ "$OSTYPE" =~ darwin.* ]]; then 146 | if [[ $(uname -p) == 'arm' ]]; then 147 | echo M1 148 | CC="clang -mcpu=apple-a14" CXX="clang++ -mcpu=apple-a14" cmake $SOURCES_DIR/ton -DCMAKE_BUILD_TYPE=Release -DTON_ARCH= -Wno-dev 149 | else 150 | cmake -DCMAKE_BUILD_TYPE=Release $SOURCES_DIR/ton 151 | fi 152 | else 153 | 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 154 | fi 155 | 156 | # Расчитываем количество процессоров для сборки 157 | if [[ "$OSTYPE" =~ darwin.* ]]; then 158 | cpu_number=$(sysctl -n hw.logicalcpu) 159 | else 160 | memory=$(cat /proc/meminfo | grep MemAvailable | awk '{print $2}') 161 | cpu_number=$(($memory/2100000)) 162 | max_cpu_number=$(nproc) 163 | if [ ${cpu_number} -gt ${max_cpu_number} ]; then 164 | cpu_number=$((${max_cpu_number}-1)) 165 | fi 166 | if [ ${cpu_number} == 0 ]; then 167 | echo "Warning! insufficient RAM" 168 | cpu_number=1 169 | fi 170 | fi 171 | 172 | echo -e "${COLOR}[4/6]${ENDC} Source compilation, use ${cpu_number} cpus" 173 | ninja -j ${cpu_number} fift validator-engine lite-client validator-engine-console generate-random-id dht-server func tonlibjson rldp-http-proxy 174 | 175 | # Скачиваем конфигурационные файлы lite-client 176 | echo -e "${COLOR}[5/6]${ENDC} Downloading config files" 177 | wget ${config} -O global.config.json 178 | 179 | # Выход из программы 180 | echo -e "${COLOR}[6/6]${ENDC} TON software installation complete" 181 | exit 0 182 | -------------------------------------------------------------------------------- /scripts/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | full=true 3 | while getopts f flag; do 4 | case "${flag}" in 5 | f) full=false 6 | esac 7 | done 8 | 9 | # Проверить sudo 10 | if [ "$(id -u)" != "0" ]; then 11 | echo "Please run script as root" 12 | exit 1 13 | fi 14 | 15 | # Цвета 16 | COLOR='\033[34m' 17 | ENDC='\033[0m' 18 | 19 | # Остановка служб 20 | systemctl stop validator 21 | systemctl stop mytoncore 22 | systemctl stop dht-server 23 | 24 | # Переменные 25 | str=$(systemctl cat mytoncore | grep User | cut -d '=' -f2) 26 | user=$(echo ${str}) 27 | 28 | # Удаление служб 29 | rm -rf /etc/systemd/system/validator.service 30 | rm -rf /etc/systemd/system/mytoncore.service 31 | rm -rf /etc/systemd/system/dht-server.service 32 | systemctl daemon-reload 33 | 34 | # Удаление файлов 35 | if $full; then 36 | echo "removing Ton node" 37 | rm -rf /usr/src/ton 38 | rm -rf /usr/bin/ton 39 | rm -rf /var/ton-work 40 | rm -rf /var/ton-dht-server 41 | fi 42 | 43 | rm -rf /usr/src/mytonctrl 44 | rm -rf /usr/src/mtc-jsonrpc 45 | rm -rf /usr/src/pytonv3 46 | rm -rf /tmp/myton* 47 | rm -rf /usr/local/bin/mytoninstaller/ 48 | rm -rf /usr/local/bin/mytoncore/mytoncore.db 49 | rm -rf /home/${user}/.local/share/mytonctrl 50 | rm -rf /home/${user}/.local/share/mytoncore/mytoncore.db 51 | 52 | # Удаление ссылок 53 | if $full; then 54 | echo "removing ton node" 55 | rm -rf /usr/bin/fift 56 | rm -rf /usr/bin/liteclient 57 | rm -rf /usr/bin/validator-console 58 | fi 59 | rm -rf /usr/bin/mytonctrl 60 | 61 | # removing pip packages 62 | pip3 uninstall -y mytonctrl 63 | pip3 uninstall -y ton-http-api 64 | 65 | # Конец 66 | echo -e "${COLOR}Uninstall Complete${ENDC}" 67 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from os.path import dirname, join 3 | 4 | with open(join(dirname(__file__), "README.md"), "r") as f: 5 | long_description = f.read() 6 | with open(join(dirname(__file__), "requirements.txt")) as file: 7 | install_requires = file.read().split('\n') 8 | 9 | 10 | version = 'v0.1' 11 | 12 | setup( 13 | author='igroman787', 14 | author_email='igroman787', 15 | name='mytonctrl', 16 | version=version, 17 | packages=find_packages('.', exclude=['tests']), 18 | install_requires=install_requires, 19 | package_data={ 20 | 'mytoninstaller.scripts': ['*.sh'], 21 | 'mytoncore': [ 22 | 'contracts/**/*', 23 | 'complaints/*' 24 | ], 25 | 'mytonctrl': [ 26 | 'resources/*', 27 | 'scripts/*', 28 | 'migrations/*.sh' 29 | ], 30 | '': ['requirements.txt'], 31 | }, 32 | zip_safe=True, 33 | python_requires='>=3.7', 34 | classifiers=[ 35 | "Development Status :: 3 - Alpha", 36 | "Intended Audience :: Developers", 37 | "Programming Language :: Python :: 3.7", 38 | "Programming Language :: Python :: 3.8", 39 | "Programming Language :: Python :: 3.9", 40 | "Programming Language :: Python :: 3.10", 41 | "Programming Language :: Python :: 3.11", 42 | "License :: Other/Proprietary License", 43 | "Topic :: Software Development :: Libraries" 44 | ], 45 | url="https://github.com/ton-blockchain/mytonctrl", 46 | description="MyTonCtrl", 47 | long_description_content_type="text/markdown", 48 | long_description=long_description, 49 | ) 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/mytonctrl/8accb472c3f357d89da40115699fe8cd1fbb90eb/tests/__init__.py -------------------------------------------------------------------------------- /tests/blocksScanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*- 3 | 4 | from mypylib.mypylib import bcolors, Sleep, MyPyClass 5 | from mytoncore import MyTonCore, TonBlocksScanner 6 | 7 | def NewBlockReaction(block): 8 | print(f"{bcolors.green} block: {bcolors.endc} {block}") 9 | #end define 10 | 11 | def NewTransReaction(trans): 12 | print(f"{bcolors.magenta} trans: {bcolors.endc} {trans}") 13 | #end define 14 | 15 | def NewMessageReaction(message): 16 | print(f"{bcolors.yellow} message: {bcolors.endc} {message}") 17 | #end define 18 | 19 | 20 | local = MyPyClass('./tests') 21 | ton = MyTonCore(local) 22 | scanner = TonBlocksScanner(ton, nbr=NewBlockReaction, ntr=NewTransReaction, nmr=NewMessageReaction) 23 | scanner.Run() 24 | Sleep() 25 | -------------------------------------------------------------------------------- /tests/bounce.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*-l 3 | 4 | import time 5 | 6 | from mypylib.mypylib import MyPyClass 7 | from mytoncore import MyTonCore 8 | 9 | local = MyPyClass('./tests') 10 | ton = MyTonCore(local) 11 | 12 | 13 | def Init(): 14 | wallets = list() 15 | local.buffer["wallets"] = wallets 16 | walletsNameList = ton.GetWalletsNameList() 17 | 18 | # Create tests wallet 19 | testsWalletName = "tests_hwallet" 20 | testsWallet = ton.CreateHighWallet(testsWalletName) 21 | 22 | # Check tests wallet balance 23 | account = ton.GetAccount(testsWallet.addr) 24 | local.AddLog("wallet: {addr}, status: {status}, balance: {balance}".format(addr=testsWallet.addr, status=account.status, balance=account.balance)) 25 | if account.balance == 0: 26 | raise Exception(testsWallet.name + " wallet balance is empty.") 27 | if account.status == "uninit": 28 | ton.SendFile(testsWallet.bocFilePath, testsWallet) 29 | 30 | # Create wallets 31 | for i in range(load): 32 | walletName = testsWalletName 33 | if walletName not in walletsNameList: 34 | wallet = ton.CreateHighWallet(walletName, i) 35 | else: 36 | wallet = ton.GetLocalWallet(walletName, "hw", i) 37 | wallets.append(wallet) 38 | #end for 39 | 40 | # Fill up wallets 41 | buff_wallet = None 42 | buff_seqno = None 43 | destList = list() 44 | for wallet in wallets: 45 | wallet.account = ton.GetAccount(wallet.addr) 46 | need = 20 - wallet.account.balance 47 | if need > 10: 48 | destList.append([wallet.addr_init, need]) 49 | elif need < -10: 50 | need = need * -1 51 | buff_wallet = wallet 52 | buff_wallet.oldseqno = ton.GetSeqno(wallet) 53 | ton.MoveCoinsFromHW(wallet, [[testsWallet.addr, need]], wait=False) 54 | local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) 55 | if buff_wallet: 56 | ton.WaitTransaction(buff_wallet) 57 | #end for 58 | 59 | # Move grams from highload wallet 60 | ton.MoveCoinsFromHW(testsWallet, destList) 61 | 62 | # Activate wallets 63 | for wallet in wallets: 64 | if wallet.account.status == "uninit": 65 | wallet.oldseqno = ton.GetSeqno(wallet) 66 | ton.SendFile(wallet.bocFilePath) 67 | local.AddLog(str(wallet.subwallet) + " - OK") 68 | ton.WaitTransaction(wallets[-1]) 69 | #end define 70 | 71 | def Work(): 72 | wallets = local.buffer["wallets"] 73 | destList = list() 74 | destList.append(["EQAY_2_A88HD43S96hbVGbCLB21e6_k1nbaqICwS3ZCrMBaZ", 2]) 75 | for wallet in wallets: 76 | wallet.oldseqno = ton.GetSeqno(wallet) 77 | ton.MoveCoinsFromHW(wallet, destList, wait=False) 78 | local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") 79 | ton.WaitTransaction(wallets[-1]) 80 | #end define 81 | 82 | def General(): 83 | Init() 84 | while True: 85 | time.sleep(1) 86 | Work() 87 | local.AddLog("Work - OK") 88 | #end while 89 | #end define 90 | 91 | 92 | 93 | ### 94 | ### Start test 95 | ### 96 | local.Run() 97 | load = 200 98 | 99 | local.StartCycle(General, sec=1) 100 | while True: 101 | time.sleep(60) 102 | #load += 10 103 | #end while 104 | -------------------------------------------------------------------------------- /tests/mg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*- 3 | 4 | import sys 5 | import time 6 | 7 | from mypylib.mypylib import bcolors, Sleep 8 | from mytoncore import MyTonCore 9 | from mypylib.mypylib import MyPyClass 10 | 11 | local = MyPyClass(__file__) 12 | ton = MyTonCore(local) 13 | 14 | def TestMoveCoins(wallet, dest, coins, **kwargs): 15 | start = time.time() 16 | ton.MoveCoins(wallet, dest, coins, timeout=600, **kwargs) 17 | end = time.time() 18 | diff = int(end - start) 19 | local.AddLog(f"{wallet.addrB64} -> {dest}, diff: {diff}") 20 | #end define 21 | 22 | def Init(): 23 | vnum = local.buffer.get("vnum") 24 | wallet1 = ton.CreateWallet(f"test{vnum+1}", workchain=0, version="v1") 25 | wallet2 = ton.CreateWallet(f"test{vnum+2}", workchain=0, version="v2") 26 | wallet3 = ton.CreateWallet(f"test{vnum+3}", workchain=0, version="v3") 27 | local.buffer["vnum"] += 3 28 | ton.ActivateWallet(wallet1) 29 | ton.ActivateWallet(wallet2) 30 | ton.ActivateWallet(wallet3) 31 | return wallet1, wallet2, wallet3 32 | #end define 33 | 34 | def Test(wallet1, wallet2, wallet3): 35 | TestMoveCoins(wallet1, wallet1.addrB64, 1.01) 36 | TestMoveCoins(wallet2, wallet2.addrB64, 1.02) 37 | TestMoveCoins(wallet3, wallet3.addrB64, 1.03) 38 | 39 | TestMoveCoins(wallet1, wallet2.addrB64, 1.04) 40 | TestMoveCoins(wallet2, wallet3.addrB64, 1.05) 41 | TestMoveCoins(wallet3, wallet1.addrB64, 1.06) 42 | 43 | TestMoveCoins(wallet1, wallet1.addrB64, 1.07, flags=["--comment", "1.07"]) 44 | TestMoveCoins(wallet2, wallet2.addrB64, 1.08, flags=["--comment", "1.08"]) 45 | TestMoveCoins(wallet3, wallet3.addrB64, 1.09, flags=["--comment", "1.09"]) 46 | #end define 47 | 48 | 49 | 50 | 51 | local.Run() 52 | local.buffer["vnum"] = 0 53 | wallet1, wallet2, wallet3 = Init() 54 | wallet4, wallet5, wallet6 = Init() 55 | wallet7, wallet8, wallet9 = Init() 56 | local.StartCycle(Test, sec=1, name="Test1", args=(wallet1, wallet2, wallet3)) 57 | local.StartCycle(Test, sec=1, name="Test2", args=(wallet4, wallet5, wallet6)) 58 | local.StartCycle(Test, sec=1, name="Test3", args=(wallet7, wallet8, wallet9)) 59 | Sleep() 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /tests/tpsLoad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*-l 3 | 4 | import time 5 | 6 | from mypylib.mypylib import MyPyClass 7 | from mytoncore import MyTonCore, Sleep 8 | 9 | 10 | local = MyPyClass('./tests') 11 | local.db["config"]["logLevel"] = "info" 12 | load = 100 13 | ton = MyTonCore(local) 14 | 15 | 16 | def Init(): 17 | wallets = list() 18 | local.buffer["wallets"] = wallets 19 | walletsNameList = ton.GetWalletsNameList() 20 | 21 | # Create tests wallet 22 | testsWalletName = "tests_hwallet" 23 | testsWallet = ton.CreateHighWallet(testsWalletName) 24 | 25 | # Check tests wallet balance 26 | account = ton.GetAccount(testsWallet.addr) 27 | local.AddLog("wallet: {addr}, status: {status}, balance: {balance}".format(addr=testsWallet.addr, status=account.status, balance=account.balance)) 28 | if account.balance == 0: 29 | raise Exception(testsWallet.name + " wallet balance is empty.") 30 | if account.status == "uninit": 31 | ton.SendFile(testsWallet.bocFilePath, testsWallet) 32 | 33 | # Create wallets 34 | for i in range(load): 35 | walletName = "w_" + str(i) 36 | if walletName not in walletsNameList: 37 | wallet = ton.CreateWallet(walletName) 38 | else: 39 | wallet = ton.GetLocalWallet(walletName) 40 | wallets.append(wallet) 41 | #end for 42 | 43 | # Fill up wallets 44 | buff_wallet = None 45 | buff_seqno = None 46 | destList = list() 47 | for wallet in wallets: 48 | wallet.account = ton.GetAccount(wallet.addr) 49 | need = 20 - wallet.account.balance 50 | if need > 10: 51 | destList.append([wallet.addr_init, need]) 52 | elif need < -10: 53 | need = need * -1 54 | buff_wallet = wallet 55 | buff_wallet.oldseqno = ton.GetSeqno(wallet) 56 | ton.MoveGrams(wallet, testsWallet.addr, need, wait=False) 57 | local.AddLog(testsWallet.name + " <<< " + wallet.name) 58 | if buff_wallet: 59 | ton.WaitTransaction(buff_wallet, False) 60 | #end for 61 | 62 | # Move grams from highload wallet 63 | ton.MoveGramsFromHW(testsWallet, destList) 64 | 65 | # Activate wallets 66 | for wallet in wallets: 67 | if wallet.account.status == "uninit": 68 | wallet.oldseqno = ton.GetSeqno(wallet) 69 | ton.SendFile(wallet.bocFilePath) 70 | local.AddLog(str(wallet.subwallet) + " - OK") 71 | ton.WaitTransaction(wallets[-1]) 72 | #end define 73 | 74 | def Work(): 75 | wallets = local.buffer["wallets"] 76 | for i in range(load): 77 | if i + 1 == load: 78 | i = -1 79 | #end if 80 | 81 | wallet1 = wallets[i] 82 | wallet2 = wallets[i+1] 83 | wallet1.oldseqno = ton.GetSeqno(wallet1) 84 | ton.MoveGrams(wallet1, wallet2.addr, 3.14, wait=False) 85 | local.AddLog(wallet1.name + " >>> " + wallet2.name) 86 | ton.WaitTransaction(wallets[-1]) 87 | #end define 88 | 89 | def General(): 90 | Init() 91 | while True: 92 | time.sleep(1) 93 | Work() 94 | local.AddLog("Work - OK") 95 | #end while 96 | #end define 97 | 98 | 99 | 100 | ### 101 | ### Start test 102 | ### 103 | 104 | local.Run() 105 | load = 100 106 | local.StartCycle(General, sec=1) 107 | Sleep() 108 | -------------------------------------------------------------------------------- /tests/tpsLoad2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf_8 -*-l 3 | 4 | import time 5 | 6 | from mypylib.mypylib import MyPyClass 7 | from mytoncore import MyTonCore 8 | 9 | local = MyPyClass('./tests') 10 | local.db["config"]["logLevel"] = "info" 11 | load = 10 12 | ton = MyTonCore(local) 13 | 14 | 15 | def Init(): 16 | wallets = list() 17 | local.buffer["wallets"] = wallets 18 | walletsNameList = ton.GetWalletsNameList() 19 | 20 | # Create tests wallet 21 | testsWalletName = "tests_hwallet" 22 | testsWallet = ton.CreateHighWallet(testsWalletName) 23 | 24 | # Check tests wallet balance 25 | account = ton.GetAccount(testsWallet.addr) 26 | local.AddLog("wallet: {addr}, status: {status}, balance: {balance}".format(addr=testsWallet.addr, status=account.status, balance=account.balance)) 27 | if account.balance == 0: 28 | raise Exception(testsWallet.name + " wallet balance is empty.") 29 | if account.status == "uninit": 30 | ton.SendFile(testsWallet.bocFilePath, testsWallet) 31 | 32 | # Create wallets 33 | for i in range(load): 34 | walletName = testsWalletName 35 | if walletName not in walletsNameList: 36 | wallet = ton.CreateHighWallet(walletName, i) 37 | else: 38 | wallet = ton.GetLocalWallet(walletName, "hw", i) 39 | wallets.append(wallet) 40 | #end for 41 | 42 | # Fill up wallets 43 | buff_wallet = None 44 | buff_seqno = None 45 | destList = list() 46 | for wallet in wallets: 47 | wallet.account = ton.GetAccount(wallet.addr) 48 | need = 20 - wallet.account.balance 49 | if need > 10: 50 | destList.append([wallet.addr_init, need]) 51 | elif need < -10: 52 | need = need * -1 53 | buff_wallet = wallet 54 | buff_wallet.oldseqno = ton.GetSeqno(wallet) 55 | ton.MoveGramsFromHW(wallet, [[testsWallet.addr, need]], wait=False) 56 | local.AddLog(testsWallet.name + " <<< " + str(wallet.subwallet)) 57 | if buff_wallet: 58 | ton.WaitTransaction(buff_wallet) 59 | #end for 60 | 61 | # Move grams from highload wallet 62 | ton.MoveGramsFromHW(testsWallet, destList) 63 | 64 | # Activate wallets 65 | for wallet in wallets: 66 | if wallet.account.status == "uninit": 67 | wallet.oldseqno = ton.GetSeqno(wallet) 68 | ton.SendFile(wallet.bocFilePath) 69 | local.AddLog(str(wallet.subwallet) + " - OK") 70 | ton.WaitTransaction(wallets[-1]) 71 | #end define 72 | 73 | def Work(): 74 | wallets = Local.buffer["wallets"] 75 | destList = list() 76 | for i in range(load): 77 | destList.append([wallets[i].addr, 0.1]) 78 | for wallet in wallets: 79 | wallet.oldseqno = ton.GetSeqno(wallet) 80 | ton.MoveGramsFromHW(wallet, destList, wait=False) 81 | local.AddLog(str(wallet.subwallet) + " " + wallet.addr + " >>> ") 82 | ton.WaitTransaction(wallets[-1]) 83 | #end define 84 | 85 | def General(): 86 | Init() 87 | while True: 88 | time.sleep(1) 89 | Work() 90 | local.AddLog("Work - OK") 91 | #end while 92 | #end define 93 | 94 | 95 | 96 | ### 97 | ### Start test 98 | ### 99 | local.Run() 100 | local.StartCycle(General, sec=1) 101 | while True: 102 | time.sleep(60) 103 | hour_str = time.strftime("%H") 104 | hour = int(hour_str) 105 | load = hour * 4 106 | #end while 107 | -------------------------------------------------------------------------------- /tools/extract_backup_node_keys.sh: -------------------------------------------------------------------------------- 1 | name="backup.tar.gz" 2 | dest="cleared_backup_$(hostname)_$(date +%s).tar.gz" 3 | ton_db="" 4 | tmp_dir="tmp/backup" 5 | user=$(logname) 6 | 7 | 8 | # Get arguments 9 | while getopts n:d:t: flag 10 | do 11 | case "${flag}" in 12 | n) name=${OPTARG};; 13 | d) dest=${OPTARG};; 14 | t) ton_db=${OPTARG};; 15 | *) 16 | echo "Flag -${flag} is not recognized. Aborting" 17 | exit 1 ;; 18 | esac 19 | done 20 | 21 | rm -rf $tmp_dir 22 | mkdir tmp/backup -p 23 | 24 | if [ ! -z "$ton_db" ]; then 25 | mkdir ${tmp_dir}/db 26 | cp -r "$ton_db"/db/keyring ${tmp_dir}/db 27 | cp "$ton_db"/db/config.json ${tmp_dir}/db 28 | else 29 | tar -xzf $name -C $tmp_dir 30 | fi 31 | 32 | rm -rf ${tmp_dir}/mytoncore 33 | rm -rf ${tmp_dir}/keys 34 | mv ${tmp_dir}/db/keyring ${tmp_dir}/db/old_keyring 35 | mkdir ${tmp_dir}/db/keyring 36 | 37 | keys=$(python3 -c "import json;import base64;f=open('${tmp_dir}/db/config.json');config=json.load(f);f.close();keys=set();[([keys.add(base64.b64decode(key['key']).hex().upper()) for key in v['temp_keys']], [keys.add(base64.b64decode(adnl['id']).hex().upper()) for adnl in v['adnl_addrs']]) for v in config['validators']];print('\n'.join(list(keys)))") 38 | 39 | for key in $keys; do 40 | mv ${tmp_dir}/db/old_keyring/${key} ${tmp_dir}/db/keyring 41 | done 42 | 43 | rm -rf ${tmp_dir}/db/old_keyring 44 | 45 | tar -zcf $dest -C $tmp_dir . 46 | chown $user:$user $dest 47 | 48 | echo -e "Node keys backup successfully created in ${dest}!" 49 | -------------------------------------------------------------------------------- /tools/inject_backup_node_keys.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import json 4 | import subprocess 5 | tmp_dir = "tmp/cleared_backup" 6 | 7 | 8 | def b64tohex(b64str: str): 9 | return base64.b64decode(b64str).hex().upper() 10 | 11 | 12 | def run_vc(cmd: str): 13 | args = ['/usr/bin/ton/validator-engine-console/validator-engine-console', '-k', '/var/ton-work/keys/client', '-p', '/var/ton-work/keys/server.pub', '-a', vc_address, '--cmd', cmd] 14 | subprocess.run(args) 15 | 16 | 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('-n') 19 | parser.add_argument('-a') 20 | 21 | args = parser.parse_args() 22 | name = args.n 23 | vc_address = args.a 24 | 25 | if not name or not vc_address: 26 | print("Usage: inject_backup_node_keys.py -n -a ") 27 | exit(1) 28 | 29 | 30 | subprocess.run(f"rm -rf {tmp_dir}", shell=True) 31 | subprocess.run(f"mkdir -p {tmp_dir}", shell=True) 32 | 33 | subprocess.run(f'tar -xzf {name} -C {tmp_dir}', shell=True) 34 | 35 | subprocess.run(f'cp -rf {tmp_dir}/db/keyring /var/ton-work/db/', shell=True) 36 | subprocess.run(f'chown -R validator:validator /var/ton-work/db/keyring', shell=True) 37 | 38 | with open(f'{tmp_dir}/db/config.json', 'r') as f: 39 | config = json.load(f) 40 | 41 | for v in config['validators']: 42 | run_vc(f'addpermkey {b64tohex(v["id"])} {v["election_date"]} {v["expire_at"]}') 43 | for tkey in v['temp_keys']: 44 | run_vc(f'addtempkey {b64tohex(v["id"])} {b64tohex(tkey["key"])} {v["expire_at"]}') 45 | for adnl in v['adnl_addrs']: 46 | run_vc(f'addadnl {b64tohex(adnl["id"])} 0') 47 | run_vc(f'addvalidatoraddr {b64tohex(v["id"])} {b64tohex(adnl["id"])} {v["expire_at"]}') 48 | 49 | subprocess.run(f'systemctl restart validator', shell=True) 50 | --------------------------------------------------------------------------------