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 @@
+