diff --git a/pool.conf b/pool.conf index 8add206..b66660c 100644 --- a/pool.conf +++ b/pool.conf @@ -36,3 +36,6 @@ smtp: username: "" password: "" timeout: 1 + +zilliqa: + enabled: false diff --git a/zilpool/apis/eth.py b/zilpool/apis/eth.py index 09f3423..0f80813 100644 --- a/zilpool/apis/eth.py +++ b/zilpool/apis/eth.py @@ -34,7 +34,10 @@ def init_apis(config): ) def no_work(): - seconds_to_next_pow = pow.PoWWindow.seconds_to_next_pow() + if config["zilliqa"]["enabled"]: + seconds_to_next_pow = utils.Zilliqa.secs_to_next_pow() + else: + seconds_to_next_pow = pow.PoWWindow.seconds_to_next_pow() return "", "", "", False, int(seconds_to_next_pow) @method diff --git a/zilpool/apis/stats.py b/zilpool/apis/stats.py index 8114feb..e459fbf 100644 --- a/zilpool/apis/stats.py +++ b/zilpool/apis/stats.py @@ -24,8 +24,7 @@ import zilpool from zilpool.common import utils -from zilpool.pyzil import crypto -from zilpool.pyzil import ethash +from zilpool.pyzil import crypto, ethash from zilpool.database import pow, miner, zilnode @@ -36,7 +35,7 @@ async def stats(request): @method async def stats_current(request): - return current_work() + return current_work(config) @method @utils.args_to_lower @@ -98,12 +97,14 @@ def summary(): } -def current_work(): +def current_work(config): latest_work = pow.PowWork.get_latest_work() block_num = 0 + tx_block_num = None difficulty = [0, 0] start_time = None + if latest_work: block_num = latest_work.block_num start_time = latest_work.start_time @@ -111,10 +112,19 @@ def current_work(): now = datetime.utcnow() secs_next_pow = pow.PoWWindow.seconds_to_next_pow() + + if config["zilliqa"]["enabled"]: + block_num = utils.Zilliqa.get_current_dsblock() + tx_block_num = utils.Zilliqa.get_current_txblock() + difficulty = utils.Zilliqa.get_difficulty() + difficulty = [ethash.difficulty_to_hashpower(d) for d in difficulty] + secs_next_pow = utils.Zilliqa.secs_to_next_pow() + next_pow_time = now + timedelta(seconds=secs_next_pow) return { "block_num": block_num, + "tx_block_num": tx_block_num, "difficulty": difficulty, "utc_time": utils.iso_format(now), "start_time": utils.iso_format(start_time), diff --git a/zilpool/apis/zil.py b/zilpool/apis/zil.py index e1f0e79..f2134f2 100644 --- a/zilpool/apis/zil.py +++ b/zilpool/apis/zil.py @@ -47,6 +47,11 @@ async def zil_requestWork(request, logging.warning(f"failed verify signature") return False + if config["zilliqa"]["enabled"]: + # 0. check network info + if not utils.Zilliqa.is_pow_window(): + return False + block_num = crypto.hex_str_to_int(block_num) timeout = crypto.hex_str_to_int(timeout) diff --git a/zilpool/backgound.py b/zilpool/backgound.py new file mode 100644 index 0000000..20a43a6 --- /dev/null +++ b/zilpool/backgound.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Zilliqa Mining Proxy +# Copyright (C) 2019 Gully Chen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +backgound tasks +""" +import time +import logging + +import asyncio +from zilpool.common import utils +from zilpool.pyzil.zilliqa_api import APIError + + +async def update_chain_info(config): + try: + prev_block = None + prev_time = None + latest_blocks_time = [] + while True: + try: + cur_block = utils.Zilliqa.get_current_txblock() + if cur_block: + if prev_block is None: + prev_block = cur_block + prev_time = time.time() + else: + if cur_block < prev_block: + utils.Zilliqa.clear_cache() + continue + + if cur_block > prev_block: + blocks = cur_block - prev_block + now = time.time() + + elapsed = now - prev_time + block_time = elapsed / blocks + latest_blocks_time.append(block_time) + + avg_time = sum(latest_blocks_time) / len(latest_blocks_time) + utils.Zilliqa.update_avg_block_time(avg_time) + + prev_block = cur_block + prev_time = now + + if len(latest_blocks_time) > 50: + latest_blocks_time = latest_blocks_time[25:] + + except APIError as e: + logging.error(f"APIError {e}") + + await asyncio.sleep(config["zilliqa"]["update_interval"]) + except asyncio.CancelledError: + pass + except: + logging.exception("unknown error in update_chain_info") + raise + finally: + pass + + +async def start_background_tasks(app): + config = app["config"] + if config["zilliqa"]["enabled"]: + app["zil_background"] = app.loop.create_task(update_chain_info(config)) + + +async def cleanup_background_tasks(app): + if "zil_background" in app: + app["zil_background"].cancel() + await app["zil_background"] diff --git a/zilpool/common/utils.py b/zilpool/common/utils.py index c46dccd..9dc4460 100644 --- a/zilpool/common/utils.py +++ b/zilpool/common/utils.py @@ -22,9 +22,11 @@ import hashlib from collections import Mapping from functools import wraps +from cachetools import TTLCache from concurrent.futures import ThreadPoolExecutor from zilpool.pyzil import crypto +from zilpool.pyzil import zilliqa_api cur_dir = os.path.dirname(os.path.abspath(__file__)) app_dir = os.path.join(cur_dir, "..") # warning: take care @@ -216,3 +218,94 @@ def wrapper(*args, **kwargs): return thread_pool.submit(func, *args, **kwargs) return wrapper + + +class Zilliqa: + conf = None + api = None + cache = TTLCache(maxsize=64, ttl=30) + + cur_tx_block = 0 + cur_ds_block = 0 + shard_difficulty = 0 + ds_difficulty = 0 + avg_block_time = 25 # from constants.xml + + @classmethod + def init(cls, conf): + cls.conf = conf["zilliqa"] + cls.api = zilliqa_api.API(cls.conf["api_endpoint"]) + + @classmethod + def get_cache(cls, key, func, *args, **kwargs): + val = cls.cache.get(key) + if val is None: + val = func(*args, **kwargs) + try: + cls.cache[key] = val + except KeyError: + pass + return val + + @classmethod + def clear_cache(cls, key=None): + if key is None: + cls.cache.clear() + else: + cls.cache.pop(key, None) + + @classmethod + def update_avg_block_time(cls, avg_time): + cls.avg_block_time = avg_time + + @classmethod + def get_current_txblock(cls): + block = cls.get_cache("txblock", cls.api.GetCurrentMiniEpoch) + block = int(block or 0) + if block > cls.cur_tx_block: + cls.cur_tx_block = block + + return block + + @classmethod + def get_current_dsblock(cls): + block = cls.get_cache("dsblock", cls.api.GetCurrentDSEpoch) + block = int(block or 0) + if block > cls.cur_ds_block: + cls.cur_ds_block = block + return block + + @classmethod + def get_difficulty(cls): + shard_difficulty = cls.get_cache("shard_difficulty", + cls.api.GetPrevDifficulty) + ds_difficulty = cls.get_cache("ds_difficulty", + cls.api.GetPrevDSDifficulty) + + if shard_difficulty: + cls.shard_difficulty = shard_difficulty + if ds_difficulty: + cls.ds_difficulty = ds_difficulty + return shard_difficulty, ds_difficulty + + @classmethod + def is_pow_window(cls): + if not cls.cur_tx_block: + cls.get_current_txblock() + + tx_block = cls.cur_tx_block + block_per_pow = cls.conf["block_per_pow"] + block_in_epoch = tx_block % block_per_pow + return block_in_epoch in [0, block_per_pow - 1] + + @classmethod + def secs_to_next_pow(cls): + if not cls.cur_tx_block: + cls.get_current_txblock() + + tx_block = cls.cur_tx_block + block_per_pow = cls.conf["block_per_pow"] + block_in_epoch = tx_block % block_per_pow + if block_in_epoch == 0: + return 0 # current pow is running + return (block_per_pow - block_in_epoch) * cls.avg_block_time diff --git a/zilpool/default.conf b/zilpool/default.conf index c681bc8..f1556e9 100644 --- a/zilpool/default.conf +++ b/zilpool/default.conf @@ -48,3 +48,9 @@ smtp: username: "" password: "" timeout: 1 + +zilliqa: + enabled: false + api_endpoint: "https://api.zilliqa.com/" + block_per_pow: 100 + update_interval: 30 # in seconds diff --git a/zilpool/poolserver.py b/zilpool/poolserver.py index 8802d78..51b0d36 100644 --- a/zilpool/poolserver.py +++ b/zilpool/poolserver.py @@ -28,6 +28,8 @@ from jsonrpcserver import async_dispatch from jsonrpcserver.response import ExceptionResponse +from zilpool import backgound + # setup root logger FORMATTER = logging.Formatter( @@ -138,11 +140,19 @@ def start_servers(conf_file=None, host=None, port=None): connect_to_db(config) init_db(config) + # init Zilliqa network APIs + utils.Zilliqa.init(config) + # init app app = web.Application(debug=config["debug"]) init_apis(app, config) init_website(app, config) + # start background tasks + app["config"] = config + app.on_startup.append(backgound.start_background_tasks) + app.on_cleanup.append(backgound.cleanup_background_tasks) + # start the server if port is None: port = config["api_server"].get("port", "4202") diff --git a/zilpool/pyzil/zilliqa_api.py b/zilpool/pyzil/zilliqa_api.py new file mode 100644 index 0000000..85cb986 --- /dev/null +++ b/zilpool/pyzil/zilliqa_api.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Zilliqa Mining Proxy +# Copyright (C) 2019 Gully Chen +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" + Zilliqa Network APIs +""" + +import ssl +import threading +import asyncio + +from aiohttp import ClientSession +from jsonrpcclient.exceptions import JsonRpcClientError +from jsonrpcclient.clients.aiohttp_client import AiohttpClient + + +context = ssl._create_unverified_context() + +APIError = JsonRpcClientError + + +class API: + class APIMethod: + def __init__(self, api, method_name): + self.api = api + self.method_name = method_name + + def __call__(self, *params, **kwargs): + resp = self.api.call(self.method_name, *params, **kwargs) + return resp and resp.data and resp.data.result + + def __init__(self, endpoint): + self.endpoint = endpoint + + self.loop = asyncio.new_event_loop() + self.session = ClientSession(loop=self.loop) + self.api_client = AiohttpClient( + self.session, + self.endpoint, + ssl=context + ) + + def __getattr__(self, item): + return API.APIMethod(self, method_name=item) + + def __del__(self): + self.loop.run_until_complete(self.session.close()) + + def call(self, method, *params, **kwargs): + resp = [None] + + def _thread_call(): + resp[0] = self.loop.run_until_complete( + self.api_client.request( + method, params, + trim_log_values=True, **kwargs + ) + ) + th = threading.Thread(target=_thread_call) + th.start() + th.join() + + return resp[0] + + +if "__main__" == __name__: + loop = asyncio.get_event_loop() + + api = API("https://api.zilliqa.com/") + block = api.GetCurrentMiniEpoch() + print(block) + block = api.GetCurrentDSEpoch() + print(block) + block = api.GetCurrentMiniEpoch() + print(block) + diff --git a/zilpool/web/main.py b/zilpool/web/main.py index 9cef0f4..0739e8a 100644 --- a/zilpool/web/main.py +++ b/zilpool/web/main.py @@ -48,7 +48,7 @@ async def index(request): return { "config": config, "summary": stats.summary(), - "current": stats.current_work(), + "current": stats.current_work(config), } app.router.add_route("GET", root_path, index) @@ -141,7 +141,7 @@ async def admin_dashboard(request): "visa": admin.visa_without_ext_data, "expire_at": admin.visa_expire_time, "summary": stats.summary(), - "current": stats.current_work(), + "current": stats.current_work(config), "per_page": 20, }) diff --git a/zilpool/web/template/index.jinja2 b/zilpool/web/template/index.jinja2 index 329a5ff..01ac601 100755 --- a/zilpool/web/template/index.jinja2 +++ b/zilpool/web/template/index.jinja2 @@ -29,8 +29,12 @@ - - + + @@ -163,10 +167,14 @@
Current DS Block{{ current.block_num }}Current Block{{ current.block_num }} + {% if current.tx_block_num %} + / {{ current.tx_block_num }} + {% endif %} +
Next PoW Time