diff --git a/mining_pool.py b/mining_pool.py
new file mode 100644
index 00000000..f008c597
--- /dev/null
+++ b/mining_pool.py
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: MIT
+# SPDX-License-Identifier: MIT
+
+import sqlite3
+import hashlib
+import time
+import json
+import logging
+from flask import Flask, request, jsonify, render_template_string
+from datetime import datetime, timedelta
+import threading
+from contextlib import contextmanager
+
+app = Flask(__name__)
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+DB_PATH = 'mining_pool.db'
+POOL_FEE = 0.02 # 2% pool fee
+MIN_PAYOUT = 1.0 # Minimum payout threshold
+DIFFICULTY_TARGET = 0x00000fffff000000000000000000000000000000000000000000000000000000
+
+@contextmanager
+def get_db():
+ conn = sqlite3.connect(DB_PATH)
+ try:
+ yield conn
+ finally:
+ conn.close()
+
+def init_db():
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS miners (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ wallet_address TEXT UNIQUE NOT NULL,
+ nickname TEXT,
+ registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ total_shares INTEGER DEFAULT 0,
+ total_rewards REAL DEFAULT 0.0,
+ pending_balance REAL DEFAULT 0.0,
+ status TEXT DEFAULT 'active'
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS shares (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_id INTEGER,
+ block_hash TEXT,
+ nonce TEXT,
+ difficulty REAL,
+ submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ is_valid BOOLEAN DEFAULT 0,
+ is_block BOOLEAN DEFAULT 0,
+ reward_amount REAL DEFAULT 0.0,
+ FOREIGN KEY (miner_id) REFERENCES miners (id)
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS payouts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_id INTEGER,
+ amount REAL,
+ txn_hash TEXT,
+ status TEXT DEFAULT 'pending',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ processed_at TIMESTAMP,
+ FOREIGN KEY (miner_id) REFERENCES miners (id)
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS pool_stats (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ total_hashrate REAL DEFAULT 0.0,
+ active_miners INTEGER DEFAULT 0,
+ blocks_found INTEGER DEFAULT 0,
+ total_rewards REAL DEFAULT 0.0,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ conn.commit()
+
+def validate_share(block_hash, nonce, difficulty):
+ """Validate submitted mining share"""
+ try:
+ hash_input = f"{block_hash}{nonce}".encode()
+ result_hash = hashlib.sha256(hash_input).hexdigest()
+
+ # Convert to integer for comparison
+ hash_int = int(result_hash, 16)
+ target = int(difficulty)
+
+ return hash_int < target
+ except Exception as e:
+ logger.error(f"Share validation error: {e}")
+ return False
+
+def calculate_reward_distribution():
+ """Calculate and distribute rewards based on shares"""
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ # Get recent shares (last 24 hours)
+ cursor.execute('''
+ SELECT m.id, m.wallet_address, COUNT(s.id) as share_count
+ FROM miners m
+ JOIN shares s ON m.id = s.miner_id
+ WHERE s.submitted_at > datetime('now', '-24 hours')
+ AND s.is_valid = 1
+ GROUP BY m.id, m.wallet_address
+ ''')
+
+ share_data = cursor.fetchall()
+ if not share_data:
+ return
+
+ total_shares = sum(row[2] for row in share_data)
+
+ # Get pending block rewards
+ cursor.execute('''
+ SELECT SUM(reward_amount) FROM shares
+ WHERE is_block = 1 AND reward_amount > 0
+ AND submitted_at > datetime('now', '-24 hours')
+ ''')
+
+ result = cursor.fetchone()
+ total_reward = result[0] if result[0] else 0.0
+
+ if total_reward > 0:
+ pool_fee_amount = total_reward * POOL_FEE
+ miner_reward = total_reward - pool_fee_amount
+
+ # Distribute rewards proportionally
+ for miner_id, wallet, share_count in share_data:
+ miner_portion = (share_count / total_shares) * miner_reward
+
+ cursor.execute('''
+ UPDATE miners
+ SET pending_balance = pending_balance + ?,
+ total_rewards = total_rewards + ?
+ WHERE id = ?
+ ''', (miner_portion, miner_portion, miner_id))
+
+ conn.commit()
+ logger.info(f"Distributed {miner_reward} RTC among {len(share_data)} miners")
+
+@app.route('/')
+def dashboard():
+ template = '''
+
+
+
+ RustChain Mining Pool
+
+
+
+
+
⛏️ RustChain Mining Pool
+
+
+
+
Pool Stats
+
Active Miners: {{ stats.active_miners }}
+
Total Hashrate: {{ "%.2f"|format(stats.total_hashrate) }} H/s
+
Blocks Found: {{ stats.blocks_found }}
+
Total Rewards: {{ "%.4f"|format(stats.total_rewards) }} RTC
+
+
+
+
Top Miners (24h)
+
+
+
+ | Wallet |
+ Nickname |
+ Shares |
+ Pending Balance |
+ Last Active |
+
+
+
+ {% for miner in miners %}
+
+ | {{ miner[1][:12] }}... |
+ {{ miner[2] or 'Anonymous' }} |
+ {{ miner[6] }} |
+ {{ "%.4f"|format(miner[7]) }} RTC |
+ {{ miner[4] }} |
+
+ {% endfor %}
+
+
+
+
API Endpoints
+
+ - POST /register - Register new miner
+ - POST /submit - Submit mining share
+ - GET /stats/<wallet> - Get miner stats
+ - POST /payout - Request payout
+
+
+
+
+ '''
+
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ # Get pool stats
+ cursor.execute('''
+ SELECT active_miners, total_hashrate, blocks_found, total_rewards
+ FROM pool_stats ORDER BY updated_at DESC LIMIT 1
+ ''')
+ stats_row = cursor.fetchone()
+ stats = {
+ 'active_miners': stats_row[0] if stats_row else 0,
+ 'total_hashrate': stats_row[1] if stats_row else 0.0,
+ 'blocks_found': stats_row[2] if stats_row else 0,
+ 'total_rewards': stats_row[3] if stats_row else 0.0
+ }
+
+ # Get top miners
+ cursor.execute('''
+ SELECT m.*, COUNT(s.id) as recent_shares, m.pending_balance
+ FROM miners m
+ LEFT JOIN shares s ON m.id = s.miner_id
+ AND s.submitted_at > datetime('now', '-24 hours')
+ GROUP BY m.id
+ ORDER BY recent_shares DESC, m.total_shares DESC
+ LIMIT 10
+ ''')
+ miners = cursor.fetchall()
+
+ return render_template_string(template, stats=stats, miners=miners)
+
+@app.route('/register', methods=['POST'])
+def register_miner():
+ """Register new miner in pool"""
+ data = request.get_json()
+ if not data or 'wallet_address' not in data:
+ return jsonify({'error': 'wallet_address required'}), 400
+
+ wallet_address = data['wallet_address']
+ nickname = data.get('nickname', '')
+
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ try:
+ cursor.execute('''
+ INSERT INTO miners (wallet_address, nickname)
+ VALUES (?, ?)
+ ''', (wallet_address, nickname))
+
+ miner_id = cursor.lastrowid
+ conn.commit()
+
+ logger.info(f"New miner registered: {wallet_address[:12]}...")
+ return jsonify({
+ 'success': True,
+ 'miner_id': miner_id,
+ 'message': 'Miner registered successfully'
+ })
+
+ except sqlite3.IntegrityError:
+ return jsonify({'error': 'Wallet already registered'}), 409
+
+@app.route('/submit', methods=['POST'])
+def submit_share():
+ """Submit mining share for validation"""
+ data = request.get_json()
+ required_fields = ['wallet_address', 'block_hash', 'nonce', 'difficulty']
+
+ if not data or not all(field in data for field in required_fields):
+ return jsonify({'error': 'Missing required fields'}), 400
+
+ wallet_address = data['wallet_address']
+ block_hash = data['block_hash']
+ nonce = data['nonce']
+ difficulty = float(data['difficulty'])
+
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ # Get miner ID
+ cursor.execute('SELECT id FROM miners WHERE wallet_address = ?', (wallet_address,))
+ miner_row = cursor.fetchone()
+
+ if not miner_row:
+ return jsonify({'error': 'Miner not registered'}), 404
+
+ miner_id = miner_row[0]
+
+ # Validate share
+ is_valid = validate_share(block_hash, nonce, difficulty)
+ is_block = difficulty >= DIFFICULTY_TARGET if is_valid else False
+ reward_amount = 50.0 if is_block else 0.0 # Block reward
+
+ # Store share
+ cursor.execute('''
+ INSERT INTO shares (miner_id, block_hash, nonce, difficulty, is_valid, is_block, reward_amount)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ''', (miner_id, block_hash, nonce, difficulty, is_valid, is_block, reward_amount))
+
+ if is_valid:
+ # Update miner stats
+ cursor.execute('''
+ UPDATE miners
+ SET total_shares = total_shares + 1, last_active = CURRENT_TIMESTAMP
+ WHERE id = ?
+ ''', (miner_id,))
+
+ conn.commit()
+
+ response = {
+ 'valid': is_valid,
+ 'block_found': is_block,
+ 'reward': reward_amount
+ }
+
+ if is_block:
+ logger.info(f"BLOCK FOUND by {wallet_address[:12]}... - Reward: {reward_amount} RTC")
+
+ return jsonify(response)
+
+@app.route('/stats/')
+def get_miner_stats(wallet_address):
+ """Get miner statistics"""
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT m.*, COUNT(s.id) as total_shares_db,
+ COUNT(CASE WHEN s.is_valid = 1 THEN 1 END) as valid_shares,
+ COUNT(CASE WHEN s.is_block = 1 THEN 1 END) as blocks_found
+ FROM miners m
+ LEFT JOIN shares s ON m.id = s.miner_id
+ WHERE m.wallet_address = ?
+ GROUP BY m.id
+ ''', (wallet_address,))
+
+ result = cursor.fetchone()
+ if not result:
+ return jsonify({'error': 'Miner not found'}), 404
+
+ # Get recent shares (24h)
+ cursor.execute('''
+ SELECT COUNT(*) FROM shares s
+ JOIN miners m ON s.miner_id = m.id
+ WHERE m.wallet_address = ?
+ AND s.submitted_at > datetime('now', '-24 hours')
+ AND s.is_valid = 1
+ ''', (wallet_address,))
+
+ recent_shares = cursor.fetchone()[0]
+
+ stats = {
+ 'wallet_address': result[1],
+ 'nickname': result[2],
+ 'registered_at': result[3],
+ 'total_shares': result[5],
+ 'valid_shares': result[7],
+ 'blocks_found': result[8],
+ 'total_rewards': result[6],
+ 'pending_balance': result[7],
+ 'recent_shares_24h': recent_shares,
+ 'status': result[8]
+ }
+
+ return jsonify(stats)
+
+@app.route('/payout', methods=['POST'])
+def request_payout():
+ """Process payout request"""
+ data = request.get_json()
+ if not data or 'wallet_address' not in data:
+ return jsonify({'error': 'wallet_address required'}), 400
+
+ wallet_address = data['wallet_address']
+
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT id, pending_balance FROM miners WHERE wallet_address = ?
+ ''', (wallet_address,))
+
+ result = cursor.fetchone()
+ if not result:
+ return jsonify({'error': 'Miner not found'}), 404
+
+ miner_id, pending_balance = result
+
+ if pending_balance < MIN_PAYOUT:
+ return jsonify({
+ 'error': f'Minimum payout is {MIN_PAYOUT} RTC',
+ 'pending_balance': pending_balance
+ }), 400
+
+ # Create payout record
+ payout_hash = hashlib.sha256(f"{wallet_address}{time.time()}".encode()).hexdigest()
+
+ cursor.execute('''
+ INSERT INTO payouts (miner_id, amount, txn_hash, status)
+ VALUES (?, ?, ?, 'processing')
+ ''', (miner_id, pending_balance, payout_hash))
+
+ # Reset pending balance
+ cursor.execute('''
+ UPDATE miners SET pending_balance = 0.0 WHERE id = ?
+ ''', (miner_id,))
+
+ conn.commit()
+
+ logger.info(f"Payout requested: {pending_balance} RTC to {wallet_address[:12]}...")
+
+ return jsonify({
+ 'success': True,
+ 'amount': pending_balance,
+ 'txn_hash': payout_hash,
+ 'status': 'processing'
+ })
+
+def update_pool_stats():
+ """Background task to update pool statistics"""
+ while True:
+ try:
+ with get_db() as conn:
+ cursor = conn.cursor()
+
+ # Count active miners (active in last hour)
+ cursor.execute('''
+ SELECT COUNT(*) FROM miners
+ WHERE last_active > datetime('now', '-1 hour')
+ ''')
+ active_miners = cursor.fetchone()[0]
+
+ # Calculate total blocks found
+ cursor.execute('SELECT COUNT(*) FROM shares WHERE is_block = 1')
+ blocks_found = cursor.fetchone()[0]
+
+ # Calculate total rewards distributed
+ cursor.execute('SELECT SUM(total_rewards) FROM miners')
+ result = cursor.fetchone()
+ total_rewards = result[0] if result[0] else 0.0
+
+ # Estimate hashrate based on recent shares
+ cursor.execute('''
+ SELECT COUNT(*) FROM shares
+ WHERE submitted_at > datetime('now', '-10 minutes')
+ AND is_valid = 1
+ ''')
+ recent_shares = cursor.fetchone()[0]
+ estimated_hashrate = recent_shares * 6 # Rough estimate
+
+ # Update or insert stats
+ cursor.execute('''
+ INSERT OR REPLACE INTO pool_stats
+ (id, active_miners, total_hashrate, blocks_found, total_rewards, updated_at)
+ VALUES (1, ?, ?, ?, ?, CURRENT_TIMESTAMP)
+ ''', (active_miners, estimated_hashrate, blocks_found, total_rewards))
+
+ conn.commit()
+
+ except Exception as e:
+ logger.error(f"Stats update error: {e}")
+
+ time.sleep(60) # Update every minute
+
+if __name__ == '__main__':
+ init_db()
+
+ # Start background stats updater
+ stats_thread = threading.Thread(target=update_pool_stats, daemon=True)
+ stats_thread.start()
+
+ # Start reward distribution scheduler
+ def reward_scheduler():
+ while True:
+ try:
+ calculate_reward_distribution()
+ time.sleep(3600) # Run every hour
+ except Exception as e:
+ logger.error(f"Reward distribution error: {e}")
+ time.sleep(300) # Retry in 5 minutes
+
+ reward_thread = threading.Thread(target=reward_scheduler, daemon=True)
+ reward_thread.start()
+
+ logger.info("Mining pool server starting...")
+ app.run(host='0.0.0.0', port=8080, debug=False)
diff --git a/pool_cli.py b/pool_cli.py
new file mode 100644
index 00000000..4f3adf65
--- /dev/null
+++ b/pool_cli.py
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: MIT
+# SPDX-License-Identifier: MIT
+
+import sqlite3
+import argparse
+import sys
+import json
+from datetime import datetime, timedelta
+from typing import Dict, List, Optional
+
+DB_PATH = 'rustchain.db'
+
+class PoolCLI:
+ def __init__(self):
+ self.ensure_pool_tables()
+
+ def ensure_pool_tables(self):
+ """Initialize pool-related database tables if they don't exist"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ # Pool configuration table
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS pool_config (
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ # Pool statistics table
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS pool_stats (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ total_miners INTEGER DEFAULT 0,
+ active_miners INTEGER DEFAULT 0,
+ total_hashrate REAL DEFAULT 0.0,
+ blocks_found INTEGER DEFAULT 0,
+ total_shares INTEGER DEFAULT 0,
+ pool_fee REAL DEFAULT 0.0
+ )
+ ''')
+
+ # Miner shares tracking
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS miner_shares (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_address TEXT NOT NULL,
+ shares INTEGER DEFAULT 0,
+ difficulty REAL DEFAULT 0.0,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ block_height INTEGER DEFAULT 0
+ )
+ ''')
+
+ # Payout history
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS payout_history (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_address TEXT NOT NULL,
+ amount REAL NOT NULL,
+ transaction_hash TEXT,
+ status TEXT DEFAULT 'pending',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ processed_at TIMESTAMP
+ )
+ ''')
+
+ conn.commit()
+
+ def set_config(self, key: str, value: str):
+ """Set pool configuration value"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ INSERT OR REPLACE INTO pool_config (key, value, updated_at)
+ VALUES (?, ?, ?)
+ ''', (key, value, datetime.now().isoformat()))
+ conn.commit()
+ print(f"Set {key} = {value}")
+
+ def get_config(self, key: str) -> Optional[str]:
+ """Get pool configuration value"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('SELECT value FROM pool_config WHERE key = ?', (key,))
+ result = cursor.fetchone()
+ return result[0] if result else None
+
+ def show_config(self):
+ """Display all pool configuration"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('SELECT key, value, updated_at FROM pool_config ORDER BY key')
+ configs = cursor.fetchall()
+
+ if not configs:
+ print("No configuration found")
+ return
+
+ print("Pool Configuration:")
+ print("-" * 50)
+ for key, value, updated in configs:
+ print(f"{key:<20}: {value} (updated: {updated})")
+
+ def show_stats(self, hours: int = 24):
+ """Show pool statistics for the last N hours"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ # Get latest stats
+ cursor.execute('''
+ SELECT * FROM pool_stats
+ ORDER BY timestamp DESC LIMIT 1
+ ''')
+ latest = cursor.fetchone()
+
+ if not latest:
+ print("No pool statistics available")
+ return
+
+ # Get stats from N hours ago
+ cutoff = datetime.now() - timedelta(hours=hours)
+ cursor.execute('''
+ SELECT AVG(total_hashrate), SUM(blocks_found), COUNT(*) as entries
+ FROM pool_stats
+ WHERE timestamp >= ?
+ ''', (cutoff.isoformat(),))
+ period_stats = cursor.fetchone()
+
+ print(f"Pool Statistics (last {hours} hours):")
+ print("-" * 40)
+ print(f"Active Miners : {latest[3]}")
+ print(f"Total Miners : {latest[2]}")
+ print(f"Current Hashrate : {latest[4]:.2f} H/s")
+ print(f"Average Hashrate : {period_stats[0]:.2f} H/s" if period_stats[0] else "0.00 H/s")
+ print(f"Blocks Found : {period_stats[1] or 0}")
+ print(f"Pool Fee : {latest[6]:.2f}%")
+ print(f"Total Shares : {latest[5]}")
+
+ def show_miners(self, limit: int = 20):
+ """Show top miners by shares"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT miner_address, SUM(shares) as total_shares,
+ COUNT(*) as submissions, MAX(timestamp) as last_seen
+ FROM miner_shares
+ GROUP BY miner_address
+ ORDER BY total_shares DESC
+ LIMIT ?
+ ''', (limit,))
+
+ miners = cursor.fetchall()
+
+ if not miners:
+ print("No miners found")
+ return
+
+ print(f"Top {limit} Miners:")
+ print("-" * 80)
+ print(f"{'Address':<42} {'Shares':<10} {'Submissions':<12} {'Last Seen':<15}")
+ print("-" * 80)
+
+ for address, shares, subs, last_seen in miners:
+ short_addr = f"{address[:8]}...{address[-8:]}" if len(address) > 20 else address
+ print(f"{short_addr:<42} {shares:<10} {subs:<12} {last_seen[:10]:<15}")
+
+ def process_payouts(self, min_amount: float = 1.0, dry_run: bool = False):
+ """Process pending payouts for miners"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ # Calculate earnings per miner based on shares
+ cursor.execute('''
+ SELECT miner_address, SUM(shares) as total_shares
+ FROM miner_shares
+ WHERE timestamp >= datetime('now', '-24 hours')
+ GROUP BY miner_address
+ HAVING total_shares > 0
+ ''')
+
+ miner_shares = cursor.fetchall()
+
+ if not miner_shares:
+ print("No shares found for payout calculation")
+ return
+
+ # Get pool fee
+ pool_fee = float(self.get_config('pool_fee') or '2.5')
+
+ # Mock reward calculation (in a real pool, this would come from found blocks)
+ total_reward = 10.0 # Example: 10 RTC found in last 24h
+ total_shares = sum(shares for _, shares in miner_shares)
+
+ payouts_processed = 0
+ total_paid = 0.0
+
+ print("Processing Payouts:")
+ print("-" * 60)
+
+ for miner_addr, shares in miner_shares:
+ share_percent = shares / total_shares
+ gross_amount = total_reward * share_percent
+ net_amount = gross_amount * (1 - pool_fee / 100)
+
+ if net_amount >= min_amount:
+ if not dry_run:
+ # Record payout in database
+ cursor.execute('''
+ INSERT INTO payout_history
+ (miner_address, amount, status, created_at)
+ VALUES (?, ?, 'pending', ?)
+ ''', (miner_addr, net_amount, datetime.now().isoformat()))
+
+ short_addr = f"{miner_addr[:8]}...{miner_addr[-8:]}"
+ print(f"{short_addr} - {net_amount:.6f} RTC ({'DRY RUN' if dry_run else 'QUEUED'})")
+ payouts_processed += 1
+ total_paid += net_amount
+
+ if not dry_run:
+ conn.commit()
+
+ print("-" * 60)
+ print(f"Payouts processed: {payouts_processed}")
+ print(f"Total amount: {total_paid:.6f} RTC")
+ print(f"Pool fee collected: {total_reward * (pool_fee / 100):.6f} RTC")
+
+ def show_payouts(self, limit: int = 20):
+ """Show recent payout history"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ SELECT miner_address, amount, status, created_at, processed_at
+ FROM payout_history
+ ORDER BY created_at DESC
+ LIMIT ?
+ ''', (limit,))
+
+ payouts = cursor.fetchall()
+
+ if not payouts:
+ print("No payout history found")
+ return
+
+ print(f"Recent Payouts (last {limit}):")
+ print("-" * 80)
+ print(f"{'Address':<42} {'Amount':<12} {'Status':<10} {'Created':<15}")
+ print("-" * 80)
+
+ for addr, amount, status, created, processed in payouts:
+ short_addr = f"{addr[:8]}...{addr[-8:]}" if len(addr) > 20 else addr
+ print(f"{short_addr:<42} {amount:<12.6f} {status:<10} {created[:10]:<15}")
+
+def main():
+ parser = argparse.ArgumentParser(description='RustChain Mining Pool CLI')
+ subparsers = parser.add_subparsers(dest='command', help='Available commands')
+
+ # Config commands
+ config_parser = subparsers.add_parser('config', help='Manage pool configuration')
+ config_subparsers = config_parser.add_subparsers(dest='config_action')
+
+ set_parser = config_subparsers.add_parser('set', help='Set configuration value')
+ set_parser.add_argument('key', help='Configuration key')
+ set_parser.add_argument('value', help='Configuration value')
+
+ config_subparsers.add_parser('show', help='Show all configuration')
+
+ # Stats command
+ stats_parser = subparsers.add_parser('stats', help='Show pool statistics')
+ stats_parser.add_argument('--hours', type=int, default=24, help='Hours to look back (default: 24)')
+
+ # Miners command
+ miners_parser = subparsers.add_parser('miners', help='Show miner information')
+ miners_parser.add_argument('--limit', type=int, default=20, help='Number of miners to show (default: 20)')
+
+ # Payout commands
+ payout_parser = subparsers.add_parser('payout', help='Manage payouts')
+ payout_subparsers = payout_parser.add_subparsers(dest='payout_action')
+
+ process_parser = payout_subparsers.add_parser('process', help='Process pending payouts')
+ process_parser.add_argument('--min-amount', type=float, default=1.0, help='Minimum payout amount (default: 1.0)')
+ process_parser.add_argument('--dry-run', action='store_true', help='Show what would be paid without processing')
+
+ history_parser = payout_subparsers.add_parser('history', help='Show payout history')
+ history_parser.add_argument('--limit', type=int, default=20, help='Number of payouts to show (default: 20)')
+
+ args = parser.parse_args()
+
+ if not args.command:
+ parser.print_help()
+ return
+
+ cli = PoolCLI()
+
+ try:
+ if args.command == 'config':
+ if args.config_action == 'set':
+ cli.set_config(args.key, args.value)
+ elif args.config_action == 'show':
+ cli.show_config()
+ else:
+ config_parser.print_help()
+
+ elif args.command == 'stats':
+ cli.show_stats(args.hours)
+
+ elif args.command == 'miners':
+ cli.show_miners(args.limit)
+
+ elif args.command == 'payout':
+ if args.payout_action == 'process':
+ cli.process_payouts(args.min_amount, args.dry_run)
+ elif args.payout_action == 'history':
+ cli.show_payouts(args.limit)
+ else:
+ payout_parser.print_help()
+
+ else:
+ parser.print_help()
+
+ except Exception as e:
+ print(f"Error: {e}")
+ sys.exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/pool_web_interface.py b/pool_web_interface.py
new file mode 100644
index 00000000..5850835c
--- /dev/null
+++ b/pool_web_interface.py
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: MIT
+# SPDX-License-Identifier: MIT
+
+from flask import Flask, request, jsonify, render_template_string
+import sqlite3
+import json
+import time
+from datetime import datetime
+import hashlib
+import os
+
+app = Flask(__name__)
+
+DB_PATH = 'pool.db'
+
+def init_pool_db():
+ """Initialize the mining pool database"""
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS miners (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ wallet_address TEXT UNIQUE NOT NULL,
+ alias TEXT,
+ registration_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ last_seen TIMESTAMP,
+ total_shares INTEGER DEFAULT 0,
+ total_earnings REAL DEFAULT 0.0,
+ status TEXT DEFAULT 'active'
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS shares (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_id INTEGER,
+ block_hash TEXT NOT NULL,
+ difficulty INTEGER,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ valid BOOLEAN DEFAULT 1,
+ FOREIGN KEY (miner_id) REFERENCES miners (id)
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS payouts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ miner_id INTEGER,
+ amount REAL NOT NULL,
+ transaction_hash TEXT,
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ status TEXT DEFAULT 'pending',
+ FOREIGN KEY (miner_id) REFERENCES miners (id)
+ )
+ ''')
+
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS pool_stats (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ total_hashrate REAL DEFAULT 0.0,
+ active_miners INTEGER DEFAULT 0,
+ blocks_found INTEGER DEFAULT 0,
+ last_block_time TIMESTAMP,
+ pool_fee REAL DEFAULT 2.0,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ''')
+
+ conn.commit()
+
+# HTML Templates
+MAIN_TEMPLATE = '''
+
+
+
+
+
+ RustChain Mining Pool
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+'''
+
+DASHBOARD_CONTENT = '''
+
+
+
{{ stats.total_hashrate or 0 }}
+
Total Hashrate (GH/s)
+
+
+
{{ stats.active_miners or 0 }}
+
Active Miners
+
+
+
{{ stats.blocks_found or 0 }}
+
Blocks Found
+
+
+
{{ stats.pool_fee or 2.0 }}%
+
Pool Fee
+
+
+
+
+
Recent Activity
+
+
+
+ | Time |
+ Miner |
+ Event |
+ Details |
+
+
+
+ {% for activity in recent_activity %}
+
+ | {{ activity.timestamp }} |
+ {{ activity.miner_alias or activity.wallet_address[:12] + '...' }} |
+ {{ activity.event_type }} |
+ {{ activity.details }} |
+
+ {% endfor %}
+
+
+
+'''
+
+MINERS_CONTENT = '''
+
+
Registered Miners
+
+
+
+ | Alias |
+ Wallet Address |
+ Total Shares |
+ Total Earnings |
+ Last Seen |
+ Status |
+
+
+
+ {% for miner in miners %}
+
+ | {{ miner.alias or 'Anonymous' }} |
+ {{ miner.wallet_address[:12] + '...' if miner.wallet_address|length > 12 else miner.wallet_address }} |
+ {{ miner.total_shares }} |
+ {{ miner.total_earnings }} RTC |
+ {{ miner.last_seen or 'Never' }} |
+ {{ miner.status }} |
+
+ {% endfor %}
+
+
+
+'''
+
+REGISTER_CONTENT = '''
+
+
Register New Miner
+
+
+
+
+
+
+'''
+
+PAYOUTS_CONTENT = '''
+
+
Payout History
+
+
+
+ | Date |
+ Miner |
+ Amount |
+ Transaction Hash |
+ Status |
+
+
+
+ {% for payout in payouts %}
+
+ | {{ payout.timestamp }} |
+ {{ payout.miner_alias or payout.wallet_address[:12] + '...' }} |
+ {{ payout.amount }} RTC |
+ {{ payout.transaction_hash[:16] + '...' if payout.transaction_hash else 'Pending' }} |
+ {{ payout.status.title() }} |
+
+ {% endfor %}
+
+
+
+'''
+
+@app.route('/')
+def dashboard():
+ stats = get_pool_stats()
+ recent_activity = get_recent_activity()
+ content = render_template_string(DASHBOARD_CONTENT, stats=stats, recent_activity=recent_activity)
+ return render_template_string(MAIN_TEMPLATE, content=content)
+
+@app.route('/miners')
+def miners_page():
+ miners = get_all_miners()
+ content = render_template_string(MINERS_CONTENT, miners=miners)
+ return render_template_string(MAIN_TEMPLATE, content=content)
+
+@app.route('/register')
+def register_page():
+ content = render_template_string(REGISTER_CONTENT)
+ return render_template_string(MAIN_TEMPLATE, content=content)
+
+@app.route('/payouts')
+def payouts_page():
+ payouts = get_payout_history()
+ content = render_template_string(PAYOUTS_CONTENT, payouts=payouts)
+ return render_template_string(MAIN_TEMPLATE, content=content)
+
+@app.route('/api/register', methods=['POST'])
+def register_miner():
+ try:
+ data = request.get_json()
+ wallet_address = data.get('wallet_address')
+ alias = data.get('alias', '')
+
+ if not wallet_address:
+ return jsonify({'success': False, 'error': 'Wallet address required'})
+
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ INSERT INTO miners (wallet_address, alias, registration_time)
+ VALUES (?, ?, ?)
+ ''', (wallet_address, alias, datetime.now()))
+ conn.commit()
+
+ return jsonify({'success': True, 'message': 'Miner registered successfully'})
+
+ except sqlite3.IntegrityError:
+ return jsonify({'success': False, 'error': 'Wallet address already registered'})
+ except Exception as e:
+ return jsonify({'success': False, 'error': str(e)})
+
+@app.route('/api/submit_share', methods=['POST'])
+def submit_share():
+ try:
+ data = request.get_json()
+ wallet_address = data.get('wallet_address')
+ block_hash = data.get('block_hash')
+ difficulty = data.get('difficulty', 1)
+
+ if not wallet_address or not block_hash:
+ return jsonify({'success': False, 'error': 'Missing required fields'})
+
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ # Get miner ID
+ cursor.execute('SELECT id FROM miners WHERE wallet_address = ?', (wallet_address,))
+ miner = cursor.fetchone()
+
+ if not miner:
+ return jsonify({'success': False, 'error': 'Miner not registered'})
+
+ miner_id = miner[0]
+
+ # Insert share
+ cursor.execute('''
+ INSERT INTO shares (miner_id, block_hash, difficulty, timestamp)
+ VALUES (?, ?, ?, ?)
+ ''', (miner_id, block_hash, difficulty, datetime.now()))
+
+ # Update miner stats
+ cursor.execute('''
+ UPDATE miners SET total_shares = total_shares + 1, last_seen = ?
+ WHERE id = ?
+ ''', (datetime.now(), miner_id))
+
+ conn.commit()
+
+ return jsonify({'success': True, 'message': 'Share submitted successfully'})
+
+ except Exception as e:
+ return jsonify({'success': False, 'error': str(e)})
+
+@app.route('/api/stats')
+def get_stats_api():
+ stats = get_pool_stats()
+ return jsonify({'success': True, 'stats': stats})
+
+@app.route('/api/miner/')
+def get_miner_stats(wallet_address):
+ try:
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ SELECT m.*,
+ COUNT(s.id) as recent_shares,
+ SUM(p.amount) as pending_balance
+ FROM miners m
+ LEFT JOIN shares s ON m.id = s.miner_id AND s.timestamp > datetime('now', '-24 hours')
+ LEFT JOIN payouts p ON m.id = p.miner_id AND p.status = 'pending'
+ WHERE m.wallet_address = ?
+ GROUP BY m.id
+ ''', (wallet_address,))
+
+ result = cursor.fetchone()
+ if not result:
+ return jsonify({'success': False, 'error': 'Miner not found'})
+
+ miner_data = {
+ 'wallet_address': result[1],
+ 'alias': result[2],
+ 'total_shares': result[5],
+ 'total_earnings': result[6],
+ 'recent_shares': result[8] or 0,
+ 'pending_balance': result[9] or 0.0,
+ 'status': result[7]
+ }
+
+ return jsonify({'success': True, 'miner': miner_data})
+
+ except Exception as e:
+ return jsonify({'success': False, 'error': str(e)})
+
+def get_pool_stats():
+ try:
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+
+ # Get active miners count
+ cursor.execute('''
+ SELECT COUNT(*) FROM miners
+ WHERE last_seen > datetime('now', '-1 hour')
+ ''')
+ active_miners = cursor.fetchone()[0]
+
+ # Get total blocks found
+ cursor.execute('SELECT COUNT(DISTINCT block_hash) FROM shares WHERE valid = 1')
+ blocks_found = cursor.fetchone()[0]
+
+ # Calculate approximate hashrate (simplified)
+ cursor.execute('''
+ SELECT COUNT(*) * 10.0 as hashrate
+ FROM shares
+ WHERE timestamp > datetime('now', '-1 hour')
+ ''')
+ hashrate_result = cursor.fetchone()
+ total_hashrate = hashrate_result[0] if hashrate_result else 0.0
+
+ return {
+ 'total_hashrate': round(total_hashrate, 2),
+ 'active_miners': active_miners,
+ 'blocks_found': blocks_found,
+ 'pool_fee': 2.0
+ }
+
+ except Exception:
+ return {
+ 'total_hashrate': 0.0,
+ 'active_miners': 0,
+ 'blocks_found': 0,
+ 'pool_fee': 2.0
+ }
+
+def get_all_miners():
+ try:
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ SELECT wallet_address, alias, total_shares, total_earnings,
+ last_seen, status
+ FROM miners
+ ORDER BY total_shares DESC
+ ''')
+
+ miners = []
+ for row in cursor.fetchall():
+ miners.append({
+ 'wallet_address': row[0],
+ 'alias': row[1],
+ 'total_shares': row[2],
+ 'total_earnings': row[3],
+ 'last_seen': row[4],
+ 'status': row[5]
+ })
+
+ return miners
+
+ except Exception:
+ return []
+
+def get_recent_activity():
+ try:
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ SELECT s.timestamp, m.wallet_address, m.alias, 'Share Submitted' as event_type,
+ 'Difficulty: ' || s.difficulty as details
+ FROM shares s
+ JOIN miners m ON s.miner_id = m.id
+ ORDER BY s.timestamp DESC
+ LIMIT 10
+ ''')
+
+ activity = []
+ for row in cursor.fetchall():
+ activity.append({
+ 'timestamp': row[0],
+ 'wallet_address': row[1],
+ 'miner_alias': row[2],
+ 'event_type': row[3],
+ 'details': row[4]
+ })
+
+ return activity
+
+ except Exception:
+ return []
+
+def get_payout_history():
+ try:
+ with sqlite3.connect(DB_PATH) as conn:
+ cursor = conn.cursor()
+ cursor.execute('''
+ SELECT p.timestamp, p.amount, p.transaction_hash, p.status,
+ m.wallet_address, m.alias
+ FROM payouts p
+ JOIN miners m ON p.miner_id = m.id
+ ORDER BY p.timestamp DESC
+ LIMIT 50
+ ''')
+
+ payouts = []
+ for row in cursor.fetchall():
+ payouts.append({
+ 'timestamp': row[0],
+ 'amount': row[1],
+ 'transaction_hash': row[2],
+ 'status': row[3],
+ 'wallet_address': row[4],
+ 'miner_alias': row[5]
+ })
+
+ return payouts
+
+ except Exception:
+ return []
+
+if __name__ == '__main__':
+ if not os.path.exists(DB_PATH):
+ init_pool_db()
+
+ app.run(debug=True, host='0.0.0.0', port=5000)