stratum working, implement mining pool, support "extranonce"
This commit is contained in:
271
MINE/rin/pool_web_interface.py
Normal file
271
MINE/rin/pool_web_interface.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
RinCoin Mining Pool Web Interface
|
||||
Provides web dashboard for pool statistics and miner management
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import threading
|
||||
import time
|
||||
|
||||
class PoolWebInterface:
|
||||
def __init__(self, pool_db, host='0.0.0.0', port=8080):
|
||||
self.pool_db = pool_db
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def get_pool_stats(self):
|
||||
"""Get current pool statistics"""
|
||||
try:
|
||||
cursor = self.pool_db.cursor()
|
||||
|
||||
# Total miners
|
||||
cursor.execute('SELECT COUNT(DISTINCT id) FROM miners')
|
||||
total_miners = cursor.fetchone()[0]
|
||||
|
||||
# Active miners (last 10 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', '-10 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 estimate
|
||||
cursor.execute('''
|
||||
SELECT COUNT(*) FROM shares
|
||||
WHERE submitted > datetime('now', '-5 minutes')
|
||||
''')
|
||||
recent_shares = cursor.fetchone()[0]
|
||||
hashrate = recent_shares * 12 # Rough estimate
|
||||
|
||||
# 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
|
||||
cursor.execute('''
|
||||
SELECT m.user, m.worker, COUNT(s.id) as shares, m.last_share
|
||||
FROM miners m
|
||||
JOIN shares s ON m.id = s.miner_id
|
||||
WHERE s.submitted > datetime('now', '-24 hours')
|
||||
GROUP BY m.id
|
||||
ORDER BY shares DESC
|
||||
LIMIT 10
|
||||
''')
|
||||
top_miners = cursor.fetchall()
|
||||
|
||||
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
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error getting pool stats: {e}")
|
||||
return {}
|
||||
|
||||
def generate_html(self, stats):
|
||||
"""Generate HTML dashboard"""
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RinCoin Mining Pool</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }}
|
||||
.container {{ max-width: 1200px; margin: 0 auto; }}
|
||||
.header {{ background: #2c3e50; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
|
||||
.stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }}
|
||||
.stat-card {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
|
||||
.stat-value {{ font-size: 2em; font-weight: bold; color: #3498db; }}
|
||||
.stat-label {{ color: #7f8c8d; margin-top: 5px; }}
|
||||
.section {{ background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }}
|
||||
.section h2 {{ margin-top: 0; color: #2c3e50; }}
|
||||
table {{ width: 100%; border-collapse: collapse; }}
|
||||
th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
|
||||
th {{ background: #f8f9fa; font-weight: bold; }}
|
||||
.block-hash {{ font-family: monospace; font-size: 0.9em; }}
|
||||
.refresh-btn {{ background: #3498db; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; }}
|
||||
.refresh-btn:hover {{ background: #2980b9; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🏊♂️ RinCoin Mining Pool</h1>
|
||||
<p>Distribute block rewards among multiple miners</p>
|
||||
</div>
|
||||
|
||||
<button class="refresh-btn" onclick="location.reload()">🔄 Refresh</button>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{stats.get('total_miners', 0)}</div>
|
||||
<div class="stat-label">Total Miners</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{stats.get('active_miners', 0)}</div>
|
||||
<div class="stat-label">Active Miners</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{stats.get('hashrate', 0):.2f}</div>
|
||||
<div class="stat-label">Hashrate (H/s)</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{stats.get('total_blocks', 0)}</div>
|
||||
<div class="stat-label">Blocks Found</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>📊 Pool Statistics</h2>
|
||||
<p><strong>24h Shares:</strong> {stats.get('total_shares_24h', 0):,}</p>
|
||||
<p><strong>Pool Fee:</strong> 1%</p>
|
||||
<p><strong>Connection String:</strong> <code>stratum+tcp://YOUR_IP:3333</code></p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>👥 Top Miners (24h)</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Worker</th>
|
||||
<th>Shares</th>
|
||||
<th>Last Share</th>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
for miner in stats.get('top_miners', []):
|
||||
user, worker, shares, last_share = miner
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{user}</td>
|
||||
<td>{worker}</td>
|
||||
<td>{shares:,}</td>
|
||||
<td>{last_share or 'Never'}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>🏆 Recent Blocks</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Height</th>
|
||||
<th>Hash</th>
|
||||
<th>Reward</th>
|
||||
<th>Found At</th>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
for block in stats.get('recent_blocks', []):
|
||||
block_hash, height, reward, found_at = block
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{height}</td>
|
||||
<td class="block-hash">{block_hash[:16]}...</td>
|
||||
<td>{reward:.8f} RIN</td>
|
||||
<td>{found_at}</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>🔗 Connect to Pool</h2>
|
||||
<p>Use any RinHash-compatible miner:</p>
|
||||
<pre><code>./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u username.workername -p x</code></pre>
|
||||
<p><strong>Replace YOUR_IP with your server's public IP address</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-refresh every 30 seconds
|
||||
setTimeout(() => location.reload(), 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
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=8080):
|
||||
"""Start the web interface server"""
|
||||
interface = PoolWebInterface(pool_db, host, port)
|
||||
|
||||
class Handler(PoolWebHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, pool_interface=interface, **kwargs)
|
||||
|
||||
server = HTTPServer((host, port), Handler)
|
||||
print(f"🌐 Web interface running on http://{host}:{port}")
|
||||
print("Press Ctrl+C to stop")
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
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")
|
||||
Reference in New Issue
Block a user