From a3e17748b9779c3bfacf26275f365e2ab7ea2a22 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 31 Jan 2022 20:02:46 +0200 Subject: [PATCH 01/27] SKALE-4289 SKALE Proxy v2 initial structure --- .flake8 | 3 + .gitignore | 7 ++ Dockerfile | 39 +++-------- README.md | 17 +++-- VERSION | 2 +- config/nginx.conf | 40 +++++++++++ docker-compose.yml | 34 +++++----- endpoints.py | 140 -------------------------------------- proxy/__init__.py | 0 proxy/config.py | 32 +++++++++ proxy/endpoints.py | 151 +++++++++++++++++++++++++++++++++++++++++ proxy/helper.py | 37 ++++++++++ proxy/main.py | 36 ++++++++++ proxy/skaled_ports.py | 33 +++++++++ requirements-dev.txt | 1 + requirements.txt | 1 + sample/nginx-site.conf | 26 ------- www/.keep | 0 18 files changed, 382 insertions(+), 217 deletions(-) create mode 100644 .flake8 create mode 100644 config/nginx.conf delete mode 100644 endpoints.py create mode 100644 proxy/__init__.py create mode 100644 proxy/config.py create mode 100644 proxy/endpoints.py create mode 100644 proxy/helper.py create mode 100644 proxy/main.py create mode 100644 proxy/skaled_ports.py create mode 100644 requirements-dev.txt create mode 100644 requirements.txt delete mode 100644 sample/nginx-site.conf create mode 100644 www/.keep diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..39451e9 --- /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,helper-scripts \ No newline at end of file diff --git a/.gitignore b/.gitignore index de137b1..4fff1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ *.crt *.key .env + +venv + +__pycache__ + +abi.json +chains.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7012daf..6551341 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,16 @@ -FROM ubuntu:20.10 - -# Keep upstart from complaining -RUN dpkg-divert --local --rename --add /sbin/initctl -RUN ln -sf /bin/true /sbin/initctl - -# Let the conatiner know that there is no tty -ENV DEBIAN_FRONTEND noninteractive +FROM python:3.8-buster RUN apt-get update -RUN apt-get -y upgrade - -# Basic Requirements -RUN apt-get -y install nginx pwgen python3 python3-pip curl git unzip vim - -RUN pip3 install web3==5.13.1 - -# nginx config -RUN sed -i -e"s/keepalive_timeout\s*65/keepalive_timeout 2/" /etc/nginx/nginx.conf -RUN sed -i -e"s/keepalive_timeout 2/keepalive_timeout 2;\n\tclient_max_body_size 100m/" /etc/nginx/nginx.conf -RUN echo "daemon off;" >> /etc/nginx/nginx.conf -# nginx site conf -ADD sample/nginx-site.conf /etc/nginx/sites-available/default -COPY abi /etc/ +RUN mkdir /usr/src/proxy /data +WORKDIR /usr/src/proxy -RUN pip3 install web3==5.13.1 +COPY requirements.txt ./ +RUN pip3 install --no-cache-dir -r requirements.txt -ADD ./endpoints.py /etc/endpoints.py -ADD ./periodic_config_update.py /etc/periodic_config_update.py -ADD ./VERSION /etc/VERSION +COPY . . -# Initialization and Startup Script -ADD ./start.sh /start.sh -RUN chmod 755 /start.sh +ENV PYTHONPATH="/usr/src/proxy" +ENV COLUMNS=80 -CMD ["/bin/bash", "/start.sh"] +CMD python /usr/src/proxy/proxy/main.py diff --git a/README.md b/README.md index 3ad7ddd..19dd3f9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -# skale-proxy +# SKALE Proxy + +[![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB) skale-proxy is a public service that provides proxied and load-balanced JSON-RPC endpoints for SKALE chains -# running +## Usage guide + +### Prerequisites +- Docker +- docker-compose 1. Place you ABI json file into abi directory @@ -26,7 +32,10 @@ skale-proxy is a public service that provides proxied and load-balanced JSON-RPC Voila! -# local development +## License + +[![License](https://img.shields.io/github/license/skalenetwork/skale-admin.svg)](LICENSE) -Set `FULL_PROXY_DOMAIN_NAME` to localhost and run `cert_gen.sh`. This will generate a self-signed cert in the `data/` directory. +All contributions to SKALE Admin are made under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl-3.0.en.html). See [LICENSE](LICENSE). +Copyright (C) 2022-Present SKALE Labs. \ No newline at end of file diff --git a/VERSION b/VERSION index b113158..359a5b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.77.1 +2.0.0 \ No newline at end of file diff --git a/config/nginx.conf b/config/nginx.conf new file mode 100644 index 0000000..f7b2eb0 --- /dev/null +++ b/config/nginx.conf @@ -0,0 +1,40 @@ +events { + worker_connections 1024; +} + +http { + # server { + # listen 443 ssl; + # ssl_certificate /config/server.crt; + # ssl_certificate_key /config/server.key; + # ssl_verify_client off; + # server_name dappnet-api.skalenodes.com; + # # location / { + # # proxy_http_version 1.1; + # # proxy_pass http://proxy-ui:5000/; + # # proxy_set_header Upgrade $http_upgrade; + # # proxy_set_header Connection 'upgrade'; + # # proxy_set_header Host $host; + # # proxy_cache_bypass $http_upgrade; + # # } + # location /files/ { + # root /usr/share/nginx/www; + # } + # } + + server { + listen 80; + server_name dappnet-api.skalenodes.com; + # location / { + # proxy_http_version 1.1; + # proxy_pass http://proxy-ui:5000/; + # proxy_set_header Upgrade $http_upgrade; + # proxy_set_header Connection 'upgrade'; + # proxy_set_header Host $host; + # proxy_cache_bypass $http_upgrade; + # } + location /files/ { + root /usr/share/nginx/www; + } + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index bf99927..ff1f4cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,34 @@ version: '3' services: - proxy-ui: - image: proxy-ui:latest - build: - context: ./proxy-ui - dockerfile: Dockerfile - ports: - - "5000:5000" + # proxy-ui: + # image: proxy-ui:latest + # build: + # context: ./proxy-ui + # dockerfile: Dockerfile + # ports: + # - "5000:5000" skale-proxy: environment: - PROXY_FULL_HOST_NAME: ${PROXY_FULL_HOST_NAME} - ETH_ENDPOINT: ${ENDPOINT} - ENDPOINT_PREFIX: mainnet - ABI_FILENAME: abi.json + ETH_ENDPOINT: ${ETH_ENDPOINT} image: skale-proxy:latest build: context: . dockerfile: Dockerfile - ports: - - "80:80" - - "443:443" volumes: - - /dev/urandom:/dev/random - ./data:/data + - ./www:/www logging: driver: json-file options: max-size: "10m" max-file: "4" restart: unless-stopped - + nginx: + image: nginx:1.20.2 + container_name: skale_nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./www:/usr/share/nginx/www/files + - ./config/nginx.conf:/etc/nginx/nginx.conf diff --git a/endpoints.py b/endpoints.py deleted file mode 100644 index d94c7f6..0000000 --- a/endpoints.py +++ /dev/null @@ -1,140 +0,0 @@ -# pip install web3==5.13.1 -# Usage: ENDPOINT= ABI_FILEPATH= RESULTS_PATH= python endpoints.py - -import os -import json -import socket -from enum import Enum - -from web3 import Web3, HTTPProvider -from Crypto.Hash import keccak - - -ENDPOINT = os.environ['ETH_ENDPOINT'] - -if ENDPOINT is None: - print("Fatal error: ETH main net endpoint not set. Exiting") - exit(-5) - - -ABI_FILEPATH = "/tmp/abi.json" - -if not os.path.exists(ABI_FILEPATH): - print("Fatal error: could not find ABI. Exiting") - exit(-6) - - -RESULTS_PATH = "/tmp/chains.json" - -PORTS_PER_SCHAIN = 64 - - -class SkaledPorts(Enum): - PROPOSAL = 0 - CATCHUP = 1 - WS_JSON = 2 - HTTP_JSON = 3 - BINARY_CONSENSUS = 4 - ZMQ_BROADCAST = 5 - IMA_MONITORING = 6 - WSS_JSON = 7 - HTTPS_JSON = 8 - INFO_HTTP_JSON = 9 - - -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) - - -def schain_name_to_id(name): - keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) - return '0x' + keccak_hash.hexdigest() - - -def ip_from_bytes(bytes): - return socket.inet_ntoa(bytes) - - -def get_schain_index_in_node(schain_id, schains_ids_on_node): - for index, schain_id_on_node in enumerate(schains_ids_on_node): - if schain_id == schain_id_on_node: - return index - raise Exception(f'sChain {schain_id} is not found in the list: {schains_ids_on_node}') - - -def get_schain_base_port_on_node(schain_id, schains_ids_on_node, node_base_port): - schain_index = get_schain_index_in_node(schain_id, schains_ids_on_node) - return calc_schain_base_port(node_base_port, schain_index) - - -def calc_schain_base_port(node_base_port, schain_index): - return node_base_port + schain_index * PORTS_PER_SCHAIN - - -def calc_ports(schain_base_port): - return { - 'httpRpcPort': schain_base_port + SkaledPorts.HTTP_JSON.value, - 'httpsRpcPort': schain_base_port + SkaledPorts.HTTPS_JSON.value, - 'wsRpcPort': schain_base_port + SkaledPorts.WS_JSON.value, - 'wssRpcPort': schain_base_port + SkaledPorts.WSS_JSON.value, - 'infoHttpRpcPort': schain_base_port + SkaledPorts.INFO_HTTP_JSON.value - } - - -def compose_endpoints(node_dict, endpoint_type): - node_dict[f'http_endpoint_{endpoint_type}'] = f'http://{node_dict[endpoint_type]}:{node_dict["httpRpcPort"]}' - node_dict[f'https_endpoint_{endpoint_type}'] = f'https://{node_dict[endpoint_type]}:{node_dict["httpsRpcPort"]}' - node_dict[f'ws_endpoint_{endpoint_type}'] = f'ws://{node_dict[endpoint_type]}:{node_dict["wsRpcPort"]}' - node_dict[f'wss_endpoint_{endpoint_type}'] = f'wss://{node_dict[endpoint_type]}:{node_dict["wssRpcPort"]}' - node_dict[f'info_http_endpoint_{endpoint_type}'] = f'http://{node_dict[endpoint_type]}:{node_dict["infoHttpRpcPort"]}' - - -def endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id): - node_ids = schains_internal_contract.functions.getNodesInGroup(schain_id).call() - nodes = [] - for node_id in node_ids: - node = nodes_contract.functions.nodes(node_id).call() - node_dict = { - 'id': node_id, - 'name': node[0], - 'ip': ip_from_bytes(node[1]), - 'base_port': node[3], - 'domain': nodes_contract.functions.getNodeDomainName(node_id).call() - } - schain_ids = schains_internal_contract.functions.getSchainIdsForNode(node_id).call() - node_dict['schain_base_port'] = get_schain_base_port_on_node(schain_id, schain_ids, node_dict['base_port']) - node_dict.update(calc_ports(node_dict['schain_base_port'])) - - compose_endpoints(node_dict, endpoint_type='ip') - compose_endpoints(node_dict, endpoint_type='domain') - - nodes.append(node_dict) - schain = schains_internal_contract.functions.schains(schain_id).call() - return { - 'schain': schain, - 'schain_id': schain_name_to_id(schain[0])[:15], - 'nodes': nodes - } - - -def endpoints_for_all_schains(): - provider = HTTPProvider(ENDPOINT) - web3 = Web3(provider) - sm_abi = read_json(ABI_FILEPATH) - - schains_internal_contract = web3.eth.contract(address=sm_abi['schains_internal_address'], abi=sm_abi['schains_internal_abi']) - nodes_contract = web3.eth.contract(address=sm_abi['nodes_address'], abi=sm_abi['nodes_abi']) - schain_ids = schains_internal_contract.functions.getSchains().call() - - all_endpoints = [endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id) for schain_id in schain_ids] - write_json(RESULTS_PATH, all_endpoints) - - -if __name__ == '__main__': - endpoints_for_all_schains() \ No newline at end of file diff --git a/proxy/__init__.py b/proxy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/proxy/config.py b/proxy/config.py new file mode 100644 index 0000000..9a1d306 --- /dev/null +++ b/proxy/config.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 + +ENDPOINT = os.environ['ETH_ENDPOINT'] +PORTS_PER_SCHAIN = 64 + +MONITOR_INTERVAL = os.getenv('MONITOR_INTERVAL', 60 * 60 * 2) + +NGINX_WWW_FOLDER = '/www' +CHAINS_INFO_FILEPATH = os.path.join(NGINX_WWW_FOLDER, 'chains.json') + +DATA_FOLDER = '/data' +SM_ABI_DEFAULT_FILEPATH = os.path.join(DATA_FOLDER, 'abi.json') +SM_ABI_FILEPATH = os.getenv('SM_ABI_FILEPATH', SM_ABI_DEFAULT_FILEPATH) diff --git a/proxy/endpoints.py b/proxy/endpoints.py new file mode 100644 index 0000000..c25c13a --- /dev/null +++ b/proxy/endpoints.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 json + +from web3 import Web3, HTTPProvider +from Crypto.Hash import keccak + +from proxy.skaled_ports import SkaledPorts +from proxy.helper import ip_from_bytes, read_json, write_json +from proxy.config import PORTS_PER_SCHAIN, ENDPOINT, SM_ABI_FILEPATH + + +URL_PREFIXES = { + 'http': 'http://', + 'https': 'https://', + 'ws': 'ws://', + 'wss': 'wss://', + 'infoHttp': 'http://' +} + + +def schain_name_to_id(name: str) -> str: + keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) + return '0x' + keccak_hash.hexdigest() + + +def schain_name_to_network_id(name: str) -> str: + schain_name_to_id(name) + + +def get_schain_index_in_node(schain_id, schains_ids_on_node): + for index, schain_id_on_node in enumerate(schains_ids_on_node): + if schain_id == schain_id_on_node: + return index + raise Exception(f'sChain {schain_id} is not found in the list: {schains_ids_on_node}') + + +def get_schain_base_port_on_node(schain_id, schains_ids_on_node, node_base_port): + schain_index = get_schain_index_in_node(schain_id, schains_ids_on_node) + return calc_schain_base_port(node_base_port, schain_index) + + +def calc_schain_base_port(node_base_port, schain_index): + return node_base_port + schain_index * PORTS_PER_SCHAIN + + +def calc_ports(schain_base_port): + return { + 'httpRpcPort': schain_base_port + SkaledPorts.HTTP_JSON.value, + 'httpsRpcPort': schain_base_port + SkaledPorts.HTTPS_JSON.value, + 'wsRpcPort': schain_base_port + SkaledPorts.WS_JSON.value, + 'wssRpcPort': schain_base_port + SkaledPorts.WSS_JSON.value, + 'infoHttpRpcPort': schain_base_port + SkaledPorts.INFO_HTTP_JSON.value + } + + +def _compose_endpoints(node_dict, endpoint_type): + for prefix_name in URL_PREFIXES: + prefix = URL_PREFIXES[prefix_name] + port = node_dict[f'{prefix_name}RpcPort'] + key_name = f'{prefix_name}_endpoint_{endpoint_type}' + node_dict[key_name] = f'{prefix}{node_dict[endpoint_type]}:{port}' + + +def endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id): + """Generates endpoints list for a given SKALE chain""" + node_ids = schains_internal_contract.functions.getNodesInGroup(schain_id).call() + nodes = [] + for node_id in node_ids: + node = nodes_contract.functions.nodes(node_id).call() + node_dict = { + 'id': node_id, + 'name': node[0], + 'ip': ip_from_bytes(node[1]), + 'base_port': node[3], + 'domain': nodes_contract.functions.getNodeDomainName(node_id).call() + } + schain_ids = schains_internal_contract.functions.getSchainIdsForNode(node_id).call() + node_dict['schain_base_port'] = get_schain_base_port_on_node( + schain_id, schain_ids, node_dict['base_port'] + ) + node_dict.update(calc_ports(node_dict['schain_base_port'])) + + _compose_endpoints(node_dict, endpoint_type='ip') + _compose_endpoints(node_dict, endpoint_type='domain') + + nodes.append(node_dict) + + schain = schains_internal_contract.functions.schains(schain_id).call() + return { + 'schain': schain, + 'schain_id': schain_name_to_id(schain[0])[:15], + 'nodes': nodes + } + + +def init_contracts(web3: Web3, sm_abi: str): + schains_internal_contract = web3.eth.contract( + address=sm_abi['schains_internal_address'], + abi=sm_abi['schains_internal_abi'] + ) + nodes_contract = web3.eth.contract( + address=sm_abi['nodes_address'], + abi=sm_abi['nodes_abi'] + ) + return schains_internal_contract, nodes_contract + + +def endpoints_for_all_schains(endpoint: str, abi_filepath: str) -> list: + """Main function that generates endpoints for all SKALE Chains on the given network""" + provider = HTTPProvider(endpoint) + web3 = Web3(provider) + sm_abi = read_json(abi_filepath) + + schains_internal_contract, nodes_contract = init_contracts( + web3=web3, + sm_abi=sm_abi + ) + + schain_ids = schains_internal_contract.functions.getSchains().call() + + schain_ids = [schain_ids[0]] # TODO: TMP! + + endpoints = [ + endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id) + for schain_id in schain_ids + ] + return endpoints + + +if __name__ == '__main__': + endpoints = endpoints_for_all_schains(ENDPOINT, SM_ABI_FILEPATH) + print(json.dumps(endpoints, indent=4)) + # write_json(RESULTS_PATH, endpoints) diff --git a/proxy/helper.py b/proxy/helper.py new file mode 100644 index 0000000..3c07642 --- /dev/null +++ b/proxy/helper.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 json +import socket + + + +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) + + +def ip_from_bytes(bytes): + return socket.inet_ntoa(bytes) diff --git a/proxy/main.py b/proxy/main.py new file mode 100644 index 0000000..79b1984 --- /dev/null +++ b/proxy/main.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 time import sleep + +from proxy.endpoints import endpoints_for_all_schains +from proxy.helper import ip_from_bytes, read_json, write_json +from proxy.config import CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH + + +def main(): + while True: + endpoints = endpoints_for_all_schains(ENDPOINT, SM_ABI_FILEPATH) + # endpoints = ENDP + write_json(CHAINS_INFO_FILEPATH, endpoints) + sleep(MONITOR_INTERVAL) + + +if __name__ == '__main__': + main() diff --git a/proxy/skaled_ports.py b/proxy/skaled_ports.py new file mode 100644 index 0000000..dff2033 --- /dev/null +++ b/proxy/skaled_ports.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 SkaledPorts(Enum): + PROPOSAL = 0 + CATCHUP = 1 + WS_JSON = 2 + HTTP_JSON = 3 + BINARY_CONSENSUS = 4 + ZMQ_BROADCAST = 5 + IMA_MONITORING = 6 + WSS_JSON = 7 + HTTPS_JSON = 8 + INFO_HTTP_JSON = 9 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..b42fde8 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +flake8==3.7.8 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9134548 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +web3==5.26.0 diff --git a/sample/nginx-site.conf b/sample/nginx-site.conf deleted file mode 100644 index 0772eee..0000000 --- a/sample/nginx-site.conf +++ /dev/null @@ -1,26 +0,0 @@ - - - -upstream backend { - server testnet-16.skalenodes.com:10131; - server testnet-15.skalenodes.com:10195; -} - -server { - - listen 80; ## listen for ipv4; this line is default and implied - - root /usr/share/nginx/www; - index index.php index.html index.htm; - - # Make site accessible from http://localhost/ - server_name localhost; - - -# location /mainnet/chain1 { -# proxy_http_version 1.1; -# proxy_pass http://backend/; -# } - -} - diff --git a/www/.keep b/www/.keep new file mode 100644 index 0000000..e69de29 From 9b2f7dec63743b529aa192344ac3884750ab4e5f Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 31 Jan 2022 20:22:10 +0200 Subject: [PATCH 02/27] SKALE-4289 Add helper scripts, remove redundant files, update readme, add logger --- .env-sample | 2 -- .gitmodules | 4 +++ README.md | 4 +-- docker-compose.yml | 2 +- helper-scripts | 1 + proxy-ui | 2 +- proxy/main.py | 14 ++++++++-- proxy/nginx.py | 22 ++++++++++++++++ scripts/build_image.sh | 32 ----------------------- scripts/calculate_version.sh | 42 ------------------------------ cert_gen.sh => scripts/cert_gen.sh | 0 scripts/docker_build.py | 42 ------------------------------ scripts/publish_image.sh | 23 ---------------- 13 files changed, 43 insertions(+), 147 deletions(-) delete mode 100644 .env-sample create mode 160000 helper-scripts create mode 100644 proxy/nginx.py delete mode 100644 scripts/build_image.sh delete mode 100644 scripts/calculate_version.sh rename cert_gen.sh => scripts/cert_gen.sh (100%) delete mode 100644 scripts/docker_build.py delete mode 100644 scripts/publish_image.sh diff --git a/.env-sample b/.env-sample deleted file mode 100644 index f3a2a35..0000000 --- a/.env-sample +++ /dev/null @@ -1,2 +0,0 @@ -PROXY_FULL_HOST_NAME=your-proxy.endpoint.com -ETH_ENDPOINT=https://mainnet.infura.io/v3/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 89372a9..ebc93a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = proxy-ui url = https://github.com/skalenetwork/proxy-ui.git branch = master +[submodule "helper-scripts"] + path = helper-scripts + url = https://github.com/skalenetwork/helper-scripts.git + branch = develop diff --git a/README.md b/README.md index 19dd3f9..b1bb189 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB) -skale-proxy is a public service that provides proxied and load-balanced JSON-RPC endpoints for SKALE chains - +SKALE Proxy is high performance, easy-to-run public service that provides proxied and load-balanced +JSON-RPC endpoints for SKALE chains. It is based on NGINX. ## Usage guide diff --git a/docker-compose.yml b/docker-compose.yml index ff1f4cb..a440c3c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,4 +31,4 @@ services: - "443:443" volumes: - ./www:/usr/share/nginx/www/files - - ./config/nginx.conf:/etc/nginx/nginx.conf + - ./config/nginx.conf:/etc/nginx/nginx.conf:ro diff --git a/helper-scripts b/helper-scripts new file mode 160000 index 0000000..5cd6ed4 --- /dev/null +++ b/helper-scripts @@ -0,0 +1 @@ +Subproject commit 5cd6ed42c44ddf33126a4613cbc381c231401910 diff --git a/proxy-ui b/proxy-ui index 8e019cb..0cf2a33 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 8e019cb3557c1a31f6791a217289004892ab6013 +Subproject commit 0cf2a33422e1bbabbccf24faf8358df06536386f diff --git a/proxy/main.py b/proxy/main.py index 79b1984..5e6a0b3 100644 --- a/proxy/main.py +++ b/proxy/main.py @@ -17,18 +17,28 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging from time import sleep +from proxy.nginx import generate_nginx_configs from proxy.endpoints import endpoints_for_all_schains -from proxy.helper import ip_from_bytes, read_json, write_json +from proxy.helper import write_json from proxy.config import CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH +logger = logging.getLogger(__name__) + + def main(): + logger.info('Starting SKALE Proxy server') while True: + logger.info('Collecting endpoints list') endpoints = endpoints_for_all_schains(ENDPOINT, SM_ABI_FILEPATH) - # endpoints = ENDP write_json(CHAINS_INFO_FILEPATH, endpoints) + + generate_nginx_configs(endpoints) + + logger.info(f'Proxy iteration done, sleeping for {MONITOR_INTERVAL}') sleep(MONITOR_INTERVAL) diff --git a/proxy/nginx.py b/proxy/nginx.py new file mode 100644 index 0000000..3fa2b59 --- /dev/null +++ b/proxy/nginx.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 . + + +def generate_nginx_configs(endpoints: list) -> None: + pass diff --git a/scripts/build_image.sh b/scripts/build_image.sh deleted file mode 100644 index 16ca923..0000000 --- a/scripts/build_image.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -DOCKERFILE=$1 -CONTAINER_NAME=$2 - -: "${VERSION?Need to set VERSION}" -: "${BRANCH?Need to set BRANCH}" - -REPO_NAME=skalenetwork/$CONTAINER_NAME -IMAGE_NAME=$REPO_NAME:$VERSION - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" - -# Build image - -echo "Building $IMAGE_NAME..." -docker build -f "${DIR}"/../"${DOCKERFILE}" -t "${IMAGE_NAME}" . || exit $? - -if [ "${BRANCH}" = "stable" ]; -then - LATEST_IMAGE_NAME=$REPO_NAME:latest - docker tag "${IMAGE_NAME}" "${LATEST_IMAGE_NAME}" -else - LATEST_IMAGE_NAME=$REPO_NAME:$BRANCH-latest - docker tag "${IMAGE_NAME}" "${LATEST_IMAGE_NAME}" -fi - -echo "=========================================================================================" -echo "Built $IMAGE_NAME" diff --git a/scripts/calculate_version.sh b/scripts/calculate_version.sh deleted file mode 100644 index 98ba49d..0000000 --- a/scripts/calculate_version.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -BRANCH=$1 -VERSION=$2 - -if [ -z "$BRANCH" ] -then - echo "A branch is not set." - exit 1 -fi - -if [ -z "$VERSION" ] -then - echo "The base version is not set." - exit 1 -fi - -git fetch --tags - -if [ "$BRANCH" = "master" ] -then - echo "$VERSION" - exit 0 -fi - -LABEL="develop" -if [ "$BRANCH" = "stable" ] -then - LABEL="stable" -elif [ "$BRANCH" = "beta" ] -then - LABEL="beta" -fi - -for (( VERSION_NUMBER=0; ; VERSION_NUMBER++ )) -do - RESULT_VERSION="$VERSION-$LABEL.$VERSION_NUMBER" - if ! [[ $(git tag -l | grep $RESULT_VERSION) ]]; then - echo "$RESULT_VERSION" | tr / - - break - fi -done diff --git a/cert_gen.sh b/scripts/cert_gen.sh similarity index 100% rename from cert_gen.sh rename to scripts/cert_gen.sh diff --git a/scripts/docker_build.py b/scripts/docker_build.py deleted file mode 100644 index 7bfeaea..0000000 --- a/scripts/docker_build.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -# Copyright (C) 2019-Present SKALE Labs -# -# This file is part of sgxwallet. -# -# sgxwallet 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. -# -# sgxwallet 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 sgxwallet. If not, see . -# -# @file docker_build.py -# @author Stan Kladko -# @date 2020 -# - -import sys, os, subprocess, time - -os.chdir("..") -topDir = os.getcwd() + "/skale-proxy" -DOCKER_FILE_NAME = sys.argv[1] -IMAGE_NAME = sys.argv[2] -COMMIT_HASH = sys.argv[3] - -FULL_IMAGE_TAG = "skalenetwork/" + IMAGE_NAME + ":" + COMMIT_HASH - -print("Starting build", flush=True) - -assert subprocess.call(["pwd"]) == 0 - -assert subprocess.call(["docker", "build", topDir, "--file", topDir + "/" + DOCKER_FILE_NAME, "--tag", - FULL_IMAGE_TAG]) == 0 - -assert subprocess.call(["docker", "push", FULL_IMAGE_TAG]) == 0 diff --git a/scripts/publish_image.sh b/scripts/publish_image.sh deleted file mode 100644 index 6705409..0000000 --- a/scripts/publish_image.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -x - -CONTAINER_NAME=$1 - -: "${VERSION?Need to set VERSION}" -: "${BRANCH?Need to set BRANCH}" - -REPO_NAME=skalenetwork/$CONTAINER_NAME -IMAGE_NAME=$REPO_NAME:$VERSION - -LATEST_IMAGE_NAME=$REPO_NAME:$BRANCH-latest -docker tag "${IMAGE_NAME}" "${LATEST_IMAGE_NAME}" - -: "${DOCKER_USERNAME?Need to set DOCKER_USERNAME}" -: "${DOCKER_PASSWORD?Need to set DOCKER_PASSWORD}" - -echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin - -docker push "$IMAGE_NAME" || exit $? -docker push "$LATEST_IMAGE_NAME" || exit $? From 95bdae79852cef673f0afd01f3b51d11aef77b0c Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 1 Feb 2022 19:33:48 +0200 Subject: [PATCH 03/27] SKALE-4289 Add nginx templates processing, add chain info class, add logs, etc --- .gitignore | 4 +- Dockerfile | 2 +- config/nginx.conf | 5 +- docker-compose.yml | 8 +- periodic_config_update.py | 229 -------------------------------------- proxy/config.py | 18 ++- proxy/endpoints.py | 133 ++++++++++++---------- proxy/helper.py | 33 +++++- proxy/main.py | 25 +++-- proxy/nginx.py | 33 +++++- proxy/node_info.py | 72 ++++++++++++ proxy/str_formatters.py | 53 +++++++++ requirements.txt | 2 + scripts/cert_gen.sh | 3 +- sites-available/.keep | 0 templates/chain.conf.j2 | 63 +++++++++++ 16 files changed, 373 insertions(+), 310 deletions(-) delete mode 100644 periodic_config_update.py create mode 100644 proxy/node_info.py create mode 100644 proxy/str_formatters.py create mode 100644 sites-available/.keep create mode 100644 templates/chain.conf.j2 diff --git a/.gitignore b/.gitignore index 4fff1e6..93ca213 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ venv __pycache__ abi.json -chains.json \ No newline at end of file +chains.json + +sites-available/*.conf \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 6551341..24b6fcc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-buster +FROM python:3.9-buster RUN apt-get update diff --git a/config/nginx.conf b/config/nginx.conf index f7b2eb0..9b91443 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -3,6 +3,7 @@ events { } http { + # server { # listen 443 ssl; # ssl_certificate /config/server.crt; @@ -24,7 +25,9 @@ http { server { listen 80; - server_name dappnet-api.skalenodes.com; + server_name localhost; + + include /etc/nginx/sites-available/*; # location / { # proxy_http_version 1.1; # proxy_pass http://proxy-ui:5000/; diff --git a/docker-compose.yml b/docker-compose.yml index a440c3c..d3fc83b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,14 +9,16 @@ services: # - "5000:5000" skale-proxy: environment: + SERVER_NAME: ${SERVER_NAME} ETH_ENDPOINT: ${ETH_ENDPOINT} image: skale-proxy:latest build: context: . dockerfile: Dockerfile volumes: - - ./data:/data - - ./www:/www + - ./data:/usr/src/proxy/data + - ./www:/usr/src/proxy/www + - ./sites-available:/usr/src/proxy/sites-available logging: driver: json-file options: @@ -30,5 +32,7 @@ services: - "80:80" - "443:443" volumes: + - ./data:/data - ./www:/usr/share/nginx/www/files - ./config/nginx.conf:/etc/nginx/nginx.conf:ro + - ./sites-available:/etc/nginx/sites-available/ diff --git a/periodic_config_update.py b/periodic_config_update.py deleted file mode 100644 index 9cc8aff..0000000 --- a/periodic_config_update.py +++ /dev/null @@ -1,229 +0,0 @@ -import time -import json -import filecmp -import os -import sys -import os.path -import subprocess -from os import path -from shutil import copyfile - -CONFIG_FILE = "/etc/nginx/sites-available/default" -TMP_CONFIG_FILE = "/tmp/tmp.config" -CERT_FILE = "/data/server.crt" -KEY_FILE = "/data/server.key" -RESULTS_PATH = "/tmp/chains.json" -PROXY_FULL_HOST_NAME = os.environ.get("PROXY_FULL_HOST_NAME") - -if PROXY_FULL_HOST_NAME is None: - print("Fatal error: PROXY_FULL_HOST_NAME is not set. Exiting ...") - exit(-2) - -ENDPPOINT_PREFIX = os.environ.get("ENDPOINT_PREFIX") - -if ENDPPOINT_PREFIX is None: - print("Fatal error: ENDPOINT_PREFIX is not set. Exiting ...") - exit(-3) - - -ABI_FILENAME = os.environ.get("ABI_FILENAME") - -if ABI_FILENAME is None: - print("Fatal error: ABI_FILENAME is not set. Exiting ...") - exit(-4) - -if not path.exists(CERT_FILE): - print("Fatal error: could not find:" + CERT_FILE + " Exiting.") - exit(-5) - -if not path.exists(KEY_FILE): - print("Fatal error: could not find:" + KEY_FILE + " Exiting.") - exit(-6) - - -def parse_chains(_network: str, _path: str) -> list: - json_file = open(_path,) - parsed_json = json.load(json_file) - - chain_infos = list() - - for schain in parsed_json: - name = schain["schain"][0] - print("Schain name = ", name) - nodes = schain["nodes"] - - list_of_http_endpoints = list() - list_of_https_endpoints = list() - list_of_ws_endpoints = list() - list_of_wss_endpoints = list() - list_of_domains = list() - - for node in nodes: - endpoint_http = node["http_endpoint_domain"] - print("Http endpoint: " + endpoint_http) - list_of_http_endpoints.append(endpoint_http) - endpoint_https = node["https_endpoint_domain"] - print(endpoint_https) - list_of_https_endpoints.append(endpoint_https) - endpoint_ws = node["ws_endpoint_domain"] - endpoint_wss = node["wss_endpoint_domain"] - list_of_ws_endpoints.append(endpoint_ws) - list_of_wss_endpoints.append(endpoint_wss) - list_of_domains.append(node['domain']) - - chain_infos.append(ChainInfo(_network, name, list_of_http_endpoints, - list_of_https_endpoints, list_of_ws_endpoints, - list_of_wss_endpoints, list_of_domains)) - return chain_infos - - -class ChainInfo: - def __init__(self, _network: str, _chain_name: str, _list_of_http_endpoints: list, - _list_of_https_endpoints: list, _list_of_ws_endpoints: list, - _list_of_wss_endpoints: list, _list_of_domains: list): - self.network = _network - self.chain_name = _chain_name - self.list_of_http_endpoints = _list_of_http_endpoints - self.list_of_https_endpoints = _list_of_https_endpoints - self.list_of_ws_endpoints = _list_of_ws_endpoints - self.list_of_wss_endpoints = _list_of_wss_endpoints - self.list_of_domains = _list_of_domains - - -def run(_command) -> None: - print(">" + _command) - subprocess.check_call(_command, shell=True) - - -def print_global_server_config(_f, _use_ssl: bool) -> None: - _f.write("server {\n") - if _use_ssl: - _f.write(" listen 443 ssl;\n") - _f.write(" ssl_certificate " + CERT_FILE + ";\n") - _f.write(" ssl_certificate_key " + KEY_FILE + ";\n") - _f.write(" ssl_verify_client off;\n") - else: - _f.write(" listen 80;\n") - # _f.write(" root /usr/share/nginx/www;\n") - # _f.write(" index index.php index.html index.htm;\n") - _f.write(" server_name " + PROXY_FULL_HOST_NAME + ";\n") - - _f.write(" location / {\n") - _f.write(" proxy_http_version 1.1;\n") - _f.write(" proxy_pass http://proxy-ui:5000/;\n") - _f.write(" proxy_set_header Upgrade $http_upgrade;\n") - _f.write(" proxy_set_header Connection 'upgrade';\n") - _f.write(" proxy_set_header Host $host;\n") - _f.write(" proxy_cache_bypass $http_upgrade;\n") - _f.write(" }\n") - - -def print_group_definition(_chain_info: ChainInfo, _f) -> None: - _f.write("upstream " + _chain_info.chain_name + " {\n") - _f.write(" ip_hash;\n") - for endpoint in _chain_info.list_of_http_endpoints: - _f.write(" server " + endpoint[7:] + " max_fails=1 fail_timeout=600s;\n") - _f.write("}\n") - - -def print_ws_group_definition(_chain_info: ChainInfo, _f) -> None: - _f.write("upstream ws-" + _chain_info.chain_name + " {\n") - _f.write(" ip_hash;\n") - for endpoint in _chain_info.list_of_ws_endpoints: - _f.write(" server " + endpoint[5:] + " max_fails=1 fail_timeout=600s;\n") - _f.write("}\n") - - -def print_storage_group_definition(_chain_info: ChainInfo, _f) -> None: - _f.write("upstream storage-" + _chain_info.chain_name + " {\n") - _f.write(" ip_hash;\n") - for domain in _chain_info.list_of_domains: - _f.write(" server " + domain + " max_fails=1 fail_timeout=600s;\n") - _f.write("}\n") - - -def print_loadbalacing_config_for_chain(_chain_info: ChainInfo, _f) -> None: - _f.write(" location /v1/" + _chain_info.chain_name + " {\n") - _f.write(" proxy_http_version 1.1;\n") - _f.write(" proxy_pass http://" + _chain_info.chain_name + "/;\n") - _f.write(" }\n") - - -def print_ws_config_for_chain(_chain_info: ChainInfo, _f) -> None: - _f.write(" location /v1/ws/" + _chain_info.chain_name + " {\n") - _f.write(" proxy_http_version 1.1;\n") - _f.write(" proxy_set_header Upgrade $http_upgrade;\n") - _f.write(" proxy_set_header Connection \"upgrade\";\n") - _f.write(" proxy_pass http://ws-" + _chain_info.chain_name + "/;\n") - _f.write(" }\n") - - -def print_storage_proxy_for_chain(_chain_info: ChainInfo, _f) -> None: - _f.write(" location /fs/" + _chain_info.chain_name + " {\n") - _f.write(" rewrite /fs/" + _chain_info.chain_name + "/(.*) /" + _chain_info.chain_name + "/$1 break;\n") - _f.write(" proxy_http_version 1.1;\n") - _f.write(" proxy_pass http://storage-" + _chain_info.chain_name + "/;\n") - _f.write(" }\n") - - -def print_config_file(_chain_infos: list) -> None: - if os.path.exists(TMP_CONFIG_FILE): - os.remove(TMP_CONFIG_FILE) - with open(TMP_CONFIG_FILE, 'w') as f: - for chain_info in _chain_infos: - print_group_definition(chain_info, f) - print_ws_group_definition(chain_info, f) - print_storage_group_definition(chain_info, f) - print_global_server_config(f, False) - for chain_info in _chain_infos: - print_loadbalacing_config_for_chain(chain_info, f) - print_ws_config_for_chain(chain_info, f) - print_storage_proxy_for_chain(chain_info, f) - f.write("}\n") - print_global_server_config(f, True) - for chain_info in _chain_infos: - print_loadbalacing_config_for_chain(chain_info, f) - print_ws_config_for_chain(chain_info, f) - print_storage_proxy_for_chain(chain_info, f) - f.write("}\n") - f.close() - - -def copy_config_file_if_modified() -> None: - if (not path.exists(CONFIG_FILE)) or (not filecmp.cmp(CONFIG_FILE, TMP_CONFIG_FILE, shallow=False)): - print("New config file. Reloading server") - os.remove(CONFIG_FILE) - copyfile(TMP_CONFIG_FILE, CONFIG_FILE) - copyfile(TMP_CONFIG_FILE, CONFIG_FILE) - run("/usr/sbin/nginx -s reload") - - -def main(): - while True: - print("Updating chain info ...") - subprocess.check_call(["/bin/bash", "-c", "rm -f /tmp/*"]) - subprocess.check_call(["/bin/bash", "-c", - "cp /etc/abi.json /tmp/abi.json"]) - subprocess.check_call(["python3", "/etc/endpoints.py"]) - subprocess.check_call(["/bin/bash", "-c", "mkdir -p /usr/share/nginx/www"]) - subprocess.check_call(["/bin/bash", "-c", "cp -f /tmp/chains.json /usr/share/nginx/www/chains.json"]) - subprocess.check_call(["/bin/bash", "-c", "cp -f /etc/VERSION /usr/share/nginx/www/VERSION.txt"]) - - if not os.path.exists(RESULTS_PATH): - print("Fatal error: Chains file does not exist. Exiting ...") - exit(-4) - - print("Generating config file ...") - - chain_infos = parse_chains(ENDPPOINT_PREFIX, RESULTS_PATH) - - print("Checking Config file ") - print_config_file(chain_infos) - copy_config_file_if_modified() - print("monitor loop iteration") - sys.stdout.flush() - time.sleep(6000) - - -# run main -main() diff --git a/proxy/config.py b/proxy/config.py index 9a1d306..8827688 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -19,14 +19,28 @@ import os +DIR_PATH = os.path.dirname(os.path.realpath(__file__)) +PROJECT_PATH = os.path.join(DIR_PATH, os.pardir) + ENDPOINT = os.environ['ETH_ENDPOINT'] PORTS_PER_SCHAIN = 64 MONITOR_INTERVAL = os.getenv('MONITOR_INTERVAL', 60 * 60 * 2) -NGINX_WWW_FOLDER = '/www' +NGINX_WWW_FOLDER = os.path.join(PROJECT_PATH, 'www') CHAINS_INFO_FILEPATH = os.path.join(NGINX_WWW_FOLDER, 'chains.json') -DATA_FOLDER = '/data' +DATA_FOLDER = os.path.join(PROJECT_PATH, 'data') + SM_ABI_DEFAULT_FILEPATH = os.path.join(DATA_FOLDER, 'abi.json') SM_ABI_FILEPATH = os.getenv('SM_ABI_FILEPATH', SM_ABI_DEFAULT_FILEPATH) + +TEMPLATES_FOLDER = os.path.join(PROJECT_PATH, 'templates') +SCHAIN_NGINX_TEMPLATE = os.path.join(TEMPLATES_FOLDER, 'chain.conf.j2') + +SITES_AVAILABLE_FOLDER = os.path.join(PROJECT_PATH, 'sites-available') + +SERVER_NAME = os.environ['SERVER_NAME'] + +PROXY_LOG_FORMAT = '[%(asctime)s] %(process)d %(levelname)s %(module)s: %(message)s' +LONG_LINE = '=' * 100 diff --git a/proxy/endpoints.py b/proxy/endpoints.py index c25c13a..c49439f 100644 --- a/proxy/endpoints.py +++ b/proxy/endpoints.py @@ -18,13 +18,18 @@ # along with this program. If not, see . import json +import logging from web3 import Web3, HTTPProvider from Crypto.Hash import keccak -from proxy.skaled_ports import SkaledPorts -from proxy.helper import ip_from_bytes, read_json, write_json -from proxy.config import PORTS_PER_SCHAIN, ENDPOINT, SM_ABI_FILEPATH +from proxy.node_info import get_node_info +from proxy.helper import read_json +from proxy.config import ENDPOINT, SM_ABI_FILEPATH +from proxy.str_formatters import arguments_list_string + + +logger = logging.getLogger(__name__) URL_PREFIXES = { @@ -36,39 +41,52 @@ } +class ChainInfo: + def __init__(self, schain_name: str, nodes: list): + self.schain_name = schain_name + self.chain_id = schain_name_to_network_id(schain_name) + self.http_endpoints = [] + # self.http_endpoints_ip, self.https_endpoints = [], [] + self.ws_endpoints = [] + # self.ws_endpoints_ip, self.wss_endpoints = [], [] + self.fs_endpoints = [] + self._format_nodes(nodes) + + def _format_nodes(self, nodes): + for node in nodes: + self.http_endpoints.append( + node['http_endpoint_domain'].removeprefix(URL_PREFIXES['http']) + ) + #self.http_endpoints_ip.append(node['http_endpoint_ip'].removeprefix(URL_PREFIXES['http'])) + #self.https_endpoints.append(node['https_endpoint_domain'].removeprefix(URL_PREFIXES['https'])) + + self.ws_endpoints.append(node['ws_endpoint_domain'].removeprefix(URL_PREFIXES['ws'])) + #self.ws_endpoints_ip.append(node['ws_endpoint_ip'].removeprefix(URL_PREFIXES['ws'])) + #self.wss_endpoints.append(node['wss_endpoint_domain'].removeprefix(URL_PREFIXES['wss'])) + + self.fs_endpoints.append(node['domain']) + + def to_dict(self): + return { + 'schain_name': self.schain_name, + 'chain_id': self.chain_id, + 'http_endpoints': self.http_endpoints, + # 'http_endpoints_ip': self.http_endpoints_ip, + # 'https_endpoints': self.https_endpoints, + 'ws_endpoints': self.ws_endpoints, + # 'ws_endpoints_ip': self.ws_endpoints_ip, + # 'wss_endpoints': self.wss_endpoints, + 'fs_endpoints': self.fs_endpoints + } + + def schain_name_to_id(name: str) -> str: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) return '0x' + keccak_hash.hexdigest() -def schain_name_to_network_id(name: str) -> str: - schain_name_to_id(name) - - -def get_schain_index_in_node(schain_id, schains_ids_on_node): - for index, schain_id_on_node in enumerate(schains_ids_on_node): - if schain_id == schain_id_on_node: - return index - raise Exception(f'sChain {schain_id} is not found in the list: {schains_ids_on_node}') - - -def get_schain_base_port_on_node(schain_id, schains_ids_on_node, node_base_port): - schain_index = get_schain_index_in_node(schain_id, schains_ids_on_node) - return calc_schain_base_port(node_base_port, schain_index) - - -def calc_schain_base_port(node_base_port, schain_index): - return node_base_port + schain_index * PORTS_PER_SCHAIN - - -def calc_ports(schain_base_port): - return { - 'httpRpcPort': schain_base_port + SkaledPorts.HTTP_JSON.value, - 'httpsRpcPort': schain_base_port + SkaledPorts.HTTPS_JSON.value, - 'wsRpcPort': schain_base_port + SkaledPorts.WS_JSON.value, - 'wssRpcPort': schain_base_port + SkaledPorts.WSS_JSON.value, - 'infoHttpRpcPort': schain_base_port + SkaledPorts.INFO_HTTP_JSON.value - } +def schain_name_to_network_id(raw_schain_struct: list) -> str: + return schain_name_to_id(raw_schain_struct[0])[:15] def _compose_endpoints(node_dict, endpoint_type): @@ -79,35 +97,27 @@ def _compose_endpoints(node_dict, endpoint_type): node_dict[key_name] = f'{prefix}{node_dict[endpoint_type]}:{port}' -def endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id): +def generate_endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id): """Generates endpoints list for a given SKALE chain""" + schain = schains_internal_contract.functions.schains(schain_id).call() + logger.info(f'Going to generate endpoints for sChain: {schain[0]}') + node_ids = schains_internal_contract.functions.getNodesInGroup(schain_id).call() nodes = [] for node_id in node_ids: - node = nodes_contract.functions.nodes(node_id).call() - node_dict = { - 'id': node_id, - 'name': node[0], - 'ip': ip_from_bytes(node[1]), - 'base_port': node[3], - 'domain': nodes_contract.functions.getNodeDomainName(node_id).call() - } - schain_ids = schains_internal_contract.functions.getSchainIdsForNode(node_id).call() - node_dict['schain_base_port'] = get_schain_base_port_on_node( - schain_id, schain_ids, node_dict['base_port'] + node = get_node_info( + schain_id=schain_id, + node_id=node_id, + nodes_contract=nodes_contract, + schains_internal_contract=schains_internal_contract ) - node_dict.update(calc_ports(node_dict['schain_base_port'])) - - _compose_endpoints(node_dict, endpoint_type='ip') - _compose_endpoints(node_dict, endpoint_type='domain') - - nodes.append(node_dict) - - schain = schains_internal_contract.functions.schains(schain_id).call() + _compose_endpoints(node, endpoint_type='ip') + _compose_endpoints(node, endpoint_type='domain') + nodes.append(node) return { 'schain': schain, - 'schain_id': schain_name_to_id(schain[0])[:15], - 'nodes': nodes + 'nodes': nodes, + 'chain_info': ChainInfo(schain[0], nodes).to_dict() } @@ -123,7 +133,7 @@ def init_contracts(web3: Web3, sm_abi: str): return schains_internal_contract, nodes_contract -def endpoints_for_all_schains(endpoint: str, abi_filepath: str) -> list: +def generate_endpoints(endpoint: str, abi_filepath: str) -> list: """Main function that generates endpoints for all SKALE Chains on the given network""" provider = HTTPProvider(endpoint) web3 = Web3(provider) @@ -134,18 +144,23 @@ def endpoints_for_all_schains(endpoint: str, abi_filepath: str) -> list: sm_abi=sm_abi ) + logger.info(arguments_list_string({ + 'nodes': nodes_contract.address, + 'schains_internal': schains_internal_contract.address + }, 'Contracts inited')) + schain_ids = schains_internal_contract.functions.getSchains().call() - schain_ids = [schain_ids[0]] # TODO: TMP! + # schain_ids = [schain_ids[0]] # TODO: TMP! + logger.info(f'Number of sChains: {len(schain_ids)}') endpoints = [ - endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id) + generate_endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id) for schain_id in schain_ids ] return endpoints if __name__ == '__main__': - endpoints = endpoints_for_all_schains(ENDPOINT, SM_ABI_FILEPATH) - print(json.dumps(endpoints, indent=4)) - # write_json(RESULTS_PATH, endpoints) + schains_endpoints = generate_endpoints(ENDPOINT, SM_ABI_FILEPATH) + print(json.dumps(schains_endpoints, indent=4)) diff --git a/proxy/helper.py b/proxy/helper.py index 3c07642..2dbfc28 100644 --- a/proxy/helper.py +++ b/proxy/helper.py @@ -17,10 +17,15 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . - +import sys import json import socket +import logging +from logging import Formatter, StreamHandler + +from jinja2 import Environment +from proxy.config import PROXY_LOG_FORMAT def read_json(path, mode='r'): @@ -35,3 +40,29 @@ def write_json(path, content): def ip_from_bytes(bytes): return socket.inet_ntoa(bytes) + + +def process_template(source, destination, data): + """ + :param source: j2 template source path + :param destination: out file path + :param data: dictionary with fields for template + :return: Nothing + """ + template = None + with open(source) as template_file: + template = template_file.read() + processed_template = Environment().from_string(template).render(data) + with open(destination, "w") as f: + f.write(processed_template) + + +def init_default_logger(): + handlers = [] + formatter = Formatter(PROXY_LOG_FORMAT) + stream_handler = StreamHandler(sys.stderr) + stream_handler.setFormatter(formatter) + stream_handler.setLevel(logging.INFO) + handlers.append(stream_handler) + + logging.basicConfig(level=logging.DEBUG, handlers=handlers) \ No newline at end of file diff --git a/proxy/main.py b/proxy/main.py index 5e6a0b3..63339c4 100644 --- a/proxy/main.py +++ b/proxy/main.py @@ -21,24 +21,29 @@ from time import sleep from proxy.nginx import generate_nginx_configs -from proxy.endpoints import endpoints_for_all_schains -from proxy.helper import write_json -from proxy.config import CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH +from proxy.endpoints import generate_endpoints +from proxy.helper import init_default_logger, write_json +from proxy.str_formatters import arguments_list_string +from proxy.config import ( + CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH, SERVER_NAME +) logger = logging.getLogger(__name__) def main(): - logger.info('Starting SKALE Proxy server') + init_default_logger() + logger.info(arguments_list_string({ + 'Endpoint': ENDPOINT, + 'Server name': SERVER_NAME + }, 'Starting SKALE Proxy server')) while True: logger.info('Collecting endpoints list') - endpoints = endpoints_for_all_schains(ENDPOINT, SM_ABI_FILEPATH) - write_json(CHAINS_INFO_FILEPATH, endpoints) - - generate_nginx_configs(endpoints) - - logger.info(f'Proxy iteration done, sleeping for {MONITOR_INTERVAL}') + schains_endpoints = generate_endpoints(ENDPOINT, SM_ABI_FILEPATH) + write_json(CHAINS_INFO_FILEPATH, schains_endpoints) + generate_nginx_configs(schains_endpoints) + logger.info(f'Proxy iteration done, sleeping for {MONITOR_INTERVAL}s...') sleep(MONITOR_INTERVAL) diff --git a/proxy/nginx.py b/proxy/nginx.py index 3fa2b59..79206f9 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -17,6 +17,35 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import os +import logging -def generate_nginx_configs(endpoints: list) -> None: - pass +from proxy.helper import process_template +from proxy.config import SCHAIN_NGINX_TEMPLATE, SITES_AVAILABLE_FOLDER, SERVER_NAME + + +logger = logging.getLogger(__name__) + + +def generate_nginx_configs(schains_endpoints: list) -> None: + logger.info('Generating nginx configs...') + for schain_endpoints in schains_endpoints: + logger.info(f'Processing template for {schain_endpoints["chain_info"]["schain_name"]}...') + process_nginx_config_template(schain_endpoints['chain_info'], SERVER_NAME) + + +def process_nginx_config_template(chain_info: dict, server_name: str) -> None: + dest = os.path.join(SITES_AVAILABLE_FOLDER, f'{chain_info["schain_name"]}.conf') + chain_info['server_name'] = server_name + process_template(SCHAIN_NGINX_TEMPLATE, dest, chain_info) + + +if __name__ == '__main__': + chain_data = {'schain_name': 'test'} + process_nginx_config_template({ + 'server_name': 'test.com', + 'schain_name': 'test', + 'http_endpoints': ['ssss.com:15555', 'aaaa', 'bbbbb'], + 'ws_endpoints': ['fgdfgdf', 'dsgfs', 'asaffd'], + 'fs_endpoints': ['bvf', 'g', 'hh'], + }) diff --git a/proxy/node_info.py b/proxy/node_info.py new file mode 100644 index 0000000..121e97e --- /dev/null +++ b/proxy/node_info.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 web3.contract import Contract + +from proxy.skaled_ports import SkaledPorts +from proxy.config import PORTS_PER_SCHAIN +from proxy.helper import ip_from_bytes + + +def get_node_info( + schain_id: str, + node_id: int, + nodes_contract: Contract, + schains_internal_contract: Contract +) -> dict: + node = nodes_contract.functions.nodes(node_id).call() + node_dict = { + 'id': node_id, + 'name': node[0], + 'ip': ip_from_bytes(node[1]), + 'base_port': node[3], + 'domain': nodes_contract.functions.getNodeDomainName(node_id).call() + } + schain_ids = schains_internal_contract.functions.getSchainIdsForNode(node_id).call() + node_dict['schain_base_port'] = _get_schain_base_port_on_node( + schain_id, schain_ids, node_dict['base_port'] + ) + node_dict.update(_calc_ports(node_dict['schain_base_port'])) + return node_dict + + +def _get_schain_index_in_node(schain_id, schains_ids_on_node): + for index, schain_id_on_node in enumerate(schains_ids_on_node): + if schain_id == schain_id_on_node: + return index + raise Exception(f'sChain {schain_id} is not found in the list: {schains_ids_on_node}') + + +def _get_schain_base_port_on_node(schain_id, schains_ids_on_node, node_base_port): + schain_index = _get_schain_index_in_node(schain_id, schains_ids_on_node) + return _calc_schain_base_port(node_base_port, schain_index) + + +def _calc_schain_base_port(node_base_port, schain_index): + return node_base_port + schain_index * PORTS_PER_SCHAIN + + +def _calc_ports(schain_base_port): + return { + 'httpRpcPort': schain_base_port + SkaledPorts.HTTP_JSON.value, + 'httpsRpcPort': schain_base_port + SkaledPorts.HTTPS_JSON.value, + 'wsRpcPort': schain_base_port + SkaledPorts.WS_JSON.value, + 'wssRpcPort': schain_base_port + SkaledPorts.WSS_JSON.value, + 'infoHttpRpcPort': schain_base_port + SkaledPorts.INFO_HTTP_JSON.value + } diff --git a/proxy/str_formatters.py b/proxy/str_formatters.py new file mode 100644 index 0000000..717d118 --- /dev/null +++ b/proxy/str_formatters.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE Proxy +# +# Copyright (C) 2022-Present 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 +import colorful as cf + +from proxy.config import LONG_LINE + +DISABLE_COLORS = os.environ.get('DISABLE_COLORS', None) + +cf.use_style('solarized') + +PALETTE = { + 'success': '#00c853', + 'info': '#1976d2', + 'error': '#d50000' +} + + +def arguments_list_string(args, title=None, type='info'): + s = f'\n{LONG_LINE}\n' if DISABLE_COLORS else cf.blue(f'\n{LONG_LINE}\n') + if title: + if DISABLE_COLORS: + s += f'{title}\n' + else: + with cf.with_palette(PALETTE) as c: + if type == 'error': + s += f'{c.bold_error(title)}\n' + elif type == 'success': + s += f'{c.bold_success(title)}\n' + else: + s += f'{c.bold_info(title)}\n' + for k in args: + s += f'{k}: ' if DISABLE_COLORS else f'{cf.bold_violet(k)}: ' + s += f'{args[k]}\n' + s += f'{LONG_LINE}\n' if DISABLE_COLORS else cf.blue(f'{LONG_LINE}\n') + return s diff --git a/requirements.txt b/requirements.txt index 9134548..c930523 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ web3==5.26.0 +Jinja2==3.0.3 +colorful==0.5.4 \ No newline at end of file diff --git a/scripts/cert_gen.sh b/scripts/cert_gen.sh index b68e99c..9b59269 100755 --- a/scripts/cert_gen.sh +++ b/scripts/cert_gen.sh @@ -1,4 +1,3 @@ #!/bin/bash -sudo rm -rf data -sudo mkdir data + openssl req -newkey rsa:2048 -nodes -keyout ./data/server.key -x509 -days 365 -out ./data/server.crt diff --git a/sites-available/.keep b/sites-available/.keep new file mode 100644 index 0000000..e69de29 diff --git a/templates/chain.conf.j2 b/templates/chain.conf.j2 new file mode 100644 index 0000000..63f7fdd --- /dev/null +++ b/templates/chain.conf.j2 @@ -0,0 +1,63 @@ +server { + listen 80; + server_name {{ server_name }}; + + location /v1/{{ schain_name }} { + proxy_http_version 1.1; + proxy_pass http://{{ schain_name }}/; + } + location /v1/ws/{{ schain_name }} { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_pass http://ws-{{ schain_name }}/; + } + location /fs/{{ schain_name }} { + rewrite /fs/{{ schain_name }}/(.*) /{{ schain_name }}/$1 break; + proxy_http_version 1.1; + proxy_pass http://storage-{{ schain_name }}/; + } +} + +server { + listen 443 ssl; + server_name {{ server_name }}; + ssl_certificate /data/server.crt; + ssl_certificate_key /data/server.key; + ssl_verify_client off; + + location /v1/{{ schain_name }} { + proxy_http_version 1.1; + proxy_pass http://{{ schain_name }}/; + } + location /v1/ws/{{ schain_name }} { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_pass http://ws-{{ schain_name }}/; + } + location /fs/{{ schain_name }} { + rewrite /fs/{{ schain_name }}/(.*) /{{ schain_name }}/$1 break; + proxy_http_version 1.1; + proxy_pass http://storage-{{ schain_name }}/; + } +} + +upstream {{ schain_name }} { + ip_hash; + {% for endpoint in http_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} +upstream ws-{{ schain_name }} { + ip_hash; + {% for endpoint in ws_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} +upstream storage-{{ schain_name }} { + ip_hash; + {% for endpoint in fs_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} From 77d0a8fffa455ce94bc8f5654c489ad867f20c44 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 17 Feb 2022 20:04:15 +0000 Subject: [PATCH 04/27] SKALE-4289 Finalize proxy v2 - basic functionality done --- .gitignore | 3 +- README.md | 10 ++++ {sites-available => conf/chains}/.keep | 0 conf/upstreams/.keep | 0 config/nginx.conf | 66 +++++++++++++++----------- docker-compose.yml | 22 +++++---- proxy/config.py | 5 +- proxy/nginx.py | 10 ++-- start.sh | 14 ------ templates/chain.conf.j2 | 54 ++------------------- templates/upstream.conf.j2 | 18 +++++++ 11 files changed, 94 insertions(+), 108 deletions(-) rename {sites-available => conf/chains}/.keep (100%) create mode 100644 conf/upstreams/.keep delete mode 100755 start.sh create mode 100644 templates/upstream.conf.j2 diff --git a/.gitignore b/.gitignore index 93ca213..c706308 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ __pycache__ abi.json chains.json -sites-available/*.conf \ No newline at end of file +conf/upstreams/*.conf +conf/chains/*.conf \ No newline at end of file diff --git a/README.md b/README.md index b1bb189..6b670b7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,16 @@ JSON-RPC endpoints for SKALE chains. It is based on NGINX. - Docker - docker-compose + +### Repo setup + +1. Clone all submodules +2. Put `abi.json` file in `data` folder +3. Put `server.crt` and `server.key` files in `data` folder +4. Configure proxy-ui environement params +5. Export `ENDPOINT` environement param +5. Run ` docker-compose up --build` + 1. Place you ABI json file into abi directory 2. Set 'ABI_FILENAME directory' in docker-compose.yml to the name of the ABI file. diff --git a/sites-available/.keep b/conf/chains/.keep similarity index 100% rename from sites-available/.keep rename to conf/chains/.keep diff --git a/conf/upstreams/.keep b/conf/upstreams/.keep new file mode 100644 index 0000000..e69de29 diff --git a/config/nginx.conf b/config/nginx.conf index 9b91443..da65826 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -4,40 +4,48 @@ events { http { - # server { - # listen 443 ssl; - # ssl_certificate /config/server.crt; - # ssl_certificate_key /config/server.key; - # ssl_verify_client off; - # server_name dappnet-api.skalenodes.com; - # # location / { - # # proxy_http_version 1.1; - # # proxy_pass http://proxy-ui:5000/; - # # proxy_set_header Upgrade $http_upgrade; - # # proxy_set_header Connection 'upgrade'; - # # proxy_set_header Host $host; - # # proxy_cache_bypass $http_upgrade; - # # } - # location /files/ { - # root /usr/share/nginx/www; - # } - # } + server { + listen 443 ssl; + ssl_certificate /data/server.crt; + ssl_certificate_key /data/server.key; + ssl_verify_client off; + server_name _; + + location / { + proxy_http_version 1.1; + proxy_pass http://proxy-ui:5001/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location /files/ { + root /usr/share/nginx/www; + } + + include /etc/nginx/conf/chains/*.conf; + } server { listen 80; - server_name localhost; - - include /etc/nginx/sites-available/*; - # location / { - # proxy_http_version 1.1; - # proxy_pass http://proxy-ui:5000/; - # proxy_set_header Upgrade $http_upgrade; - # proxy_set_header Connection 'upgrade'; - # proxy_set_header Host $host; - # proxy_cache_bypass $http_upgrade; - # } + server_name _; + + location / { + proxy_http_version 1.1; + proxy_pass http://proxy-ui:5001/; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + location /files/ { root /usr/share/nginx/www; } + + include /etc/nginx/conf/chains/*.conf; } + + include /etc/nginx/conf/upstreams/*.conf; } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d3fc83b..e441e49 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,16 @@ version: '3' services: - # proxy-ui: - # image: proxy-ui:latest - # build: - # context: ./proxy-ui - # dockerfile: Dockerfile - # ports: - # - "5000:5000" + proxy-ui: + image: proxy-ui:latest + build: + context: ./proxy-ui + dockerfile: Dockerfile + args: + REACT_APP_NETWORKS: "{}" + ports: + - "5001:5001" + environment: + REACT_APP_NETWORKS: "{}" skale-proxy: environment: SERVER_NAME: ${SERVER_NAME} @@ -18,7 +22,7 @@ services: volumes: - ./data:/usr/src/proxy/data - ./www:/usr/src/proxy/www - - ./sites-available:/usr/src/proxy/sites-available + - ./conf:/usr/src/proxy/conf/ logging: driver: json-file options: @@ -35,4 +39,4 @@ services: - ./data:/data - ./www:/usr/share/nginx/www/files - ./config/nginx.conf:/etc/nginx/nginx.conf:ro - - ./sites-available:/etc/nginx/sites-available/ + - ./conf:/etc/nginx/conf/ diff --git a/proxy/config.py b/proxy/config.py index 8827688..c2cd7b5 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -36,9 +36,12 @@ SM_ABI_FILEPATH = os.getenv('SM_ABI_FILEPATH', SM_ABI_DEFAULT_FILEPATH) TEMPLATES_FOLDER = os.path.join(PROJECT_PATH, 'templates') + SCHAIN_NGINX_TEMPLATE = os.path.join(TEMPLATES_FOLDER, 'chain.conf.j2') +UPSTREAM_NGINX_TEMPLATE = os.path.join(TEMPLATES_FOLDER, 'upstream.conf.j2') -SITES_AVAILABLE_FOLDER = os.path.join(PROJECT_PATH, 'sites-available') +CHAINS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'chains') +UPSTREAMS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'upstreams') SERVER_NAME = os.environ['SERVER_NAME'] diff --git a/proxy/nginx.py b/proxy/nginx.py index 79206f9..c22a232 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -21,7 +21,9 @@ import logging from proxy.helper import process_template -from proxy.config import SCHAIN_NGINX_TEMPLATE, SITES_AVAILABLE_FOLDER, SERVER_NAME +from proxy.config import ( + SCHAIN_NGINX_TEMPLATE, UPSTREAM_NGINX_TEMPLATE, CHAINS_FOLDER, UPSTREAMS_FOLDER, SERVER_NAME +) logger = logging.getLogger(__name__) @@ -35,9 +37,11 @@ def generate_nginx_configs(schains_endpoints: list) -> None: def process_nginx_config_template(chain_info: dict, server_name: str) -> None: - dest = os.path.join(SITES_AVAILABLE_FOLDER, f'{chain_info["schain_name"]}.conf') + chain_dest = os.path.join(CHAINS_FOLDER, f'{chain_info["schain_name"]}.conf') + upstream_dest = os.path.join(UPSTREAMS_FOLDER, f'{chain_info["schain_name"]}.conf') chain_info['server_name'] = server_name - process_template(SCHAIN_NGINX_TEMPLATE, dest, chain_info) + process_template(SCHAIN_NGINX_TEMPLATE, chain_dest, chain_info) + process_template(UPSTREAM_NGINX_TEMPLATE, upstream_dest, chain_info) if __name__ == '__main__': diff --git a/start.sh b/start.sh deleted file mode 100755 index d390abe..0000000 --- a/start.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -set -e -set -x - - -python3 /etc/periodic_config_update.py & - -/usr/sbin/nginx - - - - -# start nginx - diff --git a/templates/chain.conf.j2 b/templates/chain.conf.j2 index 63f7fdd..571907e 100644 --- a/templates/chain.conf.j2 +++ b/templates/chain.conf.j2 @@ -1,63 +1,15 @@ -server { - listen 80; - server_name {{ server_name }}; - - location /v1/{{ schain_name }} { - proxy_http_version 1.1; - proxy_pass http://{{ schain_name }}/; - } - location /v1/ws/{{ schain_name }} { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_pass http://ws-{{ schain_name }}/; - } - location /fs/{{ schain_name }} { - rewrite /fs/{{ schain_name }}/(.*) /{{ schain_name }}/$1 break; - proxy_http_version 1.1; - proxy_pass http://storage-{{ schain_name }}/; - } -} - -server { - listen 443 ssl; - server_name {{ server_name }}; - ssl_certificate /data/server.crt; - ssl_certificate_key /data/server.key; - ssl_verify_client off; - - location /v1/{{ schain_name }} { +location /v1/{{ schain_name }} { proxy_http_version 1.1; proxy_pass http://{{ schain_name }}/; } - location /v1/ws/{{ schain_name }} { +location /v1/ws/{{ schain_name }} { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://ws-{{ schain_name }}/; } - location /fs/{{ schain_name }} { +location /fs/{{ schain_name }} { rewrite /fs/{{ schain_name }}/(.*) /{{ schain_name }}/$1 break; proxy_http_version 1.1; proxy_pass http://storage-{{ schain_name }}/; } -} - -upstream {{ schain_name }} { - ip_hash; - {% for endpoint in http_endpoints %} - server {{ endpoint }} max_fails=1 fail_timeout=600s; - {% endfor %} -} -upstream ws-{{ schain_name }} { - ip_hash; - {% for endpoint in ws_endpoints %} - server {{ endpoint }} max_fails=1 fail_timeout=600s; - {% endfor %} -} -upstream storage-{{ schain_name }} { - ip_hash; - {% for endpoint in fs_endpoints %} - server {{ endpoint }} max_fails=1 fail_timeout=600s; - {% endfor %} -} diff --git a/templates/upstream.conf.j2 b/templates/upstream.conf.j2 new file mode 100644 index 0000000..cde7893 --- /dev/null +++ b/templates/upstream.conf.j2 @@ -0,0 +1,18 @@ +upstream {{ schain_name }} { + ip_hash; + {% for endpoint in http_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} +upstream ws-{{ schain_name }} { + ip_hash; + {% for endpoint in ws_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} +upstream storage-{{ schain_name }} { + ip_hash; + {% for endpoint in fs_endpoints %} + server {{ endpoint }} max_fails=1 fail_timeout=600s; + {% endfor %} +} From 94b673e5e2912d56ce9423d4a5d633806a7662c4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 23 Feb 2022 16:03:48 +0000 Subject: [PATCH 05/27] SKALE-4289 Add hot reload for nginx, add run script --- .github/workflows/build_docker.yml | 49 ----------------------------- .github/workflows/publish.yml | 50 ++++++++++++++++++++++++++++++ docker-compose.yml | 9 +++--- proxy-ui | 2 +- proxy/config.py | 2 ++ proxy/main.py | 4 +-- proxy/nginx.py | 19 +++++++++++- requirements.txt | 4 ++- scripts/run_proxy.sh | 16 ++++++++++ 9 files changed, 96 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/build_docker.yml create mode 100644 .github/workflows/publish.yml create mode 100644 scripts/run_proxy.sh diff --git a/.github/workflows/build_docker.yml b/.github/workflows/build_docker.yml deleted file mode 100644 index 0df9789..0000000 --- a/.github/workflows/build_docker.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Build and push skale-proxy container -on: - workflow_dispatch: - push: -jobs: - build: - runs-on: ubuntu-18.04 - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - steps: - - name: Login to docker - run: docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} - - uses: actions/checkout@v1 - - name: submodule update - run: git submodule update --init --recursive - - name: build and deploy test image - run: python3 scripts/docker_build.py Dockerfile skale-proxy ${GITHUB_SHA} - - name: deploy docker image - if: | - contains(github.ref, 'develop') || contains(github.ref, 'beta') || - contains(github.ref, 'master') - run : | - export BRANCH=${GITHUB_REF##*/} - echo "Branch $BRANCH" - export VERSION=$(cat VERSION) - echo "Version $VERSION" - export VERSION=$(bash ./scripts/calculate_version.sh $BRANCH $VERSION) - echo "::set-env name=VERSION::$VERSION" - echo "Version $VERSION" - export RELEASE=true - echo "::set-env name=RELEASE::$RELEASE" - bash ./scripts/build_image.sh Dockerfile skale-proxy - bash ./scripts/publish_image.sh skale-proxy - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - - name: Create Release - if: contains(github.ref, 'develop') || contains(github.ref, 'beta') || contains(github.ref, 'master') - id: create_release - uses: actions/create-release@latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.VERSION }} - release_name: ${{ env.VERSION }} - draft: false - prerelease: true - diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a41d3e0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,50 @@ +name: Build and publish +on: + pull_request: + types: [closed] + branches: + - master + - develop + - beta + - stable + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.pull_request.merged + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Build and publish container + run: | + export BRANCH=${GITHUB_REF##*/} + export VERSION=$(cat VERSION) + echo "Branch: $BRANCH" + echo "Base version: $VERSION" + export VERSION=$(bash ./helper-scripts/calculate_version.sh) + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "Version $VERSION" + export RELEASE=true + echo "RELEASE=$RELEASE" >> $GITHUB_ENV + export CONTAINER_NAME="admin" + bash ./helper-scripts/build_and_publish.sh + - name: Checkout code + uses: actions/checkout@master + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.VERSION }} + release_name: ${{ env.VERSION }} + draft: false + prerelease: true diff --git a/docker-compose.yml b/docker-compose.yml index e441e49..1f4b1ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,20 +2,18 @@ version: '3' services: proxy-ui: image: proxy-ui:latest + container_name: proxy_ui build: context: ./proxy-ui dockerfile: Dockerfile - args: - REACT_APP_NETWORKS: "{}" ports: - "5001:5001" - environment: - REACT_APP_NETWORKS: "{}" skale-proxy: environment: SERVER_NAME: ${SERVER_NAME} ETH_ENDPOINT: ${ETH_ENDPOINT} image: skale-proxy:latest + container_name: proxy_admin build: context: . dockerfile: Dockerfile @@ -23,6 +21,7 @@ services: - ./data:/usr/src/proxy/data - ./www:/usr/src/proxy/www - ./conf:/usr/src/proxy/conf/ + - /var/run/docker.sock:/var/run/docker.sock logging: driver: json-file options: @@ -31,7 +30,7 @@ services: restart: unless-stopped nginx: image: nginx:1.20.2 - container_name: skale_nginx + container_name: proxy_nginx ports: - "80:80" - "443:443" diff --git a/proxy-ui b/proxy-ui index 6f6f093..fed1712 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 6f6f093f58fe4bb754fa9818cd7c75af5771959c +Subproject commit fed1712d5f38ea8e7d786d36fc32137ebe6ab9f2 diff --git a/proxy/config.py b/proxy/config.py index c2cd7b5..388f213 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -47,3 +47,5 @@ PROXY_LOG_FORMAT = '[%(asctime)s] %(process)d %(levelname)s %(module)s: %(message)s' LONG_LINE = '=' * 100 + +NGINX_CONTAINER_NAME = 'proxy_nginx' diff --git a/proxy/main.py b/proxy/main.py index 63339c4..cba949c 100644 --- a/proxy/main.py +++ b/proxy/main.py @@ -20,7 +20,7 @@ import logging from time import sleep -from proxy.nginx import generate_nginx_configs +from proxy.nginx import update_nginx_configs from proxy.endpoints import generate_endpoints from proxy.helper import init_default_logger, write_json from proxy.str_formatters import arguments_list_string @@ -42,7 +42,7 @@ def main(): logger.info('Collecting endpoints list') schains_endpoints = generate_endpoints(ENDPOINT, SM_ABI_FILEPATH) write_json(CHAINS_INFO_FILEPATH, schains_endpoints) - generate_nginx_configs(schains_endpoints) + update_nginx_configs(schains_endpoints) logger.info(f'Proxy iteration done, sleeping for {MONITOR_INTERVAL}s...') sleep(MONITOR_INTERVAL) diff --git a/proxy/nginx.py b/proxy/nginx.py index c22a232..0d0f6ff 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -20,13 +20,30 @@ import os import logging +import docker + from proxy.helper import process_template from proxy.config import ( - SCHAIN_NGINX_TEMPLATE, UPSTREAM_NGINX_TEMPLATE, CHAINS_FOLDER, UPSTREAMS_FOLDER, SERVER_NAME + SCHAIN_NGINX_TEMPLATE, UPSTREAM_NGINX_TEMPLATE, CHAINS_FOLDER, UPSTREAMS_FOLDER, SERVER_NAME, + NGINX_CONTAINER_NAME ) logger = logging.getLogger(__name__) +docker_client = docker.DockerClient() + + +def update_nginx_configs(schains_endpoints: list) -> None: + generate_nginx_configs(schains_endpoints) + restart_nginx_container() + + +def restart_nginx_container(d_client=None): + logger.info('Going to restart nginx container') + d_client = d_client or docker_client + nginx_container = d_client.containers.get(NGINX_CONTAINER_NAME) + nginx_container.restart() + logger.info('Successfully restarted nginx container') def generate_nginx_configs(schains_endpoints: list) -> None: diff --git a/requirements.txt b/requirements.txt index c930523..442a1e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ web3==5.26.0 Jinja2==3.0.3 -colorful==0.5.4 \ No newline at end of file +colorful==0.5.4 + +docker==5.0.3 \ No newline at end of file diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh new file mode 100644 index 0000000..8484aa8 --- /dev/null +++ b/scripts/run_proxy.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +export DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +export NETWORKS=$NETWORKS +export DOCS_WEBSITE_URL=$DOCS_WEBSITE_URL +export MAIN_WEBSITE_URL=$MAIN_WEBSITE_URL +export NETWORK_NAME=$NETWORK_NAME +export CHAIN_ID=$CHAIN_ID +export EXPLORER_URL=$EXPLORER_URL +export BASE_PROXY_URL=$BASE_PROXY_URL + +bash $DIR/../proxy-ui/prepare_env_file.sh + +cd $DIR/.. +docker-compose up --build -d \ No newline at end of file From a22c9b8ebca0681ca671d06fcc6a7df3987831d4 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 23 Feb 2022 16:04:32 +0000 Subject: [PATCH 06/27] SKALE-4289 Fix container name in publish pipeline --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a41d3e0..856961a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: echo "Version $VERSION" export RELEASE=true echo "RELEASE=$RELEASE" >> $GITHUB_ENV - export CONTAINER_NAME="admin" + export CONTAINER_NAME="proxy" bash ./helper-scripts/build_and_publish.sh - name: Checkout code uses: actions/checkout@master From 5f8c1027da37d1b6b1581f68d68f2cbf78af687d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 11 Mar 2022 18:27:48 +0000 Subject: [PATCH 07/27] SKALE-4289 Use nginx hot reload, add endpoint check --- README.md | 42 +++++++++++++++--------------------------- config/nginx.conf | 23 +---------------------- docker-compose.yml | 4 ++-- proxy/endpoints.py | 30 +++++++++++++++--------------- proxy/nginx.py | 17 +++++++---------- requirements.txt | 4 +++- 6 files changed, 43 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 6b670b7..d4343c5 100644 --- a/README.md +++ b/README.md @@ -12,40 +12,28 @@ JSON-RPC endpoints for SKALE chains. It is based on NGINX. - Docker - docker-compose - ### Repo setup -1. Clone all submodules -2. Put `abi.json` file in `data` folder -3. Put `server.crt` and `server.key` files in `data` folder -4. Configure proxy-ui environement params -5. Export `ENDPOINT` environement param -5. Run ` docker-compose up --build` - -1. Place you ABI json file into abi directory - -2. Set 'ABI_FILENAME directory' in docker-compose.yml to the name of the ABI file. - -3. Set 'PROXY_FULL_HOST_NAME' in docker-compose.yml to the domain name of your proxy. - -4. Set 'ENDPOINT_PREFIX' in 'docker-compose.yml' to the endpoint prefix (must be non-empty!) - -5. Set 'ETH_ENDPOINT' in docker-compose to your ETH main net endpoint. - -6. Create 'data' directory and copy 'server.crt' and 'server.key' to it. - The certificate need to be issued to 'PROXY_FULL_HOST_NAME'. - - -7. Run 'docker-compose pull && docker-compose up' andf wait around a minute until skale-proxy reads schain info from blockchain and starts. +1. Clone repo & all submodules +2. Put `abi.json`, `server.crt` and `server.key`files in `data` folder +3. Export all required environement variables (see below) +4. Run `scripts/run_proxy.sh` -8. Try 'http://PROXY_FULL_HOST_NAME/api.json' . You should be able to see API descriptions. +#### Required environement variables - Voila! +- `NETWORKS` +- `DOCS_WEBSITE_URL` +- `MAIN_WEBSITE_URL` +- `NETWORK_NAME` +- `CHAIN_ID` +- `EXPLORER_URL` +- `BASE_PROXY_URL` +- `ETH_ENDPOINT` ## License -[![License](https://img.shields.io/github/license/skalenetwork/skale-admin.svg)](LICENSE) +[![License](https://img.shields.io/github/license/skalenetwork/skale-proxy.svg)](LICENSE) -All contributions to SKALE Admin are made under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl-3.0.en.html). See [LICENSE](LICENSE). +All contributions to SKALE Proxy are made under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl-3.0.en.html). See [LICENSE](LICENSE). Copyright (C) 2022-Present SKALE Labs. \ No newline at end of file diff --git a/config/nginx.conf b/config/nginx.conf index da65826..6949fbb 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -3,32 +3,11 @@ events { } http { - server { + listen 80; listen 443 ssl; ssl_certificate /data/server.crt; ssl_certificate_key /data/server.key; - ssl_verify_client off; - server_name _; - - location / { - proxy_http_version 1.1; - proxy_pass http://proxy-ui:5001/; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - - location /files/ { - root /usr/share/nginx/www; - } - - include /etc/nginx/conf/chains/*.conf; - } - - server { - listen 80; server_name _; location / { diff --git a/docker-compose.yml b/docker-compose.yml index 1f4b1ce..92a6ece 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,8 +18,8 @@ services: context: . dockerfile: Dockerfile volumes: - - ./data:/usr/src/proxy/data - - ./www:/usr/src/proxy/www + - ./data:/usr/src/proxy/data + - ./www:/usr/src/proxy/www - ./conf:/usr/src/proxy/conf/ - /var/run/docker.sock:/var/run/docker.sock logging: diff --git a/proxy/endpoints.py b/proxy/endpoints.py index c49439f..c26bd5a 100644 --- a/proxy/endpoints.py +++ b/proxy/endpoints.py @@ -20,6 +20,8 @@ import json import logging +import requests + from web3 import Web3, HTTPProvider from Crypto.Hash import keccak @@ -46,24 +48,18 @@ def __init__(self, schain_name: str, nodes: list): self.schain_name = schain_name self.chain_id = schain_name_to_network_id(schain_name) self.http_endpoints = [] - # self.http_endpoints_ip, self.https_endpoints = [], [] self.ws_endpoints = [] - # self.ws_endpoints_ip, self.wss_endpoints = [], [] self.fs_endpoints = [] self._format_nodes(nodes) def _format_nodes(self, nodes): for node in nodes: - self.http_endpoints.append( - node['http_endpoint_domain'].removeprefix(URL_PREFIXES['http']) - ) - #self.http_endpoints_ip.append(node['http_endpoint_ip'].removeprefix(URL_PREFIXES['http'])) - #self.https_endpoints.append(node['https_endpoint_domain'].removeprefix(URL_PREFIXES['https'])) - + http_endpoint = node['http_endpoint_domain'] + if not url_ok(http_endpoint): + logger.warning(f'{http_endpoint} is not accesible, removing from the list') + continue + self.http_endpoints.append(http_endpoint.removeprefix(URL_PREFIXES['http'])) self.ws_endpoints.append(node['ws_endpoint_domain'].removeprefix(URL_PREFIXES['ws'])) - #self.ws_endpoints_ip.append(node['ws_endpoint_ip'].removeprefix(URL_PREFIXES['ws'])) - #self.wss_endpoints.append(node['wss_endpoint_domain'].removeprefix(URL_PREFIXES['wss'])) - self.fs_endpoints.append(node['domain']) def to_dict(self): @@ -71,15 +67,19 @@ def to_dict(self): 'schain_name': self.schain_name, 'chain_id': self.chain_id, 'http_endpoints': self.http_endpoints, - # 'http_endpoints_ip': self.http_endpoints_ip, - # 'https_endpoints': self.https_endpoints, 'ws_endpoints': self.ws_endpoints, - # 'ws_endpoints_ip': self.ws_endpoints_ip, - # 'wss_endpoints': self.wss_endpoints, 'fs_endpoints': self.fs_endpoints } +def url_ok(url) -> bool: + try: + r = requests.head(url) + return bool(r.status_code) + except requests.exceptions.ConnectionError: + return False + + def schain_name_to_id(name: str) -> str: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) return '0x' + keccak_hash.hexdigest() diff --git a/proxy/nginx.py b/proxy/nginx.py index 0d0f6ff..8a75b78 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -42,8 +42,11 @@ def restart_nginx_container(d_client=None): logger.info('Going to restart nginx container') d_client = d_client or docker_client nginx_container = d_client.containers.get(NGINX_CONTAINER_NAME) - nginx_container.restart() - logger.info('Successfully restarted nginx container') + res = nginx_container.exec_run(cmd='nginx -s reload') + if res != 0: + logger.warning('Could not reload nginx configuration, check out nginx logs') + else: + logger.info('Successfully reloaded nginx service') def generate_nginx_configs(schains_endpoints: list) -> None: @@ -62,11 +65,5 @@ def process_nginx_config_template(chain_info: dict, server_name: str) -> None: if __name__ == '__main__': - chain_data = {'schain_name': 'test'} - process_nginx_config_template({ - 'server_name': 'test.com', - 'schain_name': 'test', - 'http_endpoints': ['ssss.com:15555', 'aaaa', 'bbbbb'], - 'ws_endpoints': ['fgdfgdf', 'dsgfs', 'asaffd'], - 'fs_endpoints': ['bvf', 'g', 'hh'], - }) + res = generate_nginx_configs([]) + print(res) diff --git a/requirements.txt b/requirements.txt index 442a1e9..709f239 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ web3==5.26.0 Jinja2==3.0.3 colorful==0.5.4 -docker==5.0.3 \ No newline at end of file +docker==5.0.3 + +requests==2.27.1 \ No newline at end of file From 5ff9bf8ba6fa70554154523e9f938f417688139d Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Mar 2022 15:43:40 +0000 Subject: [PATCH 08/27] SKALE-4289 Add restart for nginx container --- proxy/config.py | 1 + proxy/nginx.py | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/proxy/config.py b/proxy/config.py index 388f213..757254f 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -49,3 +49,4 @@ LONG_LINE = '=' * 100 NGINX_CONTAINER_NAME = 'proxy_nginx' +CONTAINER_RUNNING_STATUS = 'running' \ No newline at end of file diff --git a/proxy/nginx.py b/proxy/nginx.py index 8a75b78..33c81c0 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -25,7 +25,7 @@ from proxy.helper import process_template from proxy.config import ( SCHAIN_NGINX_TEMPLATE, UPSTREAM_NGINX_TEMPLATE, CHAINS_FOLDER, UPSTREAMS_FOLDER, SERVER_NAME, - NGINX_CONTAINER_NAME + NGINX_CONTAINER_NAME, CONTAINER_RUNNING_STATUS ) @@ -35,18 +35,32 @@ def update_nginx_configs(schains_endpoints: list) -> None: generate_nginx_configs(schains_endpoints) - restart_nginx_container() + monitor_nginx_container() -def restart_nginx_container(d_client=None): +def monitor_nginx_container(d_client=None): logger.info('Going to restart nginx container') d_client = d_client or docker_client nginx_container = d_client.containers.get(NGINX_CONTAINER_NAME) - res = nginx_container.exec_run(cmd='nginx -s reload') + + if is_container_running(nginx_container): + reload_nginx(nginx_container) + else: + logger.info('nginx container is not running, trying to restart') + nginx_container.restart() + + +def reload_nginx(container) -> int: + res = container.exec_run(cmd='nginx -s reload') if res != 0: logger.warning('Could not reload nginx configuration, check out nginx logs') else: logger.info('Successfully reloaded nginx service') + return res + + +def is_container_running(container) -> bool: + return container.status == CONTAINER_RUNNING_STATUS def generate_nginx_configs(schains_endpoints: list) -> None: From cdb35f568e2e6cf7d0ecb54179557f27afd0df65 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Mon, 14 Mar 2022 17:06:46 +0000 Subject: [PATCH 09/27] SKALE-4289 Add tmp dirs for nginx configs --- conf/chains/.keep | 0 conf/upstreams/.keep | 0 proxy/config.py | 3 +++ proxy/main.py | 8 +++++++- proxy/nginx.py | 21 ++++++++++++++++++--- 5 files changed, 28 insertions(+), 4 deletions(-) delete mode 100644 conf/chains/.keep delete mode 100644 conf/upstreams/.keep diff --git a/conf/chains/.keep b/conf/chains/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/conf/upstreams/.keep b/conf/upstreams/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/proxy/config.py b/proxy/config.py index 757254f..6345f80 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -43,6 +43,9 @@ CHAINS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'chains') UPSTREAMS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'upstreams') +TMP_CHAINS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'tmp_chains') +TMP_UPSTREAMS_FOLDER = os.path.join(PROJECT_PATH, 'conf', 'tmp_upstreams') + SERVER_NAME = os.environ['SERVER_NAME'] PROXY_LOG_FORMAT = '[%(asctime)s] %(process)d %(levelname)s %(module)s: %(message)s' diff --git a/proxy/main.py b/proxy/main.py index cba949c..3aaef86 100644 --- a/proxy/main.py +++ b/proxy/main.py @@ -19,13 +19,15 @@ import logging from time import sleep +from pathlib import Path from proxy.nginx import update_nginx_configs from proxy.endpoints import generate_endpoints from proxy.helper import init_default_logger, write_json from proxy.str_formatters import arguments_list_string from proxy.config import ( - CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH, SERVER_NAME + CHAINS_INFO_FILEPATH, MONITOR_INTERVAL, ENDPOINT, SM_ABI_FILEPATH, SERVER_NAME, + TMP_CHAINS_FOLDER, TMP_UPSTREAMS_FOLDER ) @@ -38,6 +40,10 @@ def main(): 'Endpoint': ENDPOINT, 'Server name': SERVER_NAME }, 'Starting SKALE Proxy server')) + + Path(TMP_CHAINS_FOLDER).mkdir(parents=True, exist_ok=True) + Path(TMP_UPSTREAMS_FOLDER).mkdir(parents=True, exist_ok=True) + while True: logger.info('Collecting endpoints list') schains_endpoints = generate_endpoints(ENDPOINT, SM_ABI_FILEPATH) diff --git a/proxy/nginx.py b/proxy/nginx.py index 33c81c0..5835717 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -18,14 +18,16 @@ # along with this program. If not, see . import os +import shutil import logging +from pathlib import Path import docker from proxy.helper import process_template from proxy.config import ( SCHAIN_NGINX_TEMPLATE, UPSTREAM_NGINX_TEMPLATE, CHAINS_FOLDER, UPSTREAMS_FOLDER, SERVER_NAME, - NGINX_CONTAINER_NAME, CONTAINER_RUNNING_STATUS + NGINX_CONTAINER_NAME, CONTAINER_RUNNING_STATUS, TMP_CHAINS_FOLDER, TMP_UPSTREAMS_FOLDER ) @@ -35,9 +37,22 @@ def update_nginx_configs(schains_endpoints: list) -> None: generate_nginx_configs(schains_endpoints) + move_nginx_configs() monitor_nginx_container() +def move_nginx_configs(): + """Moves nginx configs from the temporary directories to the main folders""" + logger.info('Moving nginx configs from temporary directories...') + shutil.rmtree(CHAINS_FOLDER) + shutil.rmtree(UPSTREAMS_FOLDER) + shutil.move(TMP_CHAINS_FOLDER, CHAINS_FOLDER) + shutil.move(TMP_UPSTREAMS_FOLDER, UPSTREAMS_FOLDER) + Path(TMP_CHAINS_FOLDER).mkdir(parents=True, exist_ok=True) + Path(TMP_UPSTREAMS_FOLDER).mkdir(parents=True, exist_ok=True) + logger.info('nginx configs moved') + + def monitor_nginx_container(d_client=None): logger.info('Going to restart nginx container') d_client = d_client or docker_client @@ -71,8 +86,8 @@ def generate_nginx_configs(schains_endpoints: list) -> None: def process_nginx_config_template(chain_info: dict, server_name: str) -> None: - chain_dest = os.path.join(CHAINS_FOLDER, f'{chain_info["schain_name"]}.conf') - upstream_dest = os.path.join(UPSTREAMS_FOLDER, f'{chain_info["schain_name"]}.conf') + chain_dest = os.path.join(TMP_CHAINS_FOLDER, f'{chain_info["schain_name"]}.conf') + upstream_dest = os.path.join(TMP_UPSTREAMS_FOLDER, f'{chain_info["schain_name"]}.conf') chain_info['server_name'] = server_name process_template(SCHAIN_NGINX_TEMPLATE, chain_dest, chain_info) process_template(UPSTREAM_NGINX_TEMPLATE, upstream_dest, chain_info) From 478dea981feab46af4656d07d49797173aeb7bee Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 18 Mar 2022 17:11:12 +0000 Subject: [PATCH 10/27] SKALE-4289 Add chains dirs fix --- proxy/nginx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/nginx.py b/proxy/nginx.py index 5835717..0b1746a 100644 --- a/proxy/nginx.py +++ b/proxy/nginx.py @@ -44,8 +44,8 @@ def update_nginx_configs(schains_endpoints: list) -> None: def move_nginx_configs(): """Moves nginx configs from the temporary directories to the main folders""" logger.info('Moving nginx configs from temporary directories...') - shutil.rmtree(CHAINS_FOLDER) - shutil.rmtree(UPSTREAMS_FOLDER) + shutil.rmtree(CHAINS_FOLDER, ignore_errors=True) + shutil.rmtree(UPSTREAMS_FOLDER, ignore_errors=True) shutil.move(TMP_CHAINS_FOLDER, CHAINS_FOLDER) shutil.move(TMP_UPSTREAMS_FOLDER, UPSTREAMS_FOLDER) Path(TMP_CHAINS_FOLDER).mkdir(parents=True, exist_ok=True) From a05b56b3f4e39b5c89a98766680abe0d6f2249d7 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 31 May 2022 19:43:26 +0100 Subject: [PATCH 11/27] Add ABIs url support --- helper-scripts | 2 +- proxy-ui | 2 +- scripts/run_proxy.sh | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/helper-scripts b/helper-scripts index 5cd6ed4..204dd65 160000 --- a/helper-scripts +++ b/helper-scripts @@ -1 +1 @@ -Subproject commit 5cd6ed42c44ddf33126a4613cbc381c231401910 +Subproject commit 204dd650cfb5b85242264dbceb498f8f0ebacc1c diff --git a/proxy-ui b/proxy-ui index fed1712..8caf553 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit fed1712d5f38ea8e7d786d36fc32137ebe6ab9f2 +Subproject commit 8caf5532b89dd4972b3cf86c106dd49613c1c38f diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 8484aa8..2e59a0c 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -9,6 +9,7 @@ export NETWORK_NAME=$NETWORK_NAME export CHAIN_ID=$CHAIN_ID export EXPLORER_URL=$EXPLORER_URL export BASE_PROXY_URL=$BASE_PROXY_URL +export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh From 4a5b336a0758251a8ef0620b6ca06c31a717824f Mon Sep 17 00:00:00 2001 From: Dmytro Date: Tue, 31 May 2022 20:03:56 +0100 Subject: [PATCH 12/27] Fix proxy-ui --- proxy-ui | 2 +- scripts/run_proxy.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy-ui b/proxy-ui index 8caf553..5160cb9 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 8caf5532b89dd4972b3cf86c106dd49613c1c38f +Subproject commit 5160cb9e9bd934ab560eb45e0be9b4d488266737 diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 2e59a0c..568f73a 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,5 +13,6 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh + cd $DIR/.. docker-compose up --build -d \ No newline at end of file From 38392c9031cd8e0607e198bc5f80716178fcac42 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 1 Jun 2022 13:00:35 +0100 Subject: [PATCH 13/27] Update proxy-ui --- proxy-ui | 2 +- scripts/run_proxy.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy-ui b/proxy-ui index 5160cb9..31a00d1 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 5160cb9e9bd934ab560eb45e0be9b4d488266737 +Subproject commit 31a00d1f6df0d875592b0cdd5befcd998d704c3a diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 568f73a..2e59a0c 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,6 +13,5 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh - cd $DIR/.. docker-compose up --build -d \ No newline at end of file From 6be8bf64d81fcecb51ca84c027db7082d43dd6b3 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 1 Jun 2022 13:07:12 +0100 Subject: [PATCH 14/27] Update proxy-ui --- scripts/run_proxy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 2e59a0c..568f73a 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,5 +13,6 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh + cd $DIR/.. docker-compose up --build -d \ No newline at end of file From 5af0ca61da41ea5eeeea40302e111810f5c89ffb Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 1 Jun 2022 13:07:22 +0100 Subject: [PATCH 15/27] Update proxy-ui --- proxy-ui | 2 +- scripts/run_proxy.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy-ui b/proxy-ui index 31a00d1..7a448a6 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 31a00d1f6df0d875592b0cdd5befcd998d704c3a +Subproject commit 7a448a6c15568ea0a96d1da4ed3d07b06c0fbfdb diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 568f73a..2e59a0c 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,6 +13,5 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh - cd $DIR/.. docker-compose up --build -d \ No newline at end of file From b5434e00ea4ba269fe6ff1eb2fea5123840e412f Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 1 Jun 2022 13:37:49 +0100 Subject: [PATCH 16/27] Update proxy-ui --- proxy-ui | 2 +- scripts/run_proxy.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy-ui b/proxy-ui index 7a448a6..f987eea 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 7a448a6c15568ea0a96d1da4ed3d07b06c0fbfdb +Subproject commit f987eea02b1fbae7dc943bbbf93bc77638b4415e diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 2e59a0c..568f73a 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,5 +13,6 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh + cd $DIR/.. docker-compose up --build -d \ No newline at end of file From 1a221171d6697ad5a5e430ba90042910fdbfdfac Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 2 Jun 2022 17:14:58 +0100 Subject: [PATCH 17/27] Update proxy-ui --- proxy-ui | 2 +- scripts/run_proxy.sh | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy-ui b/proxy-ui index f987eea..4420ba7 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit f987eea02b1fbae7dc943bbbf93bc77638b4415e +Subproject commit 4420ba7dadb462c8d05ef920b1c4da81a00cf4e9 diff --git a/scripts/run_proxy.sh b/scripts/run_proxy.sh index 568f73a..2e59a0c 100644 --- a/scripts/run_proxy.sh +++ b/scripts/run_proxy.sh @@ -13,6 +13,5 @@ export ABIS_URL=$ABIS_URL bash $DIR/../proxy-ui/prepare_env_file.sh - cd $DIR/.. docker-compose up --build -d \ No newline at end of file From cd207a4214b889401096e09fbbdb79a05c4a95f1 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 9 Jun 2022 14:02:00 +0100 Subject: [PATCH 18/27] SKALE-4289 Add schain options to proxy, add redirect to https --- config/nginx.conf | 8 +++++- proxy/endpoints.py | 32 +++++++++++++++++----- proxy/schain_options.py | 60 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 proxy/schain_options.py diff --git a/config/nginx.conf b/config/nginx.conf index 6949fbb..19cec57 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -1,10 +1,16 @@ events { - worker_connections 1024; + worker_connections 100000; } http { + server { listen 80; + server_name _; + return 301 https://$host$request_uri; + } + + server { listen 443 ssl; ssl_certificate /data/server.crt; ssl_certificate_key /data/server.key; diff --git a/proxy/endpoints.py b/proxy/endpoints.py index c26bd5a..9f94c9c 100644 --- a/proxy/endpoints.py +++ b/proxy/endpoints.py @@ -29,7 +29,7 @@ from proxy.helper import read_json from proxy.config import ENDPOINT, SM_ABI_FILEPATH from proxy.str_formatters import arguments_list_string - +from proxy.schain_options import parse_schain_options logger = logging.getLogger(__name__) @@ -97,9 +97,23 @@ def _compose_endpoints(node_dict, endpoint_type): node_dict[key_name] = f'{prefix}{node_dict[endpoint_type]}:{port}' -def generate_endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id): +def generate_endpoints_for_schain( + schains_internal_contract, + schains_contract, + nodes_contract, + schain_id +): """Generates endpoints list for a given SKALE chain""" schain = schains_internal_contract.functions.schains(schain_id).call() + schain_options_raw = schains_contract.functions.getOptions(schain_id).call() + + schain_options = parse_schain_options( + raw_options=schain_options_raw + ) + + schain.append(schain_options.multitransaction_mode) + schain.append(schain_options.threshold_encryption) + logger.info(f'Going to generate endpoints for sChain: {schain[0]}') node_ids = schains_internal_contract.functions.getNodesInGroup(schain_id).call() @@ -126,11 +140,15 @@ def init_contracts(web3: Web3, sm_abi: str): address=sm_abi['schains_internal_address'], abi=sm_abi['schains_internal_abi'] ) + schains_contract = web3.eth.contract( + address=sm_abi['schains_address'], + abi=sm_abi['schains_abi'] + ) nodes_contract = web3.eth.contract( address=sm_abi['nodes_address'], abi=sm_abi['nodes_abi'] ) - return schains_internal_contract, nodes_contract + return schains_internal_contract, schains_contract, nodes_contract def generate_endpoints(endpoint: str, abi_filepath: str) -> list: @@ -139,14 +157,15 @@ def generate_endpoints(endpoint: str, abi_filepath: str) -> list: web3 = Web3(provider) sm_abi = read_json(abi_filepath) - schains_internal_contract, nodes_contract = init_contracts( + schains_internal_contract, schains_contract, nodes_contract = init_contracts( web3=web3, sm_abi=sm_abi ) logger.info(arguments_list_string({ 'nodes': nodes_contract.address, - 'schains_internal': schains_internal_contract.address + 'schains_internal': schains_internal_contract.address, + 'schains': schains_contract.address }, 'Contracts inited')) schain_ids = schains_internal_contract.functions.getSchains().call() @@ -155,7 +174,8 @@ def generate_endpoints(endpoint: str, abi_filepath: str) -> list: logger.info(f'Number of sChains: {len(schain_ids)}') endpoints = [ - generate_endpoints_for_schain(schains_internal_contract, nodes_contract, schain_id) + generate_endpoints_for_schain( + schains_internal_contract, schains_contract, nodes_contract, schain_id) for schain_id in schain_ids ] return endpoints diff --git a/proxy/schain_options.py b/proxy/schain_options.py new file mode 100644 index 0000000..75e7fc7 --- /dev/null +++ b/proxy/schain_options.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2022-Present SKALE Labs +# +# SKALE.py 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. +# +# SKALE.py 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 SKALE.py. If not, see . + +from dataclasses import dataclass + + +@dataclass +class SchainOptions: + multitransaction_mode: bool + threshold_encryption: bool + + def to_tuples(self) -> list: + return [ + ('multitr', bool_to_bytes(self.multitransaction_mode)), + ('encrypt', bool_to_bytes(self.threshold_encryption)) + ] + + +def parse_schain_options(raw_options: list) -> SchainOptions: + """ + Parses raw sChain options from smart contracts (list of tuples). + Returns default values if nothing is set on contracts. + """ + if len(raw_options) == 0: + return get_default_schain_options() + return SchainOptions( + multitransaction_mode=bytes_to_bool(raw_options[0][1]), + threshold_encryption=bytes_to_bool(raw_options[1][1]) + ) + + +def get_default_schain_options() -> SchainOptions: + return SchainOptions( + multitransaction_mode=False, + threshold_encryption=False + ) + + +def bool_to_bytes(bool_value: bool) -> bytes: + return bool_value.to_bytes(1, byteorder='big') + + +def bytes_to_bool(bytes_value: bytes) -> bool: + return bool(int.from_bytes(bytes_value, 'big')) From 27d9efb96acae3fc18f429f7a365d96b810e779a Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 9 Jun 2022 14:08:02 +0100 Subject: [PATCH 19/27] Update ima-ui submodule --- README.md | 2 +- proxy-ui | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d4343c5..536c3c0 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,4 @@ JSON-RPC endpoints for SKALE chains. It is based on NGINX. All contributions to SKALE Proxy are made under the [GNU Affero General Public License v3](https://www.gnu.org/licenses/agpl-3.0.en.html). See [LICENSE](LICENSE). -Copyright (C) 2022-Present SKALE Labs. \ No newline at end of file +Copyright (C) 2022-Present SKALE Labs. diff --git a/proxy-ui b/proxy-ui index 4420ba7..a448d94 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 4420ba7dadb462c8d05ef920b1c4da81a00cf4e9 +Subproject commit a448d949263c00ded99729a3a7ea6fb0a80930fe From 629295f4711ee659d922eed2dc43ae5f44f02e78 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 9 Jun 2022 14:54:30 +0100 Subject: [PATCH 20/27] Redirect everything to https --- config/nginx.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/nginx.conf b/config/nginx.conf index 19cec57..f281d50 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -5,7 +5,8 @@ events { http { server { - listen 80; + listen 80 default_server; + listen [::]:80 default_server; server_name _; return 301 https://$host$request_uri; } From 81ffcae513d0fd0f990c9290e222d9b154bfc0cd Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 9 Jun 2022 15:10:48 +0100 Subject: [PATCH 21/27] Add additional SSL options --- config/nginx.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/nginx.conf b/config/nginx.conf index f281d50..2a164ea 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -16,6 +16,11 @@ http { ssl_certificate /data/server.crt; ssl_certificate_key /data/server.key; server_name _; + ssl on; + + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:20m; + ssl_session_tickets off; location / { proxy_http_version 1.1; From 44b3bc08465f6090e9d91903f4d6ffc501cb14bd Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 9 Jun 2022 16:20:04 +0100 Subject: [PATCH 22/27] Return http endpoints nginx --- config/nginx.conf | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/config/nginx.conf b/config/nginx.conf index 2a164ea..6ddf1c6 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -3,24 +3,12 @@ events { } http { - - server { - listen 80 default_server; - listen [::]:80 default_server; - server_name _; - return 301 https://$host$request_uri; - } - server { + listen 80; listen 443 ssl; ssl_certificate /data/server.crt; ssl_certificate_key /data/server.key; server_name _; - ssl on; - - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:20m; - ssl_session_tickets off; location / { proxy_http_version 1.1; From 0f73284de1096879f03843ca3b054f5736f8ac36 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 10 Jun 2022 20:12:46 +0100 Subject: [PATCH 23/27] SKALE-4289 Update nginx config --- config/nginx.conf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/nginx.conf b/config/nginx.conf index 6ddf1c6..45d17f6 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -1,3 +1,5 @@ +limit_req_zone $binary_remote_addr zone=one:10m rate=7r/s; + events { worker_connections 100000; } @@ -10,6 +12,14 @@ http { ssl_certificate_key /data/server.key; server_name _; + client_max_body_size 2M; + + limit_req zone=one burst=50; + + proxy_read_timeout 500s; + proxy_connect_timeout 500s; + proxy_send_timeout 500s; + location / { proxy_http_version 1.1; proxy_pass http://proxy-ui:5001/; From e15306ad7f772d1571bd581e3092418421dbd3c0 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Fri, 10 Jun 2022 20:51:37 +0100 Subject: [PATCH 24/27] Update base nginx config --- config/nginx.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/nginx.conf b/config/nginx.conf index 45d17f6..1d28f5e 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -1,10 +1,12 @@ -limit_req_zone $binary_remote_addr zone=one:10m rate=7r/s; - events { worker_connections 100000; } http { + + limit_req_zone $binary_remote_addr zone=one:10m rate=7r/s; + client_max_body_size 5M; + server { listen 80; listen 443 ssl; @@ -12,8 +14,6 @@ http { ssl_certificate_key /data/server.key; server_name _; - client_max_body_size 2M; - limit_req zone=one burst=50; proxy_read_timeout 500s; From a3e1a9595532e4cc12c8202d9dcf9f319dc5f0db Mon Sep 17 00:00:00 2001 From: Dmytro Date: Thu, 23 Jun 2022 11:46:02 +0100 Subject: [PATCH 25/27] Increase IP limits --- config/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/nginx.conf b/config/nginx.conf index 1d28f5e..d7948f4 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -4,7 +4,7 @@ events { http { - limit_req_zone $binary_remote_addr zone=one:10m rate=7r/s; + limit_req_zone $binary_remote_addr zone=one:10m rate=15r/s; client_max_body_size 5M; server { @@ -14,7 +14,7 @@ http { ssl_certificate_key /data/server.key; server_name _; - limit_req zone=one burst=50; + limit_req zone=one burst=100; proxy_read_timeout 500s; proxy_connect_timeout 500s; From 9c9b64178015afcb57196f852525ca7270b481f5 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 3 Aug 2022 19:19:32 +0100 Subject: [PATCH 26/27] Update proxy-ui version --- .gitmodules | 2 +- helper-scripts | 2 +- proxy-ui | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index ebc93a5..a5103dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "proxy-ui"] path = proxy-ui url = https://github.com/skalenetwork/proxy-ui.git - branch = master + branch = beta [submodule "helper-scripts"] path = helper-scripts url = https://github.com/skalenetwork/helper-scripts.git diff --git a/helper-scripts b/helper-scripts index 204dd65..2242899 160000 --- a/helper-scripts +++ b/helper-scripts @@ -1 +1 @@ -Subproject commit 204dd650cfb5b85242264dbceb498f8f0ebacc1c +Subproject commit 2242899037c120ce43ec87d9b80f2a223c619331 diff --git a/proxy-ui b/proxy-ui index a448d94..15db964 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit a448d949263c00ded99729a3a7ea6fb0a80930fe +Subproject commit 15db964d091c319ed891abcce1c7edbb76327b55 From 0c922f77de817fd41c70e37998717253a740ff18 Mon Sep 17 00:00:00 2001 From: Dmytro Date: Wed, 3 Aug 2022 19:42:46 +0100 Subject: [PATCH 27/27] Update proxy-ui version --- proxy-ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy-ui b/proxy-ui index 15db964..2ced0d9 160000 --- a/proxy-ui +++ b/proxy-ui @@ -1 +1 @@ -Subproject commit 15db964d091c319ed891abcce1c7edbb76327b55 +Subproject commit 2ced0d9252d7fbf1ad88383a721ae74ac57d5395