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 @@
- Current DS Block |
- {{ current.block_num }} |
+ Current Block |
+ {{ current.block_num }}
+ {% if current.tx_block_num %}
+ / {{ current.tx_block_num }}
+ {% endif %}
+ |
Next PoW Time |
@@ -163,10 +167,14 @@