diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..93814ad02 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 100 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv,node_modules \ No newline at end of file diff --git a/VERSION b/VERSION index 79a2734bb..09a3acfa1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 \ No newline at end of file +0.6.0 \ No newline at end of file diff --git a/app.py b/app.py index 0eac8f037..1a29e777f 100644 --- a/app.py +++ b/app.py @@ -17,30 +17,27 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import os import logging from flask import Flask, render_template, session, g from peewee import SqliteDatabase -import sentry_sdk - from skale import Skale from skale.wallets import RPCWallet from core.node import Node -from core.local_wallet import LocalWallet +from core.node_config import NodeConfig +from core.schains.monitor import SchainsMonitor +from core.schains.cleaner import SChainsCleaner -from tools.helper import get_sentry_env_name -from tools.configs import NODE_CONFIG_FILEPATH, FLASK_SECRET_KEY_FILE -from tools.configs.web3 import ENDPOINT, ABI_FILEPATH +from tools.configs import FLASK_SECRET_KEY_FILE +from tools.configs.web3 import ENDPOINT, ABI_FILEPATH, TM_URL from tools.configs.db import DB_FILE from tools.logger import init_admin_logger -from tools.config_storage import ConfigStorage -from tools.token_utils import TokenUtils from tools.docker_utils import DockerUtils from tools.str_formatters import arguments_list_string -from tools.sgx_utils import generate_sgx_key +from tools.sgx_utils import generate_sgx_key, sgx_server_text +from tools.token_utils import init_user_token from tools.configs.flask import FLASK_APP_HOST, FLASK_APP_PORT, FLASK_DEBUG_MODE from web.user import User @@ -61,39 +58,31 @@ werkzeug_logger = logging.getLogger('werkzeug') # todo: remove werkzeug_logger.setLevel(logging.WARNING) # todo: remove -rpc_wallet = RPCWallet(os.environ['TM_URL']) +rpc_wallet = RPCWallet(TM_URL) skale = Skale(ENDPOINT, ABI_FILEPATH, rpc_wallet) -wallet = LocalWallet(skale, rpc_wallet) -config = ConfigStorage(NODE_CONFIG_FILEPATH) + docker_utils = DockerUtils() -node = Node(skale, config, wallet) -token_utils = TokenUtils() user_session = UserSession(session) -SENTRY_URL = os.environ.get('SENTRY_URL', None) -if SENTRY_URL: - sentry_env_name = get_sentry_env_name(skale.manager.address) - sentry_sdk.init(SENTRY_URL, environment=sentry_env_name) - - -if not token_utils.get_token(): - token_utils.add_token() -token = token_utils.get_token() +node_config = NodeConfig() +node = Node(skale, node_config) +schains_monitor = SchainsMonitor(skale, node_config) +schains_cleaner = SChainsCleaner(skale, node_config) +token = init_user_token() database = SqliteDatabase(DB_FILE) app = Flask(__name__) app.register_blueprint(construct_auth_bp(user_session, token)) app.register_blueprint(web_logs) app.register_blueprint(construct_nodes_bp(skale, node, docker_utils)) -app.register_blueprint(construct_schains_bp(skale, wallet, docker_utils, node)) -app.register_blueprint(construct_wallet_bp(wallet)) -app.register_blueprint(construct_node_info_bp(skale, wallet, docker_utils)) +app.register_blueprint(construct_schains_bp(skale, node_config, docker_utils)) +app.register_blueprint(construct_wallet_bp(skale)) +app.register_blueprint(construct_node_info_bp(skale, docker_utils)) app.register_blueprint(construct_security_bp()) -app.register_blueprint(construct_validators_bp(skale, config, wallet)) -app.register_blueprint(construct_metrics_bp(skale, config)) +app.register_blueprint(construct_validators_bp(skale, node_config)) +app.register_blueprint(construct_metrics_bp(skale, node_config)) -wallet.get_or_generate() @app.before_request def before_request(): @@ -113,9 +102,13 @@ def main(): if __name__ == '__main__': - logger.info(arguments_list_string({'Root account token': token}, 'Starting Flask server')) + logger.info(arguments_list_string({ + 'Endpoint': ENDPOINT, + 'Transaction manager': TM_URL, + 'SGX Server': sgx_server_text() + }, 'Starting Flask server')) if not User.table_exists(): User.create_table() - generate_sgx_key(config) + generate_sgx_key(node_config) app.secret_key = FLASK_SECRET_KEY_FILE app.run(debug=FLASK_DEBUG_MODE, port=FLASK_APP_PORT, host=FLASK_APP_HOST, use_reloader=False) diff --git a/core/local_wallet.py b/core/local_wallet.py deleted file mode 100644 index f170ae383..000000000 --- a/core/local_wallet.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import logging -from tools.configs import LOCAL_WALLET_FILEPATH -from tools.config_storage import ConfigStorage - -logger = logging.getLogger(__name__) - -# todo: move to smart contracts -DEPOSIT_AMOUNT_SKL = 100 -DEPOSIT_AMOUNT_ETH = 0.2 - -DEPOSIT_AMOUNT_SKL_WEI = DEPOSIT_AMOUNT_SKL * (10 ** 18) -DEPOSIT_AMOUNT_ETH_WEI = int(DEPOSIT_AMOUNT_ETH * (10 ** 18)) - -class LocalWallet: # todo: refactor - def __init__(self, skale, wallet): - self.skale = skale - self.wallet = wallet - #self.skale.web3.eth.enable_unaudited_features() # todo: deal with this - - def generate_local_wallet(self, password=None, extra_entropy=''): - return {'address': self.wallet.address} - - def get_or_generate(self): - return self.generate_local_wallet() - - def get_full(self): - local_wallet_config = ConfigStorage(LOCAL_WALLET_FILEPATH) - return local_wallet_config.get() - - def get(self): - return self.generate_local_wallet() - - def get_with_balance(self): - wallet = self.get() - if not wallet.get('address', None): - return wallet - - eth_balance_wei = self.skale.web3.eth.getBalance(wallet['address']) - skale_balance_wei = self.skale.token.get_balance(wallet['address']) - - logging.debug(f'get_with_balance raw info: address: {wallet["address"]}, eth_balance_wei: {eth_balance_wei}, skale_balance_wei: {skale_balance_wei}') - - wallet['eth_balance_wei'] = str(eth_balance_wei) - wallet['skale_balance_wei'] = str(skale_balance_wei) - wallet['eth_balance'] = str(self.skale.web3.fromWei(eth_balance_wei, 'ether')) - wallet['skale_balance'] = str(self.skale.web3.fromWei(skale_balance_wei, 'ether')) - - return wallet - - def check_required_balance(self): - wallet = self.get_with_balance() - return int(wallet['eth_balance_wei']) >= DEPOSIT_AMOUNT_ETH_WEI and int(wallet[ - 'skale_balance_wei']) >= DEPOSIT_AMOUNT_SKL_WEI - - def get_required_balance(self): - return {'eth_balance': DEPOSIT_AMOUNT_ETH, 'skale_balance': DEPOSIT_AMOUNT_SKL} diff --git a/core/node.py b/core/node.py new file mode 100644 index 000000000..2ffb35308 --- /dev/null +++ b/core/node.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Admin +# +# Copyright (C) 2019 SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from enum import Enum +import docker + +from core.filebeat import run_filebeat_service + +from tools.str_formatters import arguments_list_string +from tools.wallet_utils import check_required_balance + +from skale.utils.web3_utils import wait_receipt, check_receipt +from skale.utils.helper import ip_from_bytes +from skale.wallets.web3_wallet import public_key_to_address + +docker_client = docker.from_env() +logger = logging.getLogger(__name__) + + +class NodeStatuses(Enum): + """This class contains possible node statuses""" + NOT_CREATED = 0 + REQUESTED = 1 + CREATED = 2 + ERROR = 3 + + +class Node: + """This class contains node registration logic""" + def __init__(self, skale, config): + self.skale = skale + self.config = config + + def register(self, ip, public_ip, port, name): + """ + Main node registration function. + + Parameters: + ip (str): P2P IP address that will be assigned to the node + public_ip (str): Public IP address that will be assigned to the node + port (int): Base port that will be used for sChains on the node + name (str): Node name + + Returns: + dict: Execution status and node config + """ + self._log_node_info('Node create started', ip, public_ip, port, name) + if self.config.id is not None: + return self._node_already_exist() + if not check_required_balance(self.skale): + return self._insufficient_funds() + res = self.skale.manager.create_node(ip, int(port), name, public_ip) + receipt = wait_receipt(self.skale.web3, res['tx']) + try: + check_receipt(receipt) + except ValueError as err: + logger.error(arguments_list_string({'tx': res['tx']}, 'Node creation failed', 'error')) + return {'status': 0, 'errors': [err]} + self._log_node_info('Node successfully created', ip, public_ip, port, name) + res = self.skale.nodes_data.node_name_to_index(name) + self.config.id = self.skale.nodes_data.node_name_to_index(name) + # run_filebeat_service(public_ip, self.config.id, self.skale) # todo: uncomment! + return {'status': 1, 'data': self.config.all()} + + def _insufficient_funds(self): + err_msg = f'Insufficient funds, re-check your wallet' + logger.error(err_msg) + return {'status': 0, 'errors': [err_msg]} + + def _node_already_exist(self): + err_msg = f'Node is already installed on this machine. Node ID: {self.config.id}' + logger.error(err_msg) + return {'status': 0, 'errors': [err_msg]} + + def _log_node_info(self, title, ip, public_ip, port, name): + log_params = {'IP': ip, 'Public IP': public_ip, 'Port': port, 'Name': name} + logger.info(arguments_list_string(log_params, title)) + + @property + def info(self): + _id = self.config.id + if _id is not None: + raw_info = self.skale.nodes_data.get(_id) + return self._transform_node_info(raw_info, _id) + return {'status': NodeStatuses.NOT_CREATED.value} + + def _transform_node_info(self, node_info, node_id): + node_info['ip'] = ip_from_bytes(node_info['ip']) + node_info['publicIP'] = ip_from_bytes(node_info['publicIP']) + node_info['status'] = NodeStatuses.CREATED.value + node_info['id'] = node_id + node_info['publicKey'] = self.skale.web3.toHex(node_info['publicKey']) + node_info['owner'] = public_key_to_address(node_info['publicKey']) + return node_info diff --git a/core/node/__init__.py b/core/node/__init__.py deleted file mode 100644 index f30227df3..000000000 --- a/core/node/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from core.node.main import Node \ No newline at end of file diff --git a/core/node/main.py b/core/node/main.py deleted file mode 100644 index 6469fba0b..000000000 --- a/core/node/main.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import docker, logging - -from core.schains.monitor import SchainsMonitor -from core.schains.cleaner import SChainsCleaner -from core.node.statuses import NodeStatuses -from core.filebeat import run_filebeat_service - -from tools.str_formatters import arguments_list_string - -import skale.utils.helper as Helper - -docker_client = docker.from_env() -logger = logging.getLogger(__name__) - - -class Node: - def __init__(self, skale, config, wallet): - self.skale = skale - self.wallet = wallet - self.config = config - - node_id = self.get_node_id() - if node_id is not None: - self.run_schains_monitor(node_id) - - self.install_nonce = None - self.install_address = None - - def run_schains_monitor(self, node_id): - self.schains_monitor = SchainsMonitor(self.skale, self.wallet, node_id) - self.schains_cleaner = SChainsCleaner(self.skale, node_id) - - def create(self, ip, public_ip, port, name): - log_params = {'IP': ip, 'Public IP': public_ip, 'Port': port, 'Name': name} - logger.info(arguments_list_string(log_params, 'Node create started')) - - node_id = self.get_node_id() - if node_id: - err_msg = f'Node is already installed on this machine. Node ID: {node_id}' - logger.error(err_msg) - return {'status': 0, 'errors': [err_msg]} - - if not self.wallet.check_required_balance(): - wallet = self.wallet.get_with_balance() - required_balances = self.wallet.get_required_balance() - err_msg = f'Balance is too low to create a node: {wallet["eth_balance"]} ETH, {wallet["skale_balance"]} SKALE' - required_balance_msg = f'Required amount: {required_balances["eth_balance"]} ETH, {required_balances["skale_balance"]} SKALE' - logger.error(err_msg) - logger.error(required_balance_msg) - return {'status': 0, 'errors': [err_msg, required_balance_msg]} - - local_wallet = self.wallet.get() - res = self.skale.manager.create_node(ip, int(port), name, public_ip) - logger.info(f'create_node res: {res}') - - self.install_nonce = res['nonce'] - self.install_address = local_wallet['address'] - receipt = Helper.await_receipt(self.skale.web3, res[ - 'tx']) # todo: return tx and wait for the receipt in async mode - - - if receipt['status'] != 1: - err_msg = f'Transaction failed. TX: {res["tx"]}' # todo: convert to hex - logger.error(arguments_list_string({'tx': res["tx"]}, 'Node creation failed', 'error')) - logger.error(f'create_node receipt: {receipt}') - return {'status': 0, 'errors': [err_msg]} - - logger.info(arguments_list_string(log_params, 'Node successfully created', 'success')) - - node_id = self.skale.nodes_data.node_name_to_index(name) - self.config.update({'node_id': node_id}) - self.run_schains_monitor(node_id) - run_filebeat_service(public_ip, node_id, self.skale) - return {'status': 1, 'data': self.config.get()} - - def get_node_id(self): - try: - return self.config['node_id'] - except KeyError: - logger.debug('get_node_id: No node installed on this machine.') - return None - - def get_node_info(self): - node_id = self.get_node_id() - if node_id is not None: - node_info = self.skale.nodes_data.get(node_id) - if not node_info: return {'status': NodeStatuses.ERROR.value} - return self.transform_node_info(node_info, node_id) - else: - return {'status': NodeStatuses.NOT_CREATED.value} - - def transform_node_info(self, node_info, node_id): - node_info['ip'] = Helper.ip_from_bytes(node_info['ip']) - node_info['publicIP'] = Helper.ip_from_bytes(node_info['publicIP']) - node_info['status'] = NodeStatuses.CREATED.value - node_info['id'] = node_id - node_info['publicKey'] = self.skale.web3.toHex(node_info['publicKey']) - node_info['owner'] = Helper.public_key_to_address(node_info['publicKey']) - return node_info \ No newline at end of file diff --git a/core/node/statuses.py b/core/node/statuses.py deleted file mode 100644 index 179685396..000000000 --- a/core/node/statuses.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from enum import Enum - - -class NodeStatuses(Enum): - NOT_CREATED = 0 - REQUESTED = 1 - CREATED = 2 - ERROR = 3 - - # def __str__(self): - # return str(self.value) diff --git a/core/node/utils.py b/core/node/utils.py deleted file mode 100644 index 10a9e1be8..000000000 --- a/core/node/utils.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import logging - -logger = logging.getLogger(__name__) - - -def get_node_id(config): - try: - return config['node_id'] - except KeyError: - logger.info('get_node_id: No node installed on this machine.') - return None diff --git a/core/node_config.py b/core/node_config.py new file mode 100644 index 000000000..0a0fccb0c --- /dev/null +++ b/core/node_config.py @@ -0,0 +1,57 @@ +import functools +from filelock import FileLock + +from tools.helper import read_json, write_json, init_file +from tools.configs import NODE_CONFIG_FILEPATH + + +LOCK_PATH = '/tmp/skale_node_config.lock' + + +def config_setter(func): + @functools.wraps(func) + def wrapper_decorator(*args, **kwargs): + field_name, field_value = func(*args, **kwargs) + lock = FileLock(LOCK_PATH) + with lock: + config = read_json(NODE_CONFIG_FILEPATH) + config[field_name] = field_value + write_json(NODE_CONFIG_FILEPATH, config) + return wrapper_decorator + + +def config_getter(func): + @functools.wraps(func) + def wrapper_decorator(*args, **kwargs): + field_name = func(*args, **kwargs) + config = read_json(NODE_CONFIG_FILEPATH) + return config.get(field_name) + return wrapper_decorator + + +class NodeConfig(): + def __init__(self): + init_file(NODE_CONFIG_FILEPATH, {}) + + @property + @config_getter + def id(self) -> int: + return 'node_id' + + @id.setter + @config_setter + def id(self, node_id: int) -> None: + return 'node_id', node_id + + @property + @config_getter + def sgx_key_name(self) -> int: + return 'sgx_key_name' + + @sgx_key_name.setter + @config_setter + def sgx_key_name(self, sgx_key_name: int) -> None: + return 'sgx_key_name', sgx_key_name + + def all(self) -> dict: + return read_json(NODE_CONFIG_FILEPATH) diff --git a/core/schains/__init__.py b/core/schains/__init__.py index 6cd8d0404..97a4ec847 100644 --- a/core/schains/__init__.py +++ b/core/schains/__init__.py @@ -1,3 +1,21 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Admin +# +# Copyright (C) 2019 SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . MONITOR_INTERVAL = 10 -CLEANER_INTERVAL = 60 \ No newline at end of file +CLEANER_INTERVAL = 60 diff --git a/core/schains/checks.py b/core/schains/checks.py index 3a5f1919a..606e895ac 100644 --- a/core/schains/checks.py +++ b/core/schains/checks.py @@ -103,5 +103,8 @@ def log_health_check(self): failed_checks_str = ", ".join(failed_checks) logger.info( - arguments_list_string({'sChain name': self.name, 'Failed checks': failed_checks_str}, - 'Failed sChain checks', 'error')) + arguments_list_string( + {'sChain name': self.name, 'Failed checks': failed_checks_str}, + 'Failed sChain checks', 'error' + ) + ) diff --git a/core/schains/cleaner.py b/core/schains/cleaner.py index 764b8fd10..d3756abc5 100644 --- a/core/schains/cleaner.py +++ b/core/schains/cleaner.py @@ -20,27 +20,41 @@ import os import logging import shutil +from time import sleep + +from skale import Skale from core.schains.checks import SChainChecks from core.schains.helper import get_schain_dir_path +from core.schains.runner import get_container_name + from tools.docker_utils import DockerUtils -from tools.configs.schains import SCHAINS_DIR_PATH from tools.custom_thread import CustomThread from tools.str_formatters import arguments_list_string -from . import CLEANER_INTERVAL - -from core.schains.runner import get_container_name +from tools.configs.web3 import ENDPOINT, ABI_FILEPATH +from tools.configs.schains import SCHAINS_DIR_PATH from tools.configs.containers import SCHAIN_CONTAINER, IMA_CONTAINER +from . import CLEANER_INTERVAL, MONITOR_INTERVAL + + logger = logging.getLogger(__name__) dutils = DockerUtils() class SChainsCleaner(): - def __init__(self, skale, node_id): + def __init__(self, skale, node_config): self.skale = skale - self.node_id = node_id + self.node_config = node_config + self.skale_events = Skale(ENDPOINT, ABI_FILEPATH) + CustomThread('Wait for node ID', self.wait_for_node_id, once=True).start() + + def wait_for_node_id(self, opts): + while self.node_config.id is None: + logger.debug('Waiting for the node_id in sChains Cleaner...') + sleep(MONITOR_INTERVAL) + self.node_id = self.node_config.id self.monitor = CustomThread('sChains cleaner monitor', self.schains_cleaner, interval=CLEANER_INTERVAL) self.monitor.start() @@ -49,7 +63,7 @@ def schains_cleaner(self, opts): schains_on_node = self.get_schains_on_node() schain_ids = self.schain_names_to_ids(schains_on_node) - event_filter = self.skale.schains.contract.events.SchainDeleted.createFilter( + event_filter = self.skale_events.schains.contract.events.SchainDeleted.createFilter( fromBlock=0, argument_filters={'schainId': schain_ids}) events = event_filter.get_all_entries() diff --git a/core/schains/config.py b/core/schains/config.py index 2eae23909..8a03967b1 100644 --- a/core/schains/config.py +++ b/core/schains/config.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) -def generate_schain_config(schain_name, node_id, skale): +def generate_schain_config(schain_name, schain_owner, node_id, skale): node_info = skale.schains_data.get_current_node_for_schain_config(schain_name, node_id) schain_struct = skale.schains_data.get_by_name(schain_name) @@ -43,6 +43,7 @@ def generate_schain_config(schain_name, node_id, skale): "sChain": { "schainID": 1, # todo! "schainName": schain_name, + "schainOwner": schain_owner, "nodes": schain_nodes } } @@ -77,6 +78,7 @@ def generate_allocation(schain, schain_nodes): schain['name'])) return allocation + def add_chain_id(base_config, schain_name): keccak_hash = keccak.new(digest_bits=256) keccak_hash.update(schain_name.encode("utf-8")) @@ -84,6 +86,7 @@ def add_chain_id(base_config, schain_name): hash = hash[:13] # use 52 bits base_config['params']['chainID'] = "0x" + hash + def save_schain_config(schain_config, schain_name): schain_config_filepath = get_schain_config_filepath(schain_name) with open(schain_config_filepath, 'w') as outfile: diff --git a/core/schains/monitor.py b/core/schains/monitor.py index 409e070d2..d0163de6b 100644 --- a/core/schains/monitor.py +++ b/core/schains/monitor.py @@ -19,16 +19,16 @@ import os import logging +from time import sleep + from tools.custom_thread import CustomThread from tools.docker_utils import DockerUtils from tools.str_formatters import arguments_list_string -from sentry_sdk import capture_message - from core.schains.runner import run_schain_container, run_ima_container from core.schains.helper import init_schain_dir, get_schain_config_filepath from core.schains.config import generate_schain_config, save_schain_config, get_schain_env -from core.schains.volume import init_data_volume, get_container_limits +from core.schains.volume import init_data_volume from core.schains.checks import SChainChecks from core.schains.ima import get_ima_env from core.schains.dkg import init_bls, FailedDKG @@ -43,24 +43,31 @@ class SchainsMonitor(): - def __init__(self, skale, wallet, node_id): + def __init__(self, skale, node_config): self.skale = skale - self.node_id = node_id - self.wallet = wallet + self.node_config = node_config + CustomThread('Wait for node ID', self.wait_for_node_id, once=True).start() + + def wait_for_node_id(self, opts): + while self.node_config.id is None: + logger.debug('Waiting for the node_id in sChains Monitor...') + sleep(MONITOR_INTERVAL) + self.node_id = self.node_config.id self.monitor = CustomThread('sChains monitor', self.monitor_schains, interval=MONITOR_INTERVAL) self.monitor.start() def monitor_schains(self, opts): schains = self.skale.schains_data.get_schains_for_node(self.node_id) - schains_on_node = sum(map(lambda schain: schain['active'] == True, schains)) + schains_on_node = sum(map(lambda schain: schain['active'], schains)) schains_holes = len(schains) - schains_on_node logger.info( arguments_list_string({'Node ID': self.node_id, 'sChains on node': schains_on_node, 'Empty sChain structs': schains_holes}, 'Monitoring sChains')) threads = [] for schain in schains: - if not schain['active']: continue + if not schain['active']: + continue schain_thread = CustomThread(f'sChain monitor: {schain["name"]}', self.monitor_schain, opts=schain, once=True) schain_thread.start() @@ -71,18 +78,19 @@ def monitor_schains(self, opts): def monitor_schain(self, schain): name = schain['name'] - checks = SChainChecks(name, self.node_id, log=True, failhook=capture_message).get_all() + owner = schain['owner'] + checks = SChainChecks(name, self.node_id, log=True).get_all() if not checks['data_dir']: init_schain_dir(name) if not checks['config']: - self.init_schain_config(name) - if not checks['dkg']: - try: - init_bls(self.skale.web3, self.skale, self.wallet, schain['name']) - except FailedDKG: - # todo: clean up here - exit(1) + self.init_schain_config(name, owner) + #if not checks['dkg']: + # try: + # init_bls(self.skale.web3, self.skale, schain['name']) # todo! + # except FailedDKG: + # # todo: clean up here + # exit(1) if not checks['volume']: init_data_volume(schain) if not checks['container']: @@ -90,11 +98,11 @@ def monitor_schain(self, schain): if not checks['ima_container']: self.monitor_ima_container(schain) - def init_schain_config(self, schain_name): + def init_schain_config(self, schain_name, schain_owner): config_filepath = get_schain_config_filepath(schain_name) if not os.path.isfile(config_filepath): logger.warning(f'sChain config not found: {config_filepath}, trying to create.') - schain_config = generate_schain_config(schain_name, self.node_id, self.skale) + schain_config = generate_schain_config(schain_name, schain_owner, self.node_id, self.skale) save_schain_config(schain_config, schain_name) def check_container(self, schain_name, volume_required=False): diff --git a/core/schains/runner.py b/core/schains/runner.py index 602240b7e..8678ad3c8 100644 --- a/core/schains/runner.py +++ b/core/schains/runner.py @@ -103,7 +103,7 @@ def add_config_volume(run_args): if not run_args.get('volumes', None): run_args['volumes'] = {} # mount /skale_node_data - run_args['volumes'][NODE_DATA_PATH_HOST] = { + run_args['volumes'][NODE_DATA_PATH_HOST] = { 'bind': NODE_DATA_PATH, "mode": "ro" } diff --git a/requirements.txt b/requirements.txt index 25fc8fec2..802fa4669 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,12 +13,11 @@ simple-crypt==4.1.7 pycryptodome==3.4.11 coincurve==12.0.0 -skale.py==2.0.dev10 -sgx.py==0.2.dev2 +skale.py==2.0.dev16 PyYAML==5.1.2 psutil==5.6.3 -sentry-sdk==0.11.2 - colorful==0.5.4 + +filelock==3.0.12 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/core/__init__.py b/tests/core/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/core/schains/__init__.py b/tests/core/schains/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/core/schains/runner.py b/tests/core/schains/runner.py deleted file mode 100644 index c59a87a7e..000000000 --- a/tests/core/schains/runner.py +++ /dev/null @@ -1,24 +0,0 @@ -from core.schains.runner import run_schain_container - -DEFAULT_SCHAIN_NAME = 'test' -DEFAULT_SCHAIN_DATA_DIR = '/data_dir' -DEFAULT_SCHAIN_STRUCT = {'name': DEFAULT_SCHAIN_NAME, 'partOfNode': 4} -TEST_CONFIG_FILEPATH = '/skale_node_data/schains/test/config_test.json' # todo! - -DEFAULT_SCHAIN_ENV = { - "SSL_KEY_PATH": 'NULL', - "SSL_CERT_PATH": 'NULL', - "HTTP_RPC_PORT": '18880', - "HTTPS_RPC_PORT": '18881', - "WS_RPC_PORT": '18882', - "WSS_RPC_PORT": '18883', - - "SCHAIN_ID": DEFAULT_SCHAIN_NAME, - "CONFIG_FILE": TEST_CONFIG_FILEPATH, - "DATA_DIR": DEFAULT_SCHAIN_DATA_DIR -} - - -def test_run_schain_container(skale): - container_name = run_schain_container(DEFAULT_SCHAIN_NAME, DEFAULT_SCHAIN_ENV) - diff --git a/tools/bls/dkg_client.py b/tools/bls/dkg_client.py index a49157fd3..447a33639 100644 --- a/tools/bls/dkg_client.py +++ b/tools/bls/dkg_client.py @@ -22,34 +22,39 @@ import logging import coincurve -from time import sleep +from skale.utils.web3_utils import wait_receipt + from tools.configs import NODE_DATA_PATH sys.path.insert(0, NODE_DATA_PATH) -from dkgpython import dkg -from skale.utils.web3_utils import wait_receipt +# from dkgpython import dkg # todo: uncomment logger = logging.getLogger(__name__) + def bxor(b1, b2): parts = [] for b1, b2 in zip(b1, b2): parts.append(bytes([b1 ^ b2])) return b''.join(parts) + def encrypt(plaintext, secret_key): - plaintext_in_bytes = bytearray(int(plaintext).to_bytes(32, byteorder ='big')) + plaintext_in_bytes = bytearray(int(plaintext).to_bytes(32, byteorder='big')) return bxor(plaintext_in_bytes, secret_key) + def decrypt(ciphertext, secret_key): xor_val = bxor(ciphertext, secret_key) ret_val = binascii.hexlify(xor_val) return str(int(ret_val.decode(), 16)) + class DkgVerificationError(Exception): def __init__(self, msg): super().__init__(msg) + def convert_g2_points_to_hex(data): data_hexed = "0x" for coord in data: @@ -59,11 +64,12 @@ def convert_g2_points_to_hex(data): temp = '0' + temp data_hexed += temp temp = hex(int(elem[1]))[2:] - while len(temp) < 64 : + while len(temp) < 64: temp = '0' + temp data_hexed += temp return data_hexed + def convert_g2_point_to_hex(data): data_hexed = "0x" for coord in data: @@ -74,10 +80,12 @@ def convert_g2_point_to_hex(data): data_hexed += temp return data_hexed + class DKGClient: - def __init__(self, node_id_dkg, node_id_contract, node_web3, skale, wallet, t, n, schain_name, public_keys, node_ids_dkg, node_ids_contract): + def __init__(self, node_id_dkg, node_id_contract, node_web3, skale, wallet, t, n, + schain_name, public_keys, node_ids_dkg, node_ids_contract): self.schain_name = schain_name - self.group_index = node_web3.sha3(text = self.schain_name) + self.group_index = node_web3.sha3(text=self.schain_name) self.node_id_contract = node_id_contract self.node_id_dkg = node_id_dkg self.node_web3 = node_web3 diff --git a/tools/config_storage.py b/tools/config_storage.py deleted file mode 100644 index 818b78934..000000000 --- a/tools/config_storage.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import os, json, logging -logger = logging.getLogger(__name__) - -class ConfigStorage: - def __init__(self, path, init={}): - self.path = path - self.init = init - self.config = self.__safe_read_config() - - def __getitem__(self, item): - return self.config[item] - - def __setitem__(self, key, value): - self.config[key] = value - self.update(self.config) - - def update_item_field(self, key, field, value): - item = self.config[key] - item[field] = value - self.config[key] = item - self.update(self.config) - - def update(self, new_config): - config = self._read_config() - config.update(new_config) - self.config = config - self._write_config(config) - return config - - def get(self): - return self.config - - def safe_get(self, item): - try: - return self.config[item] - except KeyError: - logger.debug(f'key {item} is not found in config {self.path}') - return None - - def __safe_read_config(self): - if not self.__check_config_file(): - self.__init_config_file() - return self._read_config() - - def _read_config(self): - with open(self.path, encoding='utf-8') as data_file: - config = json.loads(data_file.read()) - return config - - def _write_config(self, config): - with open(self.path, 'w') as data_file: - json.dump(config, data_file, indent=2) - - def __check_config_file(self): - return os.path.exists(self.path) - - def __init_config_file(self): - self._write_config(self.init) diff --git a/tools/configs/__init__.py b/tools/configs/__init__.py index d04c2262c..895b9e55a 100644 --- a/tools/configs/__init__.py +++ b/tools/configs/__init__.py @@ -1,14 +1,14 @@ import os -from tools.configs.flask import FLASK_DEBUG_MODE LONG_LINE = '=' * 100 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S" +RUNNING_ON_HOST = os.environ.get('RUNNING_ON_HOST', False) SKALE_DIR_HOST = os.environ['SKALE_DIR_HOST'] NODE_DATA_PATH_HOST = os.path.join(SKALE_DIR_HOST, 'node_data') -if FLASK_DEBUG_MODE: +if RUNNING_ON_HOST: SKALE_VOLUME_PATH = SKALE_DIR_HOST NODE_DATA_PATH = NODE_DATA_PATH_HOST else: @@ -16,7 +16,6 @@ NODE_DATA_PATH = '/skale_node_data' - CONFIG_FOLDER_NAME = 'config' CONTRACTS_INFO_FOLDER_NAME = 'contracts_info' diff --git a/tools/configs/containers.py b/tools/configs/containers.py index d94065807..9107c477f 100644 --- a/tools/configs/containers.py +++ b/tools/configs/containers.py @@ -34,4 +34,4 @@ CONTAINER_NOT_FOUND = 'not_found' EXITED_STATUS = 'exited' -RUNNING_STATUS = 'running' \ No newline at end of file +RUNNING_STATUS = 'running' diff --git a/tools/configs/filebeat.py b/tools/configs/filebeat.py index c2b720b56..5b6ac2663 100644 --- a/tools/configs/filebeat.py +++ b/tools/configs/filebeat.py @@ -4,4 +4,4 @@ FILEBEAT_TEMPLATE_PATH = os.path.join(CONFIG_FOLDER, 'filebeat.yml.j2') FILEBEAT_CONFIG_PATH = os.path.join(NODE_DATA_PATH, 'filebeat.yml') -FILEBEAT_CONTAINER_NAME = 'monitor_filebeat' \ No newline at end of file +FILEBEAT_CONTAINER_NAME = 'monitor_filebeat' diff --git a/tools/configs/flask.py b/tools/configs/flask.py index de101c3e1..ed275e967 100644 --- a/tools/configs/flask.py +++ b/tools/configs/flask.py @@ -27,5 +27,3 @@ FLASK_DEBUG_MODE = os.environ['FLASK_DEBUG_MODE'] == 'True' SKALE_LIB_NAME = 'skale.py' - - diff --git a/tools/configs/lvm.py b/tools/configs/lvm.py deleted file mode 100644 index 450cc43d7..000000000 --- a/tools/configs/lvm.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import os -from tools.config import PROJECT_DIR - -LVM_SCRIPT_NAME = 'lvm.sh' -LVM_SCRIPT_PATH = os.path.join(PROJECT_DIR, 'server', 'core', 'lvm', LVM_SCRIPT_NAME) - -VOL_GROUP_NAME = 'SkaleLVMVolGroup' -#VOL_GROUP_NAME = 'LVMVolGroup' \ No newline at end of file diff --git a/tools/configs/resource_allocation.py b/tools/configs/resource_allocation.py index 23bfdd438..ac43a0993 100644 --- a/tools/configs/resource_allocation.py +++ b/tools/configs/resource_allocation.py @@ -33,4 +33,4 @@ RESOURCE_ALLOCATION_FILEPATH = os.path.join(NODE_DATA_PATH, RESOURCE_ALLOCATION_FILENAME) DISK_MOUNTPOINT_FILENAME = 'disk_mountpoint.txt' -DISK_MOUNTPOINT_FILEPATH = os.path.join(NODE_DATA_PATH, DISK_MOUNTPOINT_FILENAME) \ No newline at end of file +DISK_MOUNTPOINT_FILEPATH = os.path.join(NODE_DATA_PATH, DISK_MOUNTPOINT_FILENAME) diff --git a/tools/configs/schains.py b/tools/configs/schains.py index 2a4c7ceb5..04a784fc9 100644 --- a/tools/configs/schains.py +++ b/tools/configs/schains.py @@ -23,7 +23,7 @@ SCHAINS_DIR_NAME = 'schains' SCHAINS_DIR_PATH = os.path.join(NODE_DATA_PATH, SCHAINS_DIR_NAME) -SCHAIN_OWNER_ALLOC = 1000000000000000000000 # todo: tmp! +SCHAIN_OWNER_ALLOC = 1000000000000000000000 # todo: tmp! NODE_OWNER_ALLOC = 1000000000000000000000 # todo: tmp! DATA_DIR_NAME = 'data_dir' diff --git a/tools/configs/web3.py b/tools/configs/web3.py index 0a0bf1846..6e52cbd6e 100644 --- a/tools/configs/web3.py +++ b/tools/configs/web3.py @@ -20,5 +20,6 @@ import os from tools.configs import CONTRACTS_INFO_FOLDER, MANAGER_CONTRACTS_INFO_NAME +TM_URL = os.environ['TM_URL'] ENDPOINT = os.environ['ENDPOINT'] ABI_FILEPATH = os.path.join(CONTRACTS_INFO_FOLDER, MANAGER_CONTRACTS_INFO_NAME) diff --git a/tools/custom_thread.py b/tools/custom_thread.py index ff176f0a7..0fe176e58 100644 --- a/tools/custom_thread.py +++ b/tools/custom_thread.py @@ -51,9 +51,10 @@ def run(self): def safe_run_func(self): try: self.func(self.opts) - except Exception as e: + except Exception: logger.exception( - f'Error was occurred during the function execution: {self.func.__name__}. See full stacktrace below.') + f'Error was occurred during the function execution: {self.func.__name__}. \ + See full stacktrace below.') # raise e todo: handle def join(self, timeout=None): diff --git a/tools/docker_utils.py b/tools/docker_utils.py index c8b7c2a10..ac69d8636 100644 --- a/tools/docker_utils.py +++ b/tools/docker_utils.py @@ -72,7 +72,7 @@ def data_volume_exists(self, name): def create_data_volume(self, name, size=None): driver_opts = {'size': str(size)} if size else None logging.info( - f'Creating volume, driver: convoy, size: {size}, name: {name}, driver_opts: {driver_opts}') + f'Creating volume - size: {size}, name: {name}, driver_opts: {driver_opts}') volume = self.client.volumes.create( name=name, driver='convoy', diff --git a/tools/helper.py b/tools/helper.py index 8d6d81002..d93f5a1cf 100644 --- a/tools/helper.py +++ b/tools/helper.py @@ -25,17 +25,24 @@ from jinja2 import Environment + logger = logging.getLogger(__name__) -def read_json(path): - with open(path, encoding='utf-8') as data_file: - return json.loads(data_file.read()) +def read_json(path, mode='r'): + with open(path, mode=mode, encoding='utf-8') as data_file: + return json.load(data_file) def write_json(path, content): with open(path, 'w') as outfile: json.dump(content, outfile, indent=4) + outfile.close() + + +def init_file(path, content=None): + if not os.path.exists(path): + write_json(path, content) def files(path): @@ -66,10 +73,6 @@ def format_output(res): return res.stdout.decode('UTF-8').rstrip(), res.stderr.decode('UTF-8').rstrip() -def get_sentry_env_name(manager_address): - return f'manager@{manager_address}' - - def read_file(path): file = open(path, 'r') text = file.read() diff --git a/tools/local_wallet.py b/tools/local_wallet.py deleted file mode 100644 index c49187a1f..000000000 --- a/tools/local_wallet.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE Admin -# -# Copyright (C) 2019 SKALE Labs -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -from tools.config import LOCAL_WALLET_FILEPATH -from encrypted_storage import EncryptedStorage - - -class LocalWallet(): - def __init__(self, password): - self.filepath = LOCAL_WALLET_FILEPATH - self.encrypted_storage = EncryptedStorage(LOCAL_WALLET_FILEPATH, password) - - def get(self): - return self.encrypted_storage.get() - - def update(self, text): - self.encrypted_storage.update(text) diff --git a/tools/logger.py b/tools/logger.py index 50552056f..8528fcc14 100644 --- a/tools/logger.py +++ b/tools/logger.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import os import sys import logging from logging import Formatter, StreamHandler diff --git a/tools/sgx_utils.py b/tools/sgx_utils.py index 98e411c9d..5db51e349 100644 --- a/tools/sgx_utils.py +++ b/tools/sgx_utils.py @@ -18,19 +18,37 @@ # along with this program. If not, see . import os +import logging + from sgx import SgxClient -sgx_key_config_item = 'sgx_key_name' +from tools.str_formatters import arguments_list_string -def generate_sgx_key(config): - if not os.environ.get('SGX_SERVER_URL') or config.safe_get(sgx_key_config_item): - return - sgx = SgxClient(os.environ['SGX_SERVER_URL']) - key_name = sgx.generate_key().keyName - save_sgx_key(key_name, config) +logger = logging.getLogger(__name__) + +SGX_KEY_CONFIG_NAME = 'sgx_key_name' +SGX_SERVER_URL = os.environ.get('SGX_SERVER_URL') + +class SGXConnecionError(Exception): + """Raised when admin couldn't establish connection with SGX server""" + + +def generate_sgx_key(config): + if not SGX_SERVER_URL: + raise SGXConnecionError('SGX server URL is not provided') + if not config.sgx_key_name: + sgx = SgxClient(SGX_SERVER_URL) + key_info = sgx.generate_key() + logger.info(arguments_list_string({ + 'Name': key_info.name, + 'Address': key_info.address + }, 'Generated new SGX key')) + config.sgx_key_name = key_info.name -def save_sgx_key(key_name, config): - config.update({sgx_key_config_item: key_name}) +def sgx_server_text(): + if SGX_SERVER_URL: + return SGX_SERVER_URL + return 'Not connected' diff --git a/tools/token_utils.py b/tools/token_utils.py index bfbe321b3..87957f8fa 100644 --- a/tools/token_utils.py +++ b/tools/token_utils.py @@ -17,29 +17,27 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import os import logging import secrets -from tools.config_storage import ConfigStorage +from tools.str_formatters import arguments_list_string +from tools.helper import write_json, read_json from tools.configs import TOKENS_FILEPATH logger = logging.getLogger(__name__) -class TokenUtils(): - def __init__(self, filepath=TOKENS_FILEPATH): - self.filepath = filepath - self.token_storage = ConfigStorage(filepath) +def init_user_token(): + if not os.path.exists(TOKENS_FILEPATH): + token = generate_user_token() + logger.info(arguments_list_string({'Token': token}, 'Generated registration token')) + write_json(TOKENS_FILEPATH, {'token': token}) + return token + else: + tokens = read_json(TOKENS_FILEPATH) + return tokens['token'] - def add_token(self): - token = self.generate_new_token() - self.save_token(token) - def generate_new_token(self, len=40): - return secrets.token_hex(len) - - def save_token(self, token): - self.token_storage.update({'token': token}) - - def get_token(self): - return self.token_storage.safe_get('token') \ No newline at end of file +def generate_user_token(token_len=40): + return secrets.token_hex(token_len) diff --git a/tools/wallet_utils.py b/tools/wallet_utils.py new file mode 100644 index 000000000..6dced63ac --- /dev/null +++ b/tools/wallet_utils.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Admin +# +# Copyright (C) 2019 SKALE Labs +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging + +logger = logging.getLogger(__name__) + +# todo: move to smart contracts +DEPOSIT_AMOUNT_SKL = 100 +DEPOSIT_AMOUNT_ETH = 0.2 + +DEPOSIT_AMOUNT_SKL_WEI = DEPOSIT_AMOUNT_SKL * (10 ** 18) +DEPOSIT_AMOUNT_ETH_WEI = int(DEPOSIT_AMOUNT_ETH * (10 ** 18)) + + +def wallet_with_balance(skale): # todo: move to the skale.py + address = skale.wallet.address + eth_balance_wei = skale.web3.eth.getBalance(address) + skale_balance_wei = skale.token.get_balance(address) + return { + 'address': address, + 'eth_balance_wei': eth_balance_wei, + 'skale_balance_wei': skale_balance_wei, + 'eth_balance': str(skale.web3.fromWei(eth_balance_wei, 'ether')), + 'skale_balance': str(skale.web3.fromWei(skale_balance_wei, 'ether')) + } + + +def check_required_balance(skale): # todo: move to the skale.py + balances = wallet_with_balance(skale) + return int(balances['eth_balance_wei']) >= DEPOSIT_AMOUNT_ETH_WEI and int(balances[ + 'skale_balance_wei']) >= DEPOSIT_AMOUNT_SKL_WEI diff --git a/web/base_model.py b/web/base_model.py index a66e3018f..4ab642f86 100644 --- a/web/base_model.py +++ b/web/base_model.py @@ -25,5 +25,6 @@ class BaseModel(Model): database = database + class Meta: database = database diff --git a/web/helper.py b/web/helper.py index 1e55da48c..ee99c8637 100644 --- a/web/helper.py +++ b/web/helper.py @@ -58,4 +58,3 @@ def inner(*args, **kwargs): return f(*args, **kwargs) return inner - diff --git a/web/routes/auth.py b/web/routes/auth.py index 7f74fd412..fb4deb62e 100644 --- a/web/routes/auth.py +++ b/web/routes/auth.py @@ -40,11 +40,10 @@ def get_user_info(): def join(): request_data = request.json if not request_data.get('username') or not request_data.get( - 'password') or not request_data.get( - 'token'): + 'password') or not request_data.get('token'): return construct_err_response(400, [{'msg': 'Wrong data provided'}]) - if request_data['token'] != token: # todo: check token from file! + if request_data['token'] != token: return construct_err_response(400, [{'msg': 'Token not match'}]) user, err = User.join(request_data['username'], request_data['password'], token) diff --git a/web/routes/metrics.py b/web/routes/metrics.py index c8d8b61cb..fa51927d7 100644 --- a/web/routes/metrics.py +++ b/web/routes/metrics.py @@ -23,7 +23,6 @@ from flask import Blueprint, request from core.db import BountyEvent -from core.node.utils import get_node_id from web.helper import construct_ok_response, login_required logger = logging.getLogger(__name__) @@ -36,11 +35,11 @@ def construct_metrics_bp(skale, config): metrics_bp = Blueprint('metrics', __name__) def get_start_date(): - node_id = get_node_id(config) + node_id = config.id return skale.nodes_data.get(node_id)['start_date'] def get_last_reward_date(): - node_id = get_node_id(config) + node_id = config.id return skale.nodes_data.get(node_id)['last_reward_date'] def find_block_for_tx_stamp(tx_stamp, lo=0, hi=None): @@ -82,7 +81,7 @@ def get_bounty_from_db(is_from_begin=True, limit=None): return bounties_list def get_bounty_from_events(start_date, end_date=None, is_limited=True): - node_id = get_node_id(config) + node_id = config.id bounties = [] start_block_number = find_block_for_tx_stamp(start_date) cur_block_number = skale.web3.eth.blockNumber diff --git a/web/routes/node_info.py b/web/routes/node_info.py index 446f1595e..d6041fcbf 100644 --- a/web/routes/node_info.py +++ b/web/routes/node_info.py @@ -30,7 +30,7 @@ logger = logging.getLogger(__name__) -def construct_node_info_bp(skale, wallet, docker_utils): +def construct_node_info_bp(skale, docker_utils): node_info_bp = Blueprint('node_info', __name__) @node_info_bp.route('/get-rpc-credentials', methods=['GET']) @@ -66,7 +66,6 @@ def about_node(): 'network': { 'endpoint': ENDPOINT }, - 'local_wallet': wallet.get_with_balance() } return construct_ok_response(node_about) diff --git a/web/routes/nodes.py b/web/routes/nodes.py index 9ad519449..813edf82b 100644 --- a/web/routes/nodes.py +++ b/web/routes/nodes.py @@ -35,12 +35,11 @@ def construct_nodes_bp(skale, node, docker_utils): @login_required def node_info(): logger.debug(request) - node_info = node.get_node_info() - return construct_ok_response(node_info) + return construct_ok_response(node.info) @nodes_bp.route('/create-node', methods=['POST']) @login_required - def create_node(): + def register_node(): logger.debug(request) if not request.json: abort(400) @@ -62,7 +61,7 @@ def create_node(): logger.error(error_msg) return construct_err_response(HTTPStatus.BAD_REQUEST, [error_msg]) - res = node.create(ip, public_ip, port, name) + res = node.register(ip, public_ip, port, name) if res['status'] != 1: return construct_err_response(HTTPStatus.INTERNAL_SERVER_ERROR, res['errors']) return construct_response(HTTPStatus.CREATED, res['data']) @@ -98,4 +97,4 @@ def skale_containers_list(): containers_list = docker_utils.get_all_skale_containers(all=all, format=True) return construct_ok_response(containers_list) - return nodes_bp \ No newline at end of file + return nodes_bp diff --git a/web/routes/schains.py b/web/routes/schains.py index 96b8bb78f..5534d6dc3 100644 --- a/web/routes/schains.py +++ b/web/routes/schains.py @@ -20,27 +20,22 @@ import logging from http import HTTPStatus -from flask import Blueprint, request, abort +from flask import Blueprint, request -import skale.utils.helper as Helper from core.schains.helper import get_schain_config -from web.helper import construct_ok_response, construct_err_response, construct_response, \ - login_required +from web.helper import construct_ok_response, construct_err_response, login_required logger = logging.getLogger(__name__) -def construct_schains_bp(skale, wallet, docker_utils, node): +def construct_schains_bp(skale, config, docker_utils): schains_bp = Blueprint('schains', __name__) @schains_bp.route('/get-owner-schains', methods=['GET']) @login_required def owner_schains(): logger.debug(request) - - local_wallet = wallet.get() - schains = skale.schains_data.get_schains_for_owner(local_wallet['address']) - + schains = skale.schains_data.get_schains_for_owner(skale.wallet.address) for schain in schains: nodes = skale.schains_data.get_nodes_for_schain_config(schain['name']) schain['nodes'] = nodes @@ -50,45 +45,25 @@ def owner_schains(): @login_required def get_schain_config_route(): logger.debug(request) - schain_name = request.args.get('schain-name') # todo: handle - if schain name is empty or invalid schain_config = get_schain_config(schain_name) skale_schain_config = schain_config['skaleConfig'] return construct_ok_response(skale_schain_config) - @schains_bp.route('/create-schain', methods=['POST']) - @login_required - def create_schain(): - logger.debug(request) - if not request.json: - abort(400) - - lifetime_years = 1 - lifetime_seconds = lifetime_years * 366 * 86400 - type_of_nodes = 4 - - price_in_wei = skale.schains.get_schain_price(type_of_nodes, lifetime_seconds) - name = Helper.generate_random_name() - - res = skale.manager.create_schain(lifetime_seconds, type_of_nodes, price_in_wei, name) - receipt = Helper.await_receipt(skale.web3, res['tx']) - res = {'res': receipt['status']} - return construct_response(HTTPStatus.CREATED, res) - @schains_bp.route('/containers/schains/list', methods=['GET']) @login_required def schains_containers_list(): logger.debug(request) - all = request.args.get('all') == 'True' - containers_list = docker_utils.get_all_schain_containers(all=all, format=True) + _all = request.args.get('all') == 'True' + containers_list = docker_utils.get_all_schain_containers(all=_all, format=True) return construct_ok_response(containers_list) @schains_bp.route('/schains/list', methods=['GET']) @login_required def node_schains_list(): logger.debug(request) - node_id = node.get_node_id() + node_id = config.id if node_id is None: return construct_err_response(HTTPStatus.BAD_REQUEST, ['No node installed']) schains_list = skale.schains_data.get_schains_for_node(node_id) diff --git a/web/routes/security.py b/web/routes/security.py index 0c145fd19..f102823dd 100644 --- a/web/routes/security.py +++ b/web/routes/security.py @@ -63,7 +63,6 @@ def upload_ssl_certificate(): return construct_ok_response(1) - @security_bp.route('/certificates-info', methods=['GET']) @login_required def get_certificates_info(): diff --git a/web/routes/validators.py b/web/routes/validators.py index 85a8f91aa..368798ec4 100644 --- a/web/routes/validators.py +++ b/web/routes/validators.py @@ -18,7 +18,6 @@ # along with this program. If not, see . import logging -from http import HTTPStatus from flask import Blueprint, request from playhouse.shortcuts import model_to_dict @@ -26,12 +25,11 @@ from core.db import BountyEvent from tools.configs import DATETIME_FORMAT -from core.node.utils import get_node_id logger = logging.getLogger(__name__) -def construct_validators_bp(skale, config, wallet): +def construct_validators_bp(skale, config): validators_bp = Blueprint('validators', __name__) @validators_bp.route('/bounty-info', methods=['GET']) @@ -52,20 +50,12 @@ def bounty_info(): @login_required def validators_info(): logger.debug(request) - - node_id = get_node_id(config) - # todo: handle no node_id - - print(skale.validators_data.get_validated_array) - - res = skale.validators_data.get_validated_array(node_id, wallet.get()['address']) - print(res) - + # todo: handle no config.id + res = skale.validators_data.get_validated_array(config.id, skale.wallet.address) validators = { 'validating': res, - 'validated_by': 2 + 'validated_by': None } - return construct_ok_response({'validators': validators}) return validators_bp diff --git a/web/routes/wallet.py b/web/routes/wallet.py index b34ffb784..d68816439 100644 --- a/web/routes/wallet.py +++ b/web/routes/wallet.py @@ -18,31 +18,23 @@ # along with this program. If not, see . import logging -from http import HTTPStatus from flask import Blueprint, request -from web.helper import construct_ok_response, construct_response, login_required +from web.helper import construct_ok_response, login_required +from tools.wallet_utils import wallet_with_balance logger = logging.getLogger(__name__) -def construct_wallet_bp(wallet): +def construct_wallet_bp(skale): wallet_bp = Blueprint('wallet', __name__) - @wallet_bp.route('/create-wallet', methods=['POST']) - @login_required - def create_wallet(): - logger.debug(request) - res = wallet.get_or_generate() - return construct_response(HTTPStatus.CREATED, res) - @wallet_bp.route('/load-wallet', methods=['GET']) @login_required def load_wallet(): logger.debug(request) - wallet.get_or_generate() # todo: tmp, remove later! - res = wallet.get_with_balance() + res = wallet_with_balance(skale) return construct_ok_response(res) return wallet_bp diff --git a/web/user.py b/web/user.py index e3ad930fd..a6331bd98 100644 --- a/web/user.py +++ b/web/user.py @@ -65,7 +65,6 @@ def login(cls, username, password, user_session): user_session.auth(user) return True, None - def info(self): return { 'username': self.username,