diff --git a/plugins/yadacoinpool/handlers.py b/plugins/yadacoinpool/handlers.py index f29ab08a..90752f86 100644 --- a/plugins/yadacoinpool/handlers.py +++ b/plugins/yadacoinpool/handlers.py @@ -63,17 +63,27 @@ async def fetch_market_data(self): return market_data +from bitcoin.wallet import P2PKHBitcoinAddress + class PoolInfoHandler(BaseWebHandler): async def get(self): await self.config.LatestBlock.block_checker() + + latest_block = self.config.LatestBlock.block.to_dict() + reward_info = self.get_latest_block_reward(latest_block) + + pool_max_target = 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + pool_public_key = ( self.config.pool_public_key if hasattr(self.config, "pool_public_key") else self.config.public_key ) + total_blocks_found = await self.config.mongo.async_db.blocks.count_documents( {"public_key": pool_public_key} ) + pool_blocks_found_list = ( await self.config.mongo.async_db.blocks.find( { @@ -84,90 +94,84 @@ async def get(self): .sort([("index", -1)]) .to_list(5) ) + expected_blocks = 144 - mining_time_interval = 600 - shares_count = await self.config.mongo.async_db.shares.count_documents( - {"time": {"$gte": time.time() - mining_time_interval}} - ) - if shares_count > 0: - pool_hash_rate = ( - shares_count * self.config.pool_diff - ) / mining_time_interval - else: - pool_hash_rate = 0 + mining_time_interval = 1200 + + pipeline = [ + { + "$match": { + "time": {"$gte": time.time() - mining_time_interval} + } + }, + { + "$group": { + "_id": None, + "total_weight": {"$sum": "$weight"} + } + } + ] + + result = await self.config.mongo.async_db.shares.aggregate(pipeline).to_list(1) + + pool_hash_rate = result[0]["total_weight"] / mining_time_interval if result else 0 daily_blocks_found = await self.config.mongo.async_db.blocks.count_documents( {"time": {"$gte": time.time() - (600 * 144)}} ) + if daily_blocks_found > 0: net_target = self.config.LatestBlock.block.target + avg_blocks_found = self.config.mongo.async_db.blocks.find( {"time": {"$gte": time.time() - (600 * 36)}} ) + avg_blocks_found = await avg_blocks_found.to_list(length=52) avg_block_time = daily_blocks_found / expected_blocks * 600 + if len(avg_blocks_found) > 0: - avg_net_target = 0 - for block in avg_blocks_found: - avg_net_target += int(block["target"], 16) - avg_net_target = avg_net_target / len(avg_blocks_found) + avg_net_target = sum(int(block["target"], 16) for block in avg_blocks_found) / len(avg_blocks_found) avg_net_difficulty = ( - 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + pool_max_target / avg_net_target ) net_difficulty = ( - 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + pool_max_target / net_target ) avg_network_hash_rate = ( - len(avg_blocks_found) - / 36 - * avg_net_difficulty - * 2**16 - / avg_block_time + len(avg_blocks_found) / 36 * avg_net_difficulty * 2**16 / avg_block_time ) network_hash_rate = net_difficulty * 2**16 / 600 else: avg_network_hash_rate = 1 net_difficulty = ( - 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - / 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + pool_max_target + / pool_max_target ) network_hash_rate = 0 try: - pool_perecentage = pool_hash_rate / network_hash_rate * 100 + pool_percentage = pool_hash_rate / avg_network_hash_rate * 100 except: - pool_perecentage = 0 + pool_percentage = 0 - if pool_hash_rate == 0: - avg_pool_block_time = 0 - else: - avg_pool_block_time = int( - network_hash_rate * avg_block_time // pool_hash_rate - ) + avg_pool_block_time = ( + int(avg_network_hash_rate * 600 // pool_hash_rate) + if pool_hash_rate > 0 + else 0 + ) - if avg_pool_block_time == 0: - avg_time = ["N/a"] - else: - avg_time = [] - for d, u in [(86400, "day"), (3600, "hour"), (60, "minute")]: - n, avg_pool_block_time = divmod(avg_pool_block_time, d) - if n: - avg_time.append(f"{n} {u}" + "s" * (n > 1)) - avg_time = " ".join(avg_time) - - miner_count_pool_stat = await self.config.mongo.async_db.pool_stats.find_one( - {"stat": "miner_count"} - ) or {"value": 0} - worker_count_pool_stat = await self.config.mongo.async_db.pool_stats.find_one( - {"stat": "worker_count"} - ) or {"value": 0} + avg_time = "N/a" if avg_pool_block_time == 0 else self.format_avg_time(avg_pool_block_time) + + miner_count_pool_stat = await self.config.mongo.async_db.pool_stats.find_one({"stat": "miner_count"}) or {"value": 0} + worker_count_pool_stat = await self.config.mongo.async_db.pool_stats.find_one({"stat": "worker_count"}) or {"value": 0} self.render_as_json( { "node": { - "latest_block": self.config.LatestBlock.block.to_dict(), + "latest_block": latest_block, "health": self.config.health.to_dict(), "version": ".".join([str(x) for x in version]), }, @@ -178,11 +182,6 @@ async def get(self): "payout_scheme": "PPLNS", "pool_fee": self.config.pool_take, "min_payout": 0, - "url": getattr( - self.config, - "pool_url", - f"{self.config.peer_host}:{self.config.stratum_pool_port}", - ), "last_five_blocks": [ {"timestamp": x["updated_at"], "height": x["index"]} for x in pool_blocks_found_list[:5] @@ -191,14 +190,13 @@ async def get(self): "fee": self.config.pool_take, "payout_frequency": self.config.payout_frequency, "blocks": pool_blocks_found_list[:5], - "pool_perecentage": pool_perecentage, + "pool_percentage": pool_percentage, "avg_block_time": avg_time, }, "network": { "height": self.config.LatestBlock.block.index, - "reward": CHAIN.get_block_reward( - self.config.LatestBlock.block.index - ), + "latest_block_reward": reward_info, + "reward": reward_info["miner_reward"] + reward_info["masternodes_total"], "last_block": self.config.LatestBlock.block.time, "avg_hashes_per_second": avg_network_hash_rate, "current_hashes_per_second": network_hash_rate, @@ -206,14 +204,52 @@ async def get(self): }, "coin": { "algo": "randomx YDA", - "circulating": CHAIN.get_circulating_supply( - self.config.LatestBlock.block.index - ), + "circulating": CHAIN.get_circulating_supply(self.config.LatestBlock.block.index), "max_supply": 21000000, }, } ) + def get_latest_block_reward(self, latest_block): + """Parses latest_block and calculates miner and masternode rewards.""" + try: + coinbase_tx = latest_block["transactions"][-1] + miner_address = str(P2PKHBitcoinAddress.from_pubkey(bytes.fromhex(latest_block["public_key"]))) + + miner_reward = 0 + masternodes_rewards = [] + + for output in coinbase_tx["outputs"]: + if output["to"] == miner_address: + miner_reward += output["value"] + else: + masternodes_rewards.append(output["value"]) + + masternodes_total = sum(masternodes_rewards) + masternode_per_node = masternodes_total / len(masternodes_rewards) if masternodes_rewards else 0 + + return { + "miner_reward": round(miner_reward, 8), + "masternodes_total": round(masternodes_total, 8), + "masternode_per_node": round(masternode_per_node, 8) + } + except Exception as e: + self.config.app_log.error(f"Error calculating block reward: {e}") + return { + "miner_reward": 0, + "masternodes_total": 0, + "masternode_per_node": 0 + } + + def format_avg_time(self, seconds): + """Converts time in seconds to a readable form (days, hours, minutes).""" + avg_time = [] + for d, u in [(86400, "day"), (3600, "hour"), (60, "minute")]: + n, seconds = divmod(seconds, d) + if n: + avg_time.append(f"{n} {u}" + "s" * (n > 1)) + return " ".join(avg_time) + class PoolBlocksHandler(BaseWebHandler): async def get(self): diff --git a/plugins/yadacoinpool/static/content/dashboard.html b/plugins/yadacoinpool/static/content/dashboard.html index 702702d7..12887ef3 100755 --- a/plugins/yadacoinpool/static/content/dashboard.html +++ b/plugins/yadacoinpool/static/content/dashboard.html @@ -1,4 +1,5 @@
+
GENERAL STATISTICS
@@ -50,9 +51,10 @@
BLOCKCHAIN HEIGHT
-
BLOCK REWARD
-
Miners: Loading... YDA
-
Master Nodes: Loading... YDA
+
LATEST BLOCK REWARD
+
Miners: Loading...
+
Master Nodes: Loading...
+
Per Masternode: Loading...
@@ -84,4 +86,26 @@
PAYOUT SCHEME
+ +
+
+
+
Pool Hashrate & Miners
+
+ +
+
+
+
+ +
+
+
+
Network Hashrate & Difficulty
+
+ +
+
+
+
diff --git a/plugins/yadacoinpool/static/js/dashboard.js b/plugins/yadacoinpool/static/js/dashboard.js index 5fbaaa75..307a77d4 100755 --- a/plugins/yadacoinpool/static/js/dashboard.js +++ b/plugins/yadacoinpool/static/js/dashboard.js @@ -1,17 +1,30 @@ function loadDashboardData() { console.log("📡 Fetching /pool-info..."); - fetch("/pool-info") - .then(response => response.json()) - .then(data => { - console.log("✅ Data received:", data); - updateDashboard(data); - }) - .catch(error => console.error("❌ Fetch error:", error)); + Promise.all([ + fetch("/pool-info").then(response => response.json()), + fetch("/pool-hashrate-stats").then(response => response.json()) + ]) + .then(([poolData, hashrateData]) => { + console.log("✅ Data received:", poolData, hashrateData); + updateDashboard(poolData); + updateDashboardCharts(hashrateData.stats); + }) + .catch(error => console.error("❌ Fetch error:", error)); +} + +function updateDashboardCharts(stats) { + if (!stats || stats.length === 0) { + console.warn("⚠️ No hashrate stats available."); + return; + } + + drawPoolHashrateChart(stats); + drawNetworkHashrateChart(stats); } function updateDashboard(data) { setText("pool-hash-rate", formatHashrate(data.pool.hashes_per_second)); - setText("pool-percentage", `${data.pool.pool_perecentage.toFixed(2)}%`); + setText("pool-percentage", `${data.pool.pool_percentage.toFixed(2)}%`); setText("pool-blocks-found", data.pool.blocks_found); setText("pool-avg-block-time", data.pool.avg_block_time); @@ -25,9 +38,13 @@ function updateDashboard(data) { setText("network-height", data.network.height); setText("network-last-block", formatDate(data.network.last_block)); - let blockReward = parseFloat(data.network.reward); - setText("miners-reward", `${(blockReward * 0.9).toFixed(2)} YDA`); - setText("master-nodes-reward", `${(blockReward * 0.1).toFixed(2)} YDA`); + let minerReward = parseFloat(data.network.latest_block_reward.miner_reward); + let mnTotalReward = parseFloat(data.network.latest_block_reward.masternodes_total); + let mnSingleReward = parseFloat(data.network.latest_block_reward.masternode_per_node); + + setText("miners-reward", `${minerReward.toFixed(6)} YDA`); + setText("master-nodes-reward", `${mnTotalReward.toFixed(6)} YDA`); + setText("single-mn-reward", `${mnSingleReward.toFixed(6)} YDA`); setText("pool-worker-count", data.pool.worker_count); setText("pool-miner-count", data.pool.miner_count); @@ -66,3 +83,200 @@ function formatDate(timestamp) { const date = new Date(timestamp * 1000); return `${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`; } + + +function drawPoolHashrateChart(stats) { + const times = []; + const poolHashRates = []; + const minersCount = []; + const workersCount = []; + + stats.forEach(entry => { + const timePoint = new Date(entry.time * 1000); + const formattedTime = timePoint.getHours() + ':' + String(timePoint.getMinutes()).padStart(2, '0'); + + times.push(formattedTime); + poolHashRates.push(entry.pool_hash_rate || 0); + minersCount.push(entry.miners || 0); + workersCount.push(entry.workers || 0); + }); + + const ctx = document.getElementById('hashrate-chart').getContext('2d'); + new Chart(ctx, { + type: 'bar', + data: { + labels: times.reverse(), + datasets: [ + { + label: 'Miners Count', + data: minersCount.reverse(), + type: 'line', + borderColor: '#007bff', + borderWidth: 2, + pointRadius: 0, + yAxisID: 'count' + }, + { + label: 'Workers Count', + data: workersCount.reverse(), + type: 'line', + borderColor: '#FF7F00', + borderWidth: 2, + pointRadius: 0, + yAxisID: 'count' + }, + { + label: 'Pool Hashrate', + data: poolHashRates.reverse(), + backgroundColor: '#333', + yAxisID: 'hashrate' + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { display: false }, + hashrate: { + position: 'right', + beginAtZero: true, + ticks: { + callback: value => formatHashrate(value), + font: { size: 10 } + }, + title: { display: true, text: 'Pool Hashrate', color: '#007bff' } + }, + count: { + position: 'left', + beginAtZero: true, + ticks: { + stepSize: 1, callback: value => value.toFixed(0), + font: { size: 10 } + }, + title: { display: true, text: 'Count (Miners / Workers)', color: '#007bff' } + } + }, + plugins: { + legend: { + display: true, + labels: { + usePointStyle: true, + } + }, + tooltip: { + mode: 'index', + intersect: false, + callbacks: { + title: function(tooltipItems) { + return `Time: ${tooltipItems[0].label}` + }, + label: function(tooltipItem) { + let datasetLabel = tooltipItem.dataset.label || '' + let value = tooltipItem.raw + if (tooltipItem.datasetIndex === 2) { + value = formatHashrate(value) + } + return `${datasetLabel}: ${value}` + } + } + }, + } + } + }); +} + +function drawNetworkHashrateChart(stats) { + const times = []; + const networkHashRates = []; + const avgDifficulties = []; + const difficulties = []; + + stats.forEach(entry => { + const timePoint = new Date(entry.time * 1000); + const formattedTime = timePoint.getHours() + ':' + String(timePoint.getMinutes()).padStart(2, '0'); + + times.push(formattedTime); + networkHashRates.push(entry.avg_network_hash_rate || 0); + difficulties.push(entry.net_difficulty || 0); + }); + + const ctx = document.getElementById('network-hashrate-chart').getContext('2d'); + new Chart(ctx, { + type: 'bar', + data: { + labels: times.reverse(), + datasets: [ + { + label: 'Difficulty', + data: difficulties.reverse(), + type: 'line', + borderColor: '#007bff', + borderWidth: 2, + pointRadius: 0, + yAxisID: 'difficulty' + }, + { + label: 'Network Hashrate', + data: networkHashRates.reverse(), + backgroundColor: '#333', + yAxisID: 'hashrate' + } + ] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { display: false }, + hashrate: { + position: 'right', + beginAtZero: true, + ticks: { + callback: value => formatHashrate(value), + font: { size: 10 } + }, + title: { display: true, text: 'Network Hashrate', color: '#007bff' } + }, + difficulty: { + position: 'left', + beginAtZero: true, + ticks: { + callback: value => value.toFixed(2), + font: { size: 10 } + }, + title: { display: true, text: 'Network Difficulty', color: '#007bff' } + } + }, + plugins: { + legend: { + display: true, + labels: { + usePointStyle: true, + } + }, + tooltip: { + mode: 'index', + intersect: false, + callbacks: { + title: function(tooltipItems) { + return `Time: ${tooltipItems[0].label}` + }, + label: function(tooltipItem) { + let datasetLabel = tooltipItem.dataset.label || '' + let value = tooltipItem.raw + + if (tooltipItem.datasetIndex === 1) { + value = formatHashrate(value); + } else if (tooltipItem.datasetIndex === 0) { + value = value.toFixed(2); + } + + return `${datasetLabel}: ${value}` + } + } + }, + } + } + }); +} diff --git a/plugins/yadacoinpool/static/style.css b/plugins/yadacoinpool/static/style.css index 250b3bd8..d11b275c 100755 --- a/plugins/yadacoinpool/static/style.css +++ b/plugins/yadacoinpool/static/style.css @@ -21,8 +21,8 @@ body { .box { background: #f8f9fa; border-radius: 8px; - padding: 15px; - min-height: 120px; + padding: 8px; + min-height: 135px; text-align: left; border: 1px solid #dee2e6; transition: all 0.3s ease-in-out; @@ -101,4 +101,4 @@ footer { .small-text { font-size: 0.85rem; -} \ No newline at end of file +} diff --git a/plugins/yadacoinpool/templates/pool-stats.html b/plugins/yadacoinpool/templates/pool-stats.html index c1c410b6..f83d1179 100644 --- a/plugins/yadacoinpool/templates/pool-stats.html +++ b/plugins/yadacoinpool/templates/pool-stats.html @@ -7,6 +7,7 @@ + @@ -50,5 +51,6 @@ + \ No newline at end of file diff --git a/yadacoin/app.py b/yadacoin/app.py index c2af92ef..b73e1b32 100644 --- a/yadacoin/app.py +++ b/yadacoin/app.py @@ -886,6 +886,20 @@ async def background_node_testing(self): finally: self.config.background_node_testing.busy = False + async def background_pool_info_checker(self): + self.config.app_log.debug("background_pool_info_checker") + if not hasattr(self.config, "background_pool_info_checker"): + self.config.background_pool_info_checker = WorkerVars(busy=False) + if self.config.background_pool_info_checker.busy: + return + self.config.background_pool_info_checker.busy = True + try: + await self.config.mp.update_pool_stats() + #await self.config.mp.update_miners_stats() + except Exception as e: + self.config.app_log.error(f"Error in background_pool_info_checker: {str(e)}") + self.config.background_pool_info_checker.busy = False + def configure_logging(self): # tornado.log.enable_pretty_logging() self.config.app_log = logging.getLogger("tornado.application") @@ -933,10 +947,14 @@ def init_config(self, options): f.write(self.config.to_json()) with open(options.config) as f: - self.config = yadacoin.core.config.Config(json.loads(f.read())) + config_data = json.loads(f.read()) + config_data.setdefault("pool_database", "yadacoinpool") + + self.config = yadacoin.core.config.Config(config_data) # Sets the global var for all objects yadacoin.core.config.CONFIG = self.config self.config.debug = options.debug + # force network, command line one takes precedence if options.network != "": self.config.network = options.network @@ -1036,6 +1054,11 @@ def init_ioloop(self): 60 * 60 * 1000, ).start() + PeriodicCallback( + self.background_pool_info_checker, + 8 * 60 * 1000 + ).start() + if self.config.pool_payout: self.config.app_log.info("PoolPayout activated") self.config.pp = PoolPayer() diff --git a/yadacoin/core/config.py b/yadacoin/core/config.py index d8165487..adc7e269 100644 --- a/yadacoin/core/config.py +++ b/yadacoin/core/config.py @@ -84,6 +84,7 @@ def __init__(self, config=None): self.mongodb_host = config["mongodb_host"] self.database = config["database"] self.site_database = config["site_database"] + self.pool_database = config["pool_database"] if config["peer_host"] == "0.0.0.0" or config["peer_host"] == "localhost": raise Exception( "Cannot use localhost or 0.0.0.0, must specify public ipv4 address" @@ -341,6 +342,7 @@ def generate( "fcm_key": "", "database": db_name or "yadacoin", "site_database": db_name + "site" if db_name else "yadacoinsite", + "pool_database": db_name + "pool" if db_name else "yadacoinpool", "mongodb_host": mongodb_host or "localhost", "mixpanel": "", "username": username or "", @@ -395,6 +397,7 @@ def from_dict(cls, config): cls.mongodb_host = config["mongodb_host"] cls.database = config["database"] cls.site_database = config["site_database"] + cls.pool_database = config["pool_database"] if config["peer_host"] == "0.0.0.0" or config["peer_host"] == "localhost": raise Exception( "cannot use localhost or 0.0.0.0, must specify public ipv4 address" @@ -567,6 +570,7 @@ def to_dict(self): "network": self.network, "database": self.database, "site_database": self.site_database, + "pool_database":self.pool_database, "peer_host": self.peer_host, "peer_port": self.peer_port, "peer_type": self.peer_type, diff --git a/yadacoin/core/miningpool.py b/yadacoin/core/miningpool.py index 538d9c75..c1b59a86 100644 --- a/yadacoin/core/miningpool.py +++ b/yadacoin/core/miningpool.py @@ -14,6 +14,7 @@ import json import random import uuid +from datetime import datetime from logging import getLogger from time import time @@ -22,6 +23,7 @@ from yadacoin.core.chain import CHAIN from yadacoin.core.config import Config from yadacoin.core.job import Job +from yadacoin.core.miner import Miner from yadacoin.core.peer import Peer from yadacoin.core.processingqueue import BlockProcessingQueueItem from yadacoin.core.transaction import Transaction @@ -104,7 +106,7 @@ async def process_nonce(self, miner, nonce, job, body): self.config.app_log.debug(f"Nonce for job {job.index}: {nonce}") hash1 = self.block_factory.generate_hash_from_header(job.index, header, nonce) - self.config.app_log.info(f"Hash1 for job {job.index}: {hash1}") + #self.config.app_log.info(f"Hash1 for job {job.index}: {hash1}") if self.block_factory.index >= CHAIN.BLOCK_V5_FORK: hash1_test = Blockchain.little_hash(hash1) @@ -667,3 +669,82 @@ async def accept_block(self, block): await self.config.websocketServer.send_block(block) await self.refresh() + + async def update_pool_stats(self): + expected_blocks = 144 + mining_time_interval = 1200 + pool_max_target = 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + + pipeline = [ + { + "$match": { + "time": {"$gte": time() - mining_time_interval} + } + }, + { + "$group": { + "_id": None, + "total_weight": {"$sum": "$weight"} + } + } + ] + + result = await self.config.mongo.async_db.shares.aggregate(pipeline).to_list(1) + pool_hash_rate = result[0]["total_weight"] / mining_time_interval if result else 0 + + daily_blocks_found = await self.config.mongo.async_db.blocks.count_documents( + {"time": {"$gte": time() - (600 * 144)}} + ) + avg_block_time = daily_blocks_found / expected_blocks * 600 + + if daily_blocks_found > 0: + net_target = self.config.LatestBlock.block.target + avg_blocks_found = self.config.mongo.async_db.blocks.find( + {"time": {"$gte": time() - (600 * 36)}}, + projection={"_id": 0, "target": 1} + ) + + avg_block_targets = [block["target"] async for block in avg_blocks_found] + if avg_block_targets: + avg_net_target = sum(int(target, 16) for target in avg_block_targets) / len(avg_block_targets) + avg_net_difficulty = ( + pool_max_target + / avg_net_target + ) + net_difficulty = ( + pool_max_target + / net_target + ) + avg_network_hash_rate = ( + len(avg_block_targets) + / 36 + * avg_net_difficulty + * 2**16 + / avg_block_time + ) + network_hash_rate = net_difficulty * 2**16 / 600 + else: + avg_network_hash_rate = 1 + net_difficulty = ( + pool_max_target + / pool_max_target + ) + network_hash_rate = 0 + + worker_count = len(StratumServer.inbound_streams[Miner.__name__].keys()) + miner_count = len(await Peer.get_miner_streams()) + + timestamp = int(time()) + date_value = datetime.utcfromtimestamp(timestamp) + + await self.config.mongo.async_pool_db.pool_hashrate_stats.insert_one({ + "pool_hash_rate": pool_hash_rate, + "network_hash_rate": network_hash_rate, + "net_difficulty": net_difficulty, + "avg_network_hash_rate": avg_network_hash_rate, + "workers": worker_count, + "miners": miner_count, + "time": timestamp, + "date": date_value, + }) + diff --git a/yadacoin/core/mongo.py b/yadacoin/core/mongo.py index f8c527c8..bcd3d214 100644 --- a/yadacoin/core/mongo.py +++ b/yadacoin/core/mongo.py @@ -46,9 +46,11 @@ def __init__(self): self.client = MongoClient(self.config.mongodb_host) self.db = self.client[self.config.database] self.site_db = self.client[self.config.site_database] + self.pool_db = self.client[self.config.pool_database] try: # test connection self.db.blocks.find_one() + self.pool_db.pool_blocks.find_one() except Exception as e: raise e @@ -434,6 +436,13 @@ def __init__(self): except: raise + __time = IndexModel([("time", DESCENDING)], name="__time") + __date = IndexModel([("date", DESCENDING)], name="__date", expireAfterSeconds=90000) + try: + self.pool_db.pool_hashrate_stats.create_indexes([__time, __date]) + except Exception as e: + self.config.app_log.error(f"Error creating indexes for pool_hashrate_stats: {e}") + # TODO: add indexes for peers if hasattr(self.config, "mongodb_username") and hasattr( @@ -452,6 +461,7 @@ def __init__(self): self.async_db = self.async_client[self.config.database] # self.async_db = self.async_client[self.config.database] self.async_site_db = self.async_client[self.config.site_database] + self.async_pool_db = self.async_client[self.config.pool_database] self.async_db.slow_queries = [] # convert block time from string to number blocks_to_convert = self.db.blocks.find({"time": {"$type": 2}}) diff --git a/yadacoin/http/pool.py b/yadacoin/http/pool.py index 31099228..82721258 100644 --- a/yadacoin/http/pool.py +++ b/yadacoin/http/pool.py @@ -154,6 +154,22 @@ async def get(self): self.render_as_json({"payouts": payouts}) +class PoolHashrateStatsHandler(BaseHandler): + async def get(self): + stats_cursor = ( + self.config.mongo.async_pool_db.pool_hashrate_stats.find({}, {"_id": 0}) + .sort([("time", -1)]) + .limit(180) + ) + + stats = await stats_cursor.to_list(length=180) + + if not stats: + return self.render_as_json({"error": "No hashrate stats available."}, status=404) + + return self.render_as_json({"stats": stats}) + + class PoolScanMissedPayoutsHandler(BaseHandler): async def get(self): start_index = self.get_query_argument("start_index") @@ -169,6 +185,7 @@ async def get(self): POOL_HANDLERS = [ (r"/miner-stats", MinerStatsHandler), (r"/miner-payouts", MinerPayoutsHandler), + (r"/pool-hashrate-stats", PoolHashrateStatsHandler), (r"/scan-missed-payouts", PoolScanMissedPayoutsHandler), # (r"/force-refresh", PoolForceRefresh), ]