#!/usr/bin/env python3 """ RinCoin Mining Pool Web Interface Provides web dashboard for pool statistics and miner management """ import json import sqlite3 import requests from datetime import datetime, timedelta from http.server import HTTPServer, BaseHTTPRequestHandler import threading import time from requests.auth import HTTPBasicAuth class PoolWebInterface: def __init__(self, pool_db, host='0.0.0.0', port=8080, rpc_host='127.0.0.1', rpc_port=9556, rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90'): self.pool_db = pool_db self.host = host self.port = port self.rpc_host = rpc_host self.rpc_port = rpc_port self.rpc_user = rpc_user self.rpc_password = rpc_password self.chart_time_window = 3600 # 1 hour default, adjustable def set_chart_time_window(self, seconds): """Set the chart time window""" self.chart_time_window = seconds def format_hashrate(self, hashrate): """Format hashrate in human readable format""" if hashrate >= 1e12: return f"{hashrate/1e12:.2f} TH/s" elif hashrate >= 1e9: return f"{hashrate/1e9:.2f} GH/s" elif hashrate >= 1e6: return f"{hashrate/1e6:.2f} MH/s" elif hashrate >= 1e3: return f"{hashrate/1e3:.2f} KH/s" elif hashrate >= 0.01: return f"{hashrate:.2f} H/s" elif hashrate > 0: return f"{hashrate*1000:.2f} mH/s" else: return "0.00 H/s" def get_pool_balance(self): """Get pool wallet balance via RPC""" try: url = f"http://{self.rpc_host}:{self.rpc_port}/" headers = {'content-type': 'text/plain'} auth = HTTPBasicAuth(self.rpc_user, self.rpc_password) payload = { "jsonrpc": "1.0", "id": "pool_balance", "method": "getbalance", "params": [] } response = requests.post(url, json=payload, headers=headers, auth=auth, timeout=10) if response.status_code == 200: result = response.json() if 'error' in result and result['error'] is not None: print(f"RPC Error getting balance: {result['error']}") return 0.0 balance = result.get('result', 0) return float(balance) / 100000000 # Convert from satoshis to RIN else: print(f"HTTP Error getting balance: {response.status_code}") return 0.0 except Exception as e: print(f"Error getting pool balance: {e}") return 0.0 def get_pool_stats(self): """Get current pool statistics""" try: cursor = self.pool_db.cursor() # Total miners (ever registered) cursor.execute('SELECT COUNT(DISTINCT id) FROM miners') total_miners = cursor.fetchone()[0] # Active miners (last 5 minutes) cursor.execute(''' SELECT COUNT(DISTINCT m.id) FROM miners m JOIN shares s ON m.id = s.miner_id WHERE s.submitted > datetime('now', '-5 minutes') ''') active_miners = cursor.fetchone()[0] # Total shares (last 24 hours) cursor.execute(''' SELECT COUNT(*) FROM shares WHERE submitted > datetime('now', '-24 hours') ''') total_shares_24h = cursor.fetchone()[0] # Pool hashrate: sum of miners.last_hashrate (instantaneous) cursor.execute('SELECT COALESCE(SUM(last_hashrate), 0) FROM miners') hashrate = cursor.fetchone()[0] or 0.0 # Debug stats cursor.execute(''' SELECT SUM(difficulty), COUNT(*) FROM shares WHERE submitted > datetime('now', '-5 minutes') ''') rd = cursor.fetchone() recent_difficulty = rd[0] if rd and rd[0] else 0 recent_share_count = rd[1] if rd and rd[1] else 0 # Get historical hashrate data for chart cursor.execute(''' SELECT strftime('%H:%M', submitted) as time, COUNT(*) as shares, SUM(difficulty) as total_difficulty FROM shares WHERE submitted > datetime('now', '-{} seconds') GROUP BY strftime('%Y-%m-%d %H:%M', submitted) ORDER BY submitted DESC LIMIT 60 '''.format(self.chart_time_window)) historical_data = cursor.fetchall() # Calculate individual miner hashrates cursor.execute(''' SELECT m.user, m.worker, COUNT(s.id) as shares, SUM(s.difficulty) as total_difficulty, m.last_share FROM miners m LEFT JOIN shares s ON m.id = s.miner_id AND s.submitted > datetime('now', '-5 minutes') GROUP BY m.id, m.user, m.worker ORDER BY shares DESC ''') miner_stats = cursor.fetchall() # Calculate individual hashrates (use miners.last_hashrate) miner_hashrates = [] for user, worker, shares, difficulty, last_share in miner_stats: cursor.execute('SELECT last_hashrate FROM miners WHERE user = ? AND worker = ? LIMIT 1', (user, worker)) row = cursor.fetchone() miner_hashrate = row[0] if row and row[0] else 0.0 miner_hashrates.append((user, worker, shares, miner_hashrate, last_share)) # Total blocks found cursor.execute('SELECT COUNT(*) FROM blocks') total_blocks = cursor.fetchone()[0] # Recent blocks cursor.execute(''' SELECT block_hash, height, reward, found_at FROM blocks ORDER BY found_at DESC LIMIT 10 ''') recent_blocks = cursor.fetchall() # Top miners (last 24 hours) - show all miners, even without shares cursor.execute(''' SELECT m.user, m.worker, COALESCE(COUNT(s.id), 0) as shares, m.last_share, m.created FROM miners m LEFT JOIN shares s ON m.id = s.miner_id AND s.submitted > datetime('now', '-24 hours') GROUP BY m.id, m.user, m.worker ORDER BY shares DESC, m.created DESC LIMIT 20 ''') top_miners = cursor.fetchall() # All active miners (for better visibility) cursor.execute(''' SELECT user, worker, created, last_share FROM miners ORDER BY created DESC LIMIT 10 ''') all_miners = cursor.fetchall() # Get pool balance pool_balance = self.get_pool_balance() return { 'total_miners': total_miners, 'active_miners': active_miners, 'total_shares_24h': total_shares_24h, 'hashrate': hashrate, 'total_blocks': total_blocks, 'recent_blocks': recent_blocks, 'top_miners': top_miners, 'all_miners': all_miners, 'miner_hashrates': miner_hashrates, 'historical_data': historical_data, 'pool_balance': pool_balance, 'debug': { 'recent_difficulty': recent_difficulty, 'recent_share_count': recent_share_count, 'total_shares_24h': total_shares_24h } } except Exception as e: print(f"Error getting pool stats: {e}") return {} def generate_html(self, stats): """Generate HTML dashboard""" html = f""" RinCoin Mining Pool

šŸŠā€ā™‚ļø RinCoin Mining Pool

Distribute block rewards among multiple miners

{stats.get('total_miners', 0)}
Total Miners
{stats.get('active_miners', 0)}
Active Miners
{self.format_hashrate(stats.get('hashrate', 0))}
Hashrate
{stats.get('total_blocks', 0)}
Blocks Found
{stats.get('pool_balance', 0):.2f}
Pool Balance (RIN)

šŸ“Š Pool Statistics

24h Shares: {stats.get('total_shares_24h', 0):,}

Pool Fee: 1%

Pool Balance: {stats.get('pool_balance', 0):.8f} RIN

Connection String: stratum+tcp://YOUR_IP:3333

šŸ” Debug Info

Recent Difficulty (5min): {stats.get('debug', {}).get('recent_difficulty', 0):.6f}

Recent Share Count (5min): {stats.get('debug', {}).get('recent_share_count', 0)}

Total Shares (24h): {stats.get('debug', {}).get('total_shares_24h', 0):,}

Active Miners: {stats.get('active_miners', 0)} (last 5 minutes)

Total Miners: {stats.get('total_miners', 0)} (ever registered)

šŸ“ˆ Hashrate Chart

šŸ‘„ Connected Miners

""" for miner in stats.get('all_miners', []): user, worker, created, last_share = miner html += f""" """ if not stats.get('all_miners', []): html += """ """ html += """
User Worker Connected Last Share
{user} {worker} {created} {last_share or 'Never'}
No miners connected

šŸ† Top Miners (24h Shares)

""" for miner in stats.get('miner_hashrates', []): user, worker, shares, hashrate, last_share = miner html += f""" """ if not stats.get('top_miners', []): html += """ """ html += """
User Worker Shares Hashrate Last Share
{user} {worker} {shares:,} {self.format_hashrate(hashrate)} {last_share or 'Never'}
No shares submitted yet

šŸ† Recent Blocks

""" for block in stats.get('recent_blocks', []): block_hash, height, reward, found_at = block html += f""" """ html += """
Height Hash Reward Found At
{height} {block_hash[:16]}... {reward:.8f} RIN {found_at}

šŸ”— Connect to Pool

Use any RinHash-compatible miner:

./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u username.workername -p x

Replace YOUR_IP with your server's public IP address

""" return html class PoolWebHandler(BaseHTTPRequestHandler): def __init__(self, *args, pool_interface=None, **kwargs): self.pool_interface = pool_interface super().__init__(*args, **kwargs) def do_GET(self): if self.path == '/': stats = self.pool_interface.get_pool_stats() html = self.pool_interface.generate_html(stats) self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(html.encode('utf-8')) elif self.path == '/api/stats': stats = self.pool_interface.get_pool_stats() self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(stats).encode('utf-8')) else: self.send_response(404) self.end_headers() self.wfile.write(b'Not Found') def log_message(self, format, *args): # Suppress access logs pass def start_web_interface(pool_db, host='0.0.0.0', port=8083, rpc_host='127.0.0.1', rpc_port=9556, rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90'): """Start the web interface server""" interface = PoolWebInterface(pool_db, host, port, rpc_host, rpc_port, rpc_user, rpc_password) class Handler(PoolWebHandler): def __init__(self, *args, **kwargs): super().__init__(*args, pool_interface=interface, **kwargs) try: server = HTTPServer((host, port), Handler) print(f"🌐 Web interface running on http://{host}:{port}") print("Press Ctrl+C to stop") server.serve_forever() except OSError as e: if "Address already in use" in str(e): print(f"āš ļø Port {port} is already in use, web interface not started") print(f"šŸ’” Try a different port or kill the process using port {port}") else: print(f"āŒ Failed to start web interface: {e}") except KeyboardInterrupt: print("\nšŸ›‘ Shutting down web interface...") server.shutdown() if __name__ == "__main__": # This would be called from the main pool server print("Web interface module loaded")