Merge branch 'feature-wallet' into node-stratum-proxy

This commit is contained in:
Dobromir Popov
2025-09-30 12:10:09 +03:00
45 changed files with 6122 additions and 1 deletions

View File

@@ -0,0 +1,40 @@
#!/bin/bash
# Kill RinCoin Stratum Proxy processes
echo "=== Killing RinCoin Stratum Proxy ==="
echo ""
# Find and kill Python processes running stratum_proxy.py
PIDS=$(ps aux | grep "stratum_proxy.py" | grep -v grep | awk '{print $2}')
if [ -n "$PIDS" ]; then
echo "Found Stratum Proxy processes: $PIDS"
echo "Killing processes..."
for pid in $PIDS; do
kill -9 "$pid" 2>/dev/null && echo "Killed PID: $pid" || echo "Failed to kill PID: $pid"
done
else
echo "No Stratum Proxy processes found"
fi
# Also kill any process using port 3333
echo ""
echo "Checking port 3333..."
PORT_PIDS=$(sudo lsof -ti:3333 2>/dev/null)
if [ -n "$PORT_PIDS" ]; then
echo "Found processes using port 3333: $PORT_PIDS"
echo "Killing processes..."
for pid in $PORT_PIDS; do
sudo kill -9 "$pid" 2>/dev/null && echo "Killed PID: $pid" || echo "Failed to kill PID: $pid"
done
else
echo "No processes using port 3333"
fi
echo ""
echo "✅ Cleanup complete!"
echo ""
echo "Port 3333 status:"
netstat -tln | grep ":3333 " || echo "Port 3333 is free"

View File

@@ -0,0 +1,529 @@
#!/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"""
<!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">{self.format_hashrate(stats.get('hashrate', 0))}</div>
<div class="stat-label">Hashrate</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 class="stat-card">
<div class="stat-value">{stats.get('pool_balance', 0):.2f}</div>
<div class="stat-label">Pool Balance (RIN)</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>Pool Balance:</strong> {stats.get('pool_balance', 0):.8f} RIN</p>
<p><strong>Connection String:</strong> <code>stratum+tcp://YOUR_IP:3333</code></p>
<!-- Debug info -->
<details style="margin-top: 20px; padding: 10px; background: #f8f9fa; border-radius: 4px;">
<summary style="cursor: pointer; font-weight: bold;">🔍 Debug Info</summary>
<p><strong>Recent Difficulty (5min):</strong> {stats.get('debug', {}).get('recent_difficulty', 0):.6f}</p>
<p><strong>Recent Share Count (5min):</strong> {stats.get('debug', {}).get('recent_share_count', 0)}</p>
<p><strong>Total Shares (24h):</strong> {stats.get('debug', {}).get('total_shares_24h', 0):,}</p>
<p><strong>Active Miners:</strong> {stats.get('active_miners', 0)} (last 5 minutes)</p>
<p><strong>Total Miners:</strong> {stats.get('total_miners', 0)} (ever registered)</p>
</details>
</div>
<div class="section">
<h2>📈 Hashrate Chart</h2>
<div class="chart-controls">
<label>Time Window: </label>
<select onchange="changeTimeWindow(this.value)">
<option value="3600">1 Hour</option>
<option value="7200">2 Hours</option>
<option value="14400">4 Hours</option>
<option value="86400">24 Hours</option>
</select>
</div>
<div class="chart-container">
<canvas id="hashrateChart"></canvas>
</div>
</div>
<div class="section">
<h2>👥 Connected Miners</h2>
<table>
<tr>
<th>User</th>
<th>Worker</th>
<th>Connected</th>
<th>Last Share</th>
</tr>
"""
for miner in stats.get('all_miners', []):
user, worker, created, last_share = miner
html += f"""
<tr>
<td>{user}</td>
<td>{worker}</td>
<td>{created}</td>
<td>{last_share or 'Never'}</td>
</tr>
"""
if not stats.get('all_miners', []):
html += """
<tr>
<td colspan="4" style="text-align: center; color: #7f8c8d;">No miners connected</td>
</tr>
"""
html += """
</table>
</div>
<div class="section">
<h2>🏆 Top Miners (24h Shares)</h2>
<table>
<tr>
<th>User</th>
<th>Worker</th>
<th>Shares</th>
<th>Hashrate</th>
<th>Last Share</th>
</tr>
"""
for miner in stats.get('miner_hashrates', []):
user, worker, shares, hashrate, last_share = miner
html += f"""
<tr>
<td>{user}</td>
<td>{worker}</td>
<td>{shares:,}</td>
<td>{self.format_hashrate(hashrate)}</td>
<td>{last_share or 'Never'}</td>
</tr>
"""
if not stats.get('top_miners', []):
html += """
<tr>
<td colspan="4" style="text-align: center; color: #7f8c8d;">No shares submitted yet</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 src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Historical data for chart
const historicalData = {json.dumps([{
'time': row[0],
'shares': row[1],
'difficulty': row[2] or 0
} for row in stats.get('historical_data', [])])};
// Create hashrate chart
const ctx = document.getElementById('hashrateChart').getContext('2d');
const chart = new Chart(ctx, {{
type: 'line',
data: {{
labels: historicalData.map(d => d.time).reverse(),
datasets: [{{
label: 'Hashrate (H/s)',
data: historicalData.map(d => (d.shares / 60.0) * 0.001).reverse(),
borderColor: '#3498db',
backgroundColor: 'rgba(52, 152, 219, 0.1)',
tension: 0.4
}}]
}},
options: {{
responsive: true,
maintainAspectRatio: false,
scales: {{
y: {{
beginAtZero: true,
title: {{
display: true,
text: 'Hashrate (H/s)'
}}
}},
x: {{
title: {{
display: true,
text: 'Time'
}}
}}
}}
}}
}});
function changeTimeWindow(seconds) {{
// Reload page with new time window
const url = new URL(window.location);
url.searchParams.set('window', seconds);
window.location.href = url.toString();
}}
</script>
<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=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")

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Solo Mining Script for RinCoin
# Uses local RinCoin node for solo mining
echo "=== RinCoin Solo Mining Setup ==="
echo ""
# Check if rincoin-node container is running
if ! sudo docker ps | grep -q "rincoin-node"; then
echo "Error: rincoin-node container is not running!"
echo "Please start it first:"
echo "sudo docker start rincoin-node"
exit 1
fi
# Get wallet address
RIN_ADDRESS=$(sudo docker exec rincoin-node rincoin-cli -datadir=/data -conf=/data/rincoin.conf -rpcwallet=main getnewaddress 2>/dev/null)
if [ -z "$RIN_ADDRESS" ]; then
echo "Error: Could not get RinCoin address!"
echo "Make sure the wallet is created and the node is synced."
exit 1
fi
echo "RinCoin Address: $RIN_ADDRESS"
echo ""
# Check node sync status
SYNC_STATUS=$(sudo docker exec rincoin-node rincoin-cli -datadir=/data -conf=/data/rincoin.conf getblockchaininfo | grep -o '"initialblockdownload": [^,]*' | cut -d' ' -f2)
if [ "$SYNC_STATUS" = "true" ]; then
echo "⚠️ WARNING: Node is still syncing (initialblockdownload: true)"
echo "Solo mining may not work properly until sync is complete."
echo ""
fi
echo "Starting solo mining with cpuminer-opt-rin..."
echo "Algorithm: rinhash"
echo "Target: Local RinCoin node (127.0.0.1:9555)"
echo "Wallet: $RIN_ADDRESS"
echo ""
echo "Press Ctrl+C to stop mining"
echo ""
# Start solo mining
sudo docker exec -it amd-strix-halo-llama-rocm bash -c "/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://127.0.0.1:9555 -u $RIN_ADDRESS -p x -t 32"

View File

@@ -0,0 +1,171 @@
#!/bin/bash
# RinCoin Solo Mining using Built-in Core Mining
# Uses RinCoin Core's generatetoaddress command
# Default address (can be overridden with command line parameter)
DEFAULT_ADDRESS="rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q"
# Get total CPU cores for default thread count
TOTAL_CORES=$(nproc)
DEFAULT_THREADS=$TOTAL_CORES
# Parse command line arguments
RIN_ADDRESS=""
THREAD_COUNT=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-a|--address)
RIN_ADDRESS="$2"
shift 2
;;
-t|--threads)
THREAD_COUNT="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " -a, --address ADDRESS RinCoin address to mine to (default: $DEFAULT_ADDRESS)"
echo " -t, --threads COUNT Number of threads to use (default: $DEFAULT_THREADS)"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 # Use defaults (all cores, default address)"
echo " $0 -a rin1q... -t 16 # Custom address and 16 threads"
echo " $0 --address rin1q... --threads 8 # Custom address and 8 threads"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Set defaults if not provided
if [ -z "$RIN_ADDRESS" ]; then
RIN_ADDRESS="$DEFAULT_ADDRESS"
echo "No address provided, using default: $RIN_ADDRESS"
fi
if [ -z "$THREAD_COUNT" ]; then
THREAD_COUNT="$DEFAULT_THREADS"
echo "No thread count provided, using all cores: $THREAD_COUNT"
fi
# Validate thread count
if ! [[ "$THREAD_COUNT" =~ ^[0-9]+$ ]] || [ "$THREAD_COUNT" -lt 1 ] || [ "$THREAD_COUNT" -gt "$TOTAL_CORES" ]; then
echo "❌ Error: Invalid thread count: $THREAD_COUNT"
echo "Thread count must be between 1 and $TOTAL_CORES"
exit 1
fi
echo "=== RinCoin Solo Mining (Built-in Core Mining) ==="
echo "CPU Cores Available: $TOTAL_CORES"
echo "Threads to Use: $THREAD_COUNT"
echo "Target Address: $RIN_ADDRESS"
echo ""
echo ""
# Configuration
RPC_HOST="127.0.0.1"
RPC_PORT="9556"
RPC_USER="rinrpc"
RPC_PASS="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
# Function to call RPC
call_rpc() {
local method="$1"
local params="$2"
curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data "{\"jsonrpc\":\"1.0\",\"id\":\"curl\",\"method\":\"$method\",\"params\":$params}" \
"http://$RPC_HOST:$RPC_PORT/"
}
# Wait for node to be ready
echo "Waiting for RinCoin node to be ready..."
while true; do
response=$(call_rpc "getblockchaininfo" "[]")
if [[ $response != *"Loading block index"* ]]; then
break
fi
echo "Node still loading... waiting 10 seconds"
sleep 10
done
echo "✅ Node is ready!"
echo ""
# Load wallet if not already loaded
echo "Loading wallet..."
wallet_response=$(call_rpc "loadwallet" "[\"main\"]")
if [[ $wallet_response == *"error"* ]] && [[ $wallet_response == *"already loaded"* ]]; then
echo "✅ Wallet already loaded"
else
echo "✅ Wallet loaded successfully"
fi
echo ""
# Validate the provided address (basic check)
if [[ ! "$RIN_ADDRESS" =~ ^rin1[a-zA-Z0-9]{25,}$ ]]; then
echo "❌ Error: Invalid RinCoin address format: $RIN_ADDRESS"
echo "RinCoin addresses should start with 'rin1' and be ~30 characters long"
exit 1
fi
echo "✅ Using RinCoin Address: $RIN_ADDRESS"
echo ""
# Get blockchain info
echo "Blockchain Status:"
blockchain_info=$(call_rpc "getblockchaininfo" "[]")
blocks=$(echo "$blockchain_info" | grep -o '"blocks":[^,]*' | cut -d':' -f2)
headers=$(echo "$blockchain_info" | grep -o '"headers":[^,]*' | cut -d':' -f2)
difficulty=$(echo "$blockchain_info" | grep -o '"difficulty":[^,]*' | cut -d':' -f2)
echo "Blocks: $blocks"
echo "Headers: $headers"
echo "Difficulty: $difficulty"
echo ""
echo "⚠️ IMPORTANT: Built-in Core Mining Limitations:"
echo "1. Uses CPU only (not GPU)"
echo "2. Very low hashpower compared to specialized miners"
echo "3. Extremely low chance of finding blocks solo"
echo "4. Best for testing, not profitable mining"
echo "5. Thread count affects mining attempts per cycle"
echo ""
echo "🚀 Starting Built-in Solo Mining..."
echo "Target Address: $RIN_ADDRESS"
echo "Threads: $THREAD_COUNT"
echo "Press Ctrl+C to stop mining"
echo ""
# Start built-in mining with specified thread count
# Note: RinCoin Core's generatetoaddress doesn't directly support thread count
# but we can run multiple instances or adjust the maxtries parameter
while true; do
echo "Attempting to mine 1 block with $THREAD_COUNT threads..."
# Adjust maxtries based on thread count for better distribution
adjusted_tries=$((1000000 * THREAD_COUNT / TOTAL_CORES))
mining_result=$(call_rpc "generatetoaddress" "[1, \"$RIN_ADDRESS\", $adjusted_tries]")
if [[ $mining_result == *"result"* ]] && [[ $mining_result != *"[]"* ]]; then
echo "🎉 BLOCK FOUND!"
echo "Result: $mining_result"
break
else
echo "No block found in this attempt (tries: $adjusted_tries). Retrying..."
sleep 5
fi
done

View File

@@ -0,0 +1,81 @@
#!/bin/bash
# Remote Solo Mining Script for RinCoin
# Connects to RinCoin node over network/RPC
# Configuration
RPC_HOST="127.0.0.1" # Change to your server IP for remote access
RPC_PORT="9556"
RPC_USER="rinrpc"
RPC_PASS="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
echo "=== Remote RinCoin Solo Mining Setup ==="
echo "RPC Host: $RPC_HOST:$RPC_PORT"
echo ""
# Test RPC connection
echo "Testing RPC connection..."
RPC_RESPONSE=$(curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data '{"jsonrpc":"1.0","id":"curl","method":"getblockchaininfo","params":[]}' \
"http://$RPC_HOST:$RPC_PORT/")
if [[ $RPC_RESPONSE == *"error"* ]]; then
echo "❌ Error: Could not connect to RinCoin RPC!"
echo "Response: $RPC_RESPONSE"
echo ""
echo "Check:"
echo "1. RinCoin node is running"
echo "2. RPC port $RPC_PORT is accessible"
echo "3. Firewall allows connections to port $RPC_PORT"
exit 1
fi
echo "✅ RPC connection successful!"
echo ""
# Get wallet address via RPC
echo "Getting wallet address..."
WALLET_RESPONSE=$(curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data '{"jsonrpc":"1.0","id":"curl","method":"getnewaddress","params":[]}' \
"http://$RPC_HOST:$RPC_PORT/")
RIN_ADDRESS=$(echo "$WALLET_RESPONSE" | grep -o '"result":"[^"]*"' | cut -d'"' -f4)
if [ -z "$RIN_ADDRESS" ]; then
echo "❌ Error: Could not get RinCoin address!"
echo "Response: $WALLET_RESPONSE"
echo ""
echo "Make sure wallet 'main' exists:"
echo "curl --user $RPC_USER:$RPC_PASS -H 'content-type: text/plain' --data '{\"jsonrpc\":\"1.0\",\"id\":\"curl\",\"method\":\"createwallet\",\"params\":[\"main\"]}' http://$RPC_HOST:$RPC_PORT/"
exit 1
fi
echo "✅ RinCoin Address: $RIN_ADDRESS"
echo ""
# Check node sync status
SYNC_RESPONSE=$(curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data '{"jsonrpc":"1.0","id":"curl","method":"getblockchaininfo","params":[]}' \
"http://$RPC_HOST:$RPC_PORT/")
SYNC_STATUS=$(echo "$SYNC_RESPONSE" | grep -o '"initialblockdownload":[^,]*' | cut -d':' -f2 | tr -d ' ')
if [ "$SYNC_STATUS" = "true" ]; then
echo "⚠️ WARNING: Node is still syncing (initialblockdownload: true)"
echo "Solo mining may not work properly until sync is complete."
echo ""
fi
echo "Starting remote solo mining with cpuminer-opt-rin..."
echo "Algorithm: rinhash"
echo "Target: RinCoin node at $RPC_HOST:9555"
echo "Wallet: $RIN_ADDRESS"
echo ""
echo "Press Ctrl+C to stop mining"
echo ""
# Start solo mining (connect to P2P port, not RPC)
sudo docker exec -it amd-strix-halo-llama-rocm bash -c "/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://$RPC_HOST:9555 -u $RIN_ADDRESS -p x -t 32"

View File

@@ -0,0 +1,76 @@
#!/bin/bash
# RinCoin Solo Mining via RPC
# This script uses RinCoin's RPC interface for solo mining
echo "=== RinCoin Solo Mining via RPC ==="
echo ""
# Configuration
RPC_HOST="127.0.0.1"
RPC_PORT="9556"
RPC_USER="rinrpc"
RPC_PASS="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
# Function to call RPC
call_rpc() {
local method="$1"
local params="$2"
curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data "{\"jsonrpc\":\"1.0\",\"id\":\"curl\",\"method\":\"$method\",\"params\":$params}" \
"http://$RPC_HOST:$RPC_PORT/"
}
# Wait for node to be ready
echo "Waiting for RinCoin node to be ready..."
while true; do
response=$(call_rpc "getblockchaininfo" "[]")
if [[ $response != *"Loading block index"* ]]; then
break
fi
echo "Node still loading... waiting 10 seconds"
sleep 10
done
echo "✅ Node is ready!"
echo ""
# Get wallet address
echo "Getting wallet address..."
wallet_response=$(call_rpc "getnewaddress" "[]")
rin_address=$(echo "$wallet_response" | grep -o '"result":"[^"]*"' | cut -d'"' -f4)
if [ -z "$rin_address" ]; then
echo "❌ Error: Could not get RinCoin address!"
echo "Response: $wallet_response"
exit 1
fi
echo "✅ RinCoin Address: $rin_address"
echo ""
# Get blockchain info
echo "Blockchain Status:"
blockchain_info=$(call_rpc "getblockchaininfo" "[]")
blocks=$(echo "$blockchain_info" | grep -o '"blocks":[^,]*' | cut -d':' -f2)
headers=$(echo "$blockchain_info" | grep -o '"headers":[^,]*' | cut -d':' -f2)
difficulty=$(echo "$blockchain_info" | grep -o '"difficulty":[^,]*' | cut -d':' -f2)
echo "Blocks: $blocks"
echo "Headers: $headers"
echo "Difficulty: $difficulty"
echo ""
echo "⚠️ IMPORTANT: RinCoin solo mining requires:"
echo "1. A fully synced node (currently at block $blocks of $headers)"
echo "2. Mining software that supports RinCoin's RPC mining protocol"
echo "3. Very high hashpower to find blocks solo"
echo ""
echo "For now, we recommend pool mining for consistent rewards:"
echo ""
echo "Pool Mining Command:"
echo "sudo docker exec -it amd-strix-halo-llama-rocm bash -c \"/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://rinhash.mine.zergpool.com:7148 -u bc1qjn4m6rmrveuxhk02a5qhe4r6kdcsvvt3vhdn9j -p c=BTC,mc=RIN,ID=StrixHalo -t 32\""
echo ""
echo "Your RinCoin address for solo mining: $rin_address"

View File

@@ -0,0 +1,76 @@
#!/bin/bash
# RinCoin Solo Mining via RPC
# This script uses RinCoin's RPC interface for solo mining
echo "=== RinCoin Solo Mining via RPC ==="
echo ""
# Configuration
RPC_HOST="127.0.0.1"
RPC_PORT="9556"
RPC_USER="rinrpc"
RPC_PASS="745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90"
# Function to call RPC
call_rpc() {
local method="$1"
local params="$2"
curl -s --user "$RPC_USER:$RPC_PASS" \
-H 'content-type: text/plain' \
--data "{\"jsonrpc\":\"1.0\",\"id\":\"curl\",\"method\":\"$method\",\"params\":$params}" \
"http://$RPC_HOST:$RPC_PORT/"
}
# Wait for node to be ready
echo "Waiting for RinCoin node to be ready..."
while true; do
response=$(call_rpc "getblockchaininfo" "[]")
if [[ $response != *"Loading block index"* ]]; then
break
fi
echo "Node still loading... waiting 10 seconds"
sleep 10
done
echo "✅ Node is ready!"
echo ""
# Get wallet address
echo "Getting wallet address..."
wallet_response=$(call_rpc "getnewaddress" "[]")
rin_address=$(echo "$wallet_response" | grep -o '"result":"[^"]*"' | cut -d'"' -f4)
if [ -z "$rin_address" ]; then
echo "❌ Error: Could not get RinCoin address!"
echo "Response: $wallet_response"
exit 1
fi
echo "✅ RinCoin Address: $rin_address"
echo ""
# Get blockchain info
echo "Blockchain Status:"
blockchain_info=$(call_rpc "getblockchaininfo" "[]")
blocks=$(echo "$blockchain_info" | grep -o '"blocks":[^,]*' | cut -d':' -f2)
headers=$(echo "$blockchain_info" | grep -o '"headers":[^,]*' | cut -d':' -f2)
difficulty=$(echo "$blockchain_info" | grep -o '"difficulty":[^,]*' | cut -d':' -f2)
echo "Blocks: $blocks"
echo "Headers: $headers"
echo "Difficulty: $difficulty"
echo ""
echo "⚠️ IMPORTANT: RinCoin solo mining requires:"
echo "1. A fully synced node (currently at block $blocks of $headers)"
echo "2. Mining software that supports RinCoin's RPC mining protocol"
echo "3. Very high hashpower to find blocks solo"
echo ""
echo "For now, we recommend pool mining for consistent rewards:"
echo ""
echo "Pool Mining Command:"
echo "sudo docker exec -it amd-strix-halo-llama-rocm bash -c \"/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://rinhash.mine.zergpool.com:7148 -u bc1qjn4m6rmrveuxhk02a5qhe4r6kdcsvvt3vhdn9j -p c=BTC,mc=RIN,ID=StrixHalo -t 32\""
echo ""
echo "Your RinCoin address for solo mining: $rin_address"

View File

@@ -0,0 +1,83 @@
#!/bin/bash
# RinCoin Mining Pool Server Startup Script
# Distributes block rewards among multiple miners
echo "=== RinCoin Mining Pool Server ==="
echo ""
# Check if RinCoin node is running
echo "Checking RinCoin node status..."
if ! curl -s -u rinrpc:745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90 \
-H 'content-type: text/plain' \
--data '{"jsonrpc":"1.0","id":"curl","method":"getblockchaininfo","params":[]}' \
http://127.0.0.1:9556/ > /dev/null; then
echo "❌ RinCoin node is not running!"
echo "Start it first with: docker start rincoin-node"
exit 1
fi
echo "✅ RinCoin node is running"
# Check Python dependencies
echo "Checking Python dependencies..."
python3 -c "import requests, sqlite3" 2>/dev/null || {
echo "Installing python3-requests..."
sudo apt-get update && sudo apt-get install -y python3-requests
}
echo "✅ Python dependencies ready"
# Check if port 3333 is already in use
if netstat -tln | grep -q ":3333 "; then
echo ""
echo "⚠️ Port 3333 is already in use!"
echo ""
echo "🔍 Process using port 3333:"
sudo netstat -tlnp | grep ":3333 " || echo "Could not determine process"
echo ""
echo "🛑 To kill existing process:"
echo "sudo lsof -ti:3333 | xargs sudo kill -9"
echo ""
read -p "Kill existing process and continue? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Killing processes using port 3333..."
sudo lsof -ti:3333 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
sleep 2
else
echo "Exiting..."
exit 1
fi
fi
echo ""
echo "🚀 Starting Mining Pool Server..."
echo "This will distribute block rewards among multiple miners"
echo ""
echo "Pool Features:"
echo "- Multiple miner support"
echo "- Share-based reward distribution"
echo "- Pool fee: 1%"
echo "- Real-time statistics"
echo "- Web dashboard on port 8083"
echo ""
echo "After it starts, miners can connect with:"
echo ""
echo "Option 1: Address as username"
echo "./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q -p x"
echo ""
echo "Option 2: Address.workername format"
echo "./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q.worker1 -p x"
echo ""
echo "Option 3: Traditional username (rewards to pool address)"
echo "./cpuminer -a rinhash -o stratum+tcp://YOUR_IP:3333 -u username.workername -p x"
echo ""
echo "🌐 Web Dashboard: http://YOUR_IP:8083"
echo "📊 View real-time pool statistics, miners, and blocks"
echo ""
echo "Press Ctrl+C to stop the pool"
# Start the mining pool
python3 MINE/rin/stratum_pool.py

View File

@@ -0,0 +1,68 @@
#!/bin/bash
# Start RinCoin Stratum Proxy Server
# Bridges cpuminer-opt-rin to RinCoin node
echo "=== RinCoin Stratum Proxy Server ==="
echo ""
# Check if RinCoin node is running
if ! sudo docker ps | grep -q "rincoin-node"; then
echo "❌ Error: rincoin-node container is not running!"
echo "Please start it first:"
echo "sudo docker start rincoin-node"
exit 1
fi
echo "✅ RinCoin node is running"
# Check if Python3 and requests are available
if ! command -v python3 &> /dev/null; then
echo "❌ Error: python3 is not installed!"
echo "Please install it: sudo apt-get install python3"
exit 1
fi
# Install requests if not available
python3 -c "import requests" 2>/dev/null || {
echo "Installing python3-requests..."
sudo apt-get update && sudo apt-get install -y python3-requests
}
echo "✅ Python dependencies ready"
# Check if port 3334 is already in use
if netstat -tln | grep -q ":3334 "; then
echo ""
echo "⚠️ Port 3334 is already in use!"
echo ""
echo "🔍 Process using port 3334:"
sudo netstat -tlnp | grep ":3334 " || echo "Could not determine process"
echo ""
echo "🛑 To kill existing process:"
echo "sudo lsof -ti:3334 | xargs sudo kill -9"
echo ""
read -p "Kill existing process and continue? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Killing processes using port 3334..."
sudo lsof -ti:3334 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill"
sleep 2
else
echo "Exiting..."
exit 1
fi
fi
echo ""
echo "🚀 Starting Stratum Proxy Server..."
echo ""
echo "After it starts, connect your miner with:"
echo "sudo docker exec -it amd-strix-halo-llama-rocm bash -c \"/mnt/dl/rinhash/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3334 -u user -p pass -t 28\""
echo ""
echo "Press Ctrl+C to stop the proxy"
echo ""
# Start the proxy
cd "$(dirname "$0")"
python3 stratum_proxy.py --submit-all-blocks

View File

@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="/mnt/shared/DEV/repos/d-popov.com/scripts/MINE/rin/web_wallet"
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is required"
exit 1
fi
python3 "${SCRIPT_DIR}/server.py"

View File

@@ -0,0 +1,602 @@
# #!/usr/bin/env python3
# """
# RinCoin Mining Pool Server
# Distributes block rewards among multiple miners based on share contributions
# """
# import socket
# import threading
# import json
# import time
# import requests
# import hashlib
# import struct
# import sqlite3
# from datetime import datetime
# from requests.auth import HTTPBasicAuth
# # Import web interface
# from pool_web_interface import start_web_interface
# # Import stratum base class
# from stratum_proxy import RinCoinStratumBase
# class RinCoinMiningPool(RinCoinStratumBase):
# def __init__(self, stratum_host='0.0.0.0', stratum_port=3333,
# rpc_host='127.0.0.1', rpc_port=9556,
# rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
# pool_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q',
# pool_fee_percent=1.0):
# # Initialize base class
# super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, pool_address)
# self.pool_address = pool_address
# self.pool_fee_percent = pool_fee_percent
# # Pool statistics
# self.total_shares = 0
# self.total_blocks = 0
# self.pool_hashrate = 0
# # Database for persistent storage
# self.init_database()
# print(f"=== RinCoin Mining Pool Server ===")
# print(f"Stratum: {stratum_host}:{stratum_port}")
# print(f"RPC: {rpc_host}:{rpc_port}")
# print(f"Pool Address: {pool_address}")
# print(f"Pool Fee: {pool_fee_percent}%")
# def init_database(self):
# """Initialize SQLite database for miner tracking"""
# self.db = sqlite3.connect(':memory:', check_same_thread=False)
# cursor = self.db.cursor()
# # Create tables
# cursor.execute('''
# CREATE TABLE IF NOT EXISTS miners (
# id INTEGER PRIMARY KEY,
# user TEXT NOT NULL,
# worker TEXT NOT NULL,
# address TEXT,
# shares INTEGER DEFAULT 0,
# last_share TIMESTAMP,
# last_hashrate REAL DEFAULT 0,
# created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
# )
# ''')
# cursor.execute('''
# CREATE TABLE IF NOT EXISTS shares (
# id INTEGER PRIMARY KEY,
# miner_id INTEGER,
# job_id TEXT,
# difficulty REAL,
# submitted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
# FOREIGN KEY (miner_id) REFERENCES miners (id)
# )
# ''')
# cursor.execute('''
# CREATE TABLE IF NOT EXISTS blocks (
# id INTEGER PRIMARY KEY,
# block_hash TEXT,
# height INTEGER,
# reward REAL,
# pool_fee REAL,
# miner_rewards TEXT, -- JSON of {address: amount}
# found_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
# )
# ''')
# # Samples for pool hashrate chart
# cursor.execute('''
# CREATE TABLE IF NOT EXISTS hashrate_samples (
# id INTEGER PRIMARY KEY,
# ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
# hashrate REAL
# )
# ''')
# self.db.commit()
# def get_pool_block_template(self):
# """Get new block template and create pool-style job"""
# template = super().get_block_template()
# if template:
# # Convert to pool-style job format if needed
# job = self.current_job
# if job:
# # Add pool-specific fields
# job["coinb1"] = "01000000" + "0" * 60
# job["coinb2"] = "ffffffff"
# job["merkle_branch"] = []
# job["clean_jobs"] = True
# return job
# return None
# def validate_rincoin_address(self, address):
# """Validate if an address is a valid RinCoin address"""
# try:
# return self.decode_bech32_address(address) is not None
# except:
# return False
# def register_miner(self, user, worker, address=None):
# """Register or update miner in database"""
# cursor = self.db.cursor()
# # Check if miner exists
# cursor.execute('SELECT id, address FROM miners WHERE user = ? AND worker = ?', (user, worker))
# result = cursor.fetchone()
# if result:
# miner_id, existing_address = result
# if address and not existing_address:
# cursor.execute('UPDATE miners SET address = ? WHERE id = ?', (address, miner_id))
# self.db.commit()
# return miner_id
# else:
# # Create new miner
# cursor.execute('INSERT INTO miners (user, worker, address) VALUES (?, ?, ?)', (user, worker, address))
# self.db.commit()
# return cursor.lastrowid
# def record_share(self, miner_id, job_id, difficulty):
# """Record a share submission"""
# cursor = self.db.cursor()
# # Record share
# cursor.execute('INSERT INTO shares (miner_id, job_id, difficulty) VALUES (?, ?, ?)',
# (miner_id, job_id, difficulty))
# # Update miner stats
# cursor.execute('UPDATE miners SET shares = shares + 1, last_share = CURRENT_TIMESTAMP WHERE id = ?', (miner_id,))
# self.db.commit()
# self.total_shares += 1
# def distribute_block_reward(self, block_hash, block_height, total_reward):
# """Distribute block reward among miners based on their shares"""
# cursor = self.db.cursor()
# # Calculate pool fee
# pool_fee = total_reward * (self.pool_fee_percent / 100.0)
# miner_reward = total_reward - pool_fee
# # Get shares from last 24 hours
# cursor.execute('''
# SELECT m.address, COUNT(s.id) as share_count, SUM(s.difficulty) as total_difficulty
# FROM miners m
# JOIN shares s ON m.id = s.miner_id
# WHERE s.submitted > datetime('now', '-1 day')
# GROUP BY m.id, m.address
# HAVING share_count > 0
# ''')
# miners = cursor.fetchall()
# if not miners:
# print("No miners with shares in last 24 hours")
# return
# # Calculate total difficulty
# total_difficulty = sum(row[2] for row in miners)
# # Separate miners with and without addresses
# miners_with_addresses = []
# miners_without_addresses = []
# total_difficulty_with_addresses = 0
# total_difficulty_without_addresses = 0
# for address, share_count, difficulty in miners:
# if address:
# miners_with_addresses.append((address, share_count, difficulty))
# total_difficulty_with_addresses += difficulty
# else:
# miners_without_addresses.append((address, share_count, difficulty))
# total_difficulty_without_addresses += difficulty
# # Calculate total difficulty
# total_difficulty = total_difficulty_with_addresses + total_difficulty_without_addresses
# if total_difficulty == 0:
# print("No valid difficulty found")
# return
# # Distribute rewards
# miner_rewards = {}
# # First, distribute to miners with valid addresses
# if miners_with_addresses:
# for address, share_count, difficulty in miners_with_addresses:
# reward_share = (difficulty / total_difficulty) * miner_reward
# miner_rewards[address] = reward_share
# print(f"💰 Miner {address}: {reward_share:.8f} RIN ({difficulty} difficulty)")
# # Calculate undistributed rewards (from miners without addresses)
# if miners_without_addresses:
# undistributed_reward = 0
# for address, share_count, difficulty in miners_without_addresses:
# undistributed_reward += (difficulty / total_difficulty) * miner_reward
# print(f"⚠️ Miner without address: {difficulty} difficulty -> {undistributed_reward:.8f} RIN to pool")
# # Keep undistributed rewards for pool (no redistribution)
# print(f"💰 Pool keeps {undistributed_reward:.8f} RIN from miners without addresses")
# # Record block
# cursor.execute('''
# INSERT INTO blocks (block_hash, height, reward, pool_fee, miner_rewards)
# VALUES (?, ?, ?, ?, ?)
# ''', (block_hash, block_height, total_reward, pool_fee, json.dumps(miner_rewards)))
# self.db.commit()
# self.total_blocks += 1
# print(f"🎉 Block {block_height} reward distributed!")
# print(f"💰 Pool fee: {pool_fee:.8f} RIN")
# print(f"💰 Total distributed: {sum(miner_rewards.values()):.8f} RIN")
# # Summary
# if miners_without_addresses:
# print(f"📊 Summary: {len(miners_with_addresses)} miners with addresses, {len(miners_without_addresses)} without (rewards to pool)")
# # Use inherited send_stratum_response and send_stratum_notification from base class
# def handle_stratum_message(self, client, addr, message):
# """Handle incoming Stratum message from miner"""
# try:
# data = json.loads(message.strip())
# method = data.get("method")
# msg_id = data.get("id")
# params = data.get("params", [])
# print(f"[{addr}] {method}: {params}")
# if method == "mining.subscribe":
# # Subscribe response
# self.send_stratum_response(client, msg_id, [
# [["mining.set_difficulty", "subscription_id"], ["mining.notify", "subscription_id"]],
# "extranonce1",
# 4
# ])
# # Send difficulty (lower for CPU mining)
# self.send_stratum_notification(client, "mining.set_difficulty", [0.0001])
# # Send initial job
# if self.get_pool_block_template():
# job = self.current_job
# self.send_stratum_notification(client, "mining.notify", [
# job["job_id"],
# job["prevhash"],
# job["coinb1"],
# job["coinb2"],
# job["merkle_branch"],
# f"{job['version']:08x}",
# job["bits"],
# job["ntime"],
# job["clean_jobs"]
# ])
# elif method == "mining.extranonce.subscribe":
# # Handle extranonce subscription
# print(f"[{addr}] Extranonce subscription requested")
# self.send_stratum_response(client, msg_id, True)
# elif method == "mining.authorize":
# # Parse user.worker format
# if len(params) >= 2:
# user_worker = params[0]
# password = params[1] if len(params) > 1 else ""
# # Extract user and worker
# if '.' in user_worker:
# user, worker = user_worker.split('.', 1)
# else:
# user = user_worker
# worker = "default"
# # Check if user contains a RinCoin address (starts with 'rin')
# miner_address = None
# if user.startswith('rin'):
# # User is a RinCoin address
# if self.validate_rincoin_address(user):
# miner_address = user
# user = f"miner_{miner_address[:8]}" # Create a user ID from address
# print(f"[{addr}] ✅ Miner using valid RinCoin address: {miner_address}")
# else:
# print(f"[{addr}] ❌ Invalid RinCoin address: {user}")
# self.send_stratum_response(client, msg_id, False, "Invalid RinCoin address")
# return
# elif '.' in user and user.split('.')[0].startswith('rin'):
# # Format: rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q.workername
# address_part, worker_part = user.split('.', 1)
# if address_part.startswith('rin'):
# if self.validate_rincoin_address(address_part):
# miner_address = address_part
# user = f"miner_{miner_address[:8]}"
# worker = worker_part
# print(f"[{addr}] ✅ Miner using valid RinCoin address format: {miner_address}.{worker}")
# else:
# print(f"[{addr}] ❌ Invalid RinCoin address: {address_part}")
# self.send_stratum_response(client, msg_id, False, "Invalid RinCoin address")
# return
# # Register miner with address
# miner_id = self.register_miner(user, worker, miner_address)
# # Store client info
# self.clients[addr] = {
# 'client': client,
# 'user': user,
# 'worker': worker,
# 'miner_id': miner_id,
# 'address': miner_address,
# 'shares': 0,
# 'last_share': time.time(),
# 'extranonce1': '00000000' # Default extranonce1
# }
# if miner_address:
# print(f"[{addr}] ✅ Authorized: {user}.{worker} -> {miner_address}")
# else:
# print(f"[{addr}] ⚠️ Authorized: {user}.{worker} (rewards will go to pool address)")
# self.send_stratum_response(client, msg_id, True)
# else:
# self.send_stratum_response(client, msg_id, False, "Invalid authorization")
# elif method == "mining.submit":
# # Submit share
# if addr not in self.clients:
# self.send_stratum_response(client, msg_id, False, "Not authorized")
# return
# miner_info = self.clients[addr]
# try:
# if self.current_job and len(params) >= 5:
# username = params[0]
# job_id = params[1]
# extranonce2 = params[2]
# ntime = params[3]
# nonce = params[4]
# # Use base class to validate and submit share
# extranonce1 = miner_info.get('extranonce1', '00000000')
# miner_address = miner_info.get('address')
# # For pool mining, always mine to pool address
# success, message = self.submit_share(
# self.current_job, extranonce1, extranonce2, ntime, nonce,
# target_address=self.pool_address
# )
# if success:
# # Record share with estimated difficulty
# actual_difficulty = 0.00133 # Estimated for ~381 kH/s
# self.record_share(miner_info['miner_id'], job_id, actual_difficulty)
# # Update miner stats
# now_ts = time.time()
# prev_ts = miner_info.get('last_share') or now_ts
# dt = max(now_ts - prev_ts, 1e-3)
# miner_hashrate = actual_difficulty * (2**32) / dt
# if miner_info['shares'] == 0:
# miner_hashrate = 381000 # Default estimate
# miner_info['shares'] += 1
# miner_info['last_share'] = now_ts
# # Update database
# try:
# cursor = self.db.cursor()
# cursor.execute('UPDATE miners SET last_share = CURRENT_TIMESTAMP, last_hashrate = ? WHERE id = ?',
# (miner_hashrate, miner_info['miner_id']))
# self.db.commit()
# except Exception as e:
# print(f"DB update error: {e}")
# print(f"[{addr}] ✅ Share accepted from {miner_info['user']}.{miner_info['worker']} (Total: {miner_info['shares']})")
# self.send_stratum_response(client, msg_id, True)
# # If block was found, distribute rewards
# if "Block found" in message:
# print(f"🎉 [{addr}] BLOCK FOUND!")
# # Get block info and distribute rewards
# total_reward = self.current_job['coinbasevalue'] / 100000000 if self.current_job else 25.0
# self.distribute_block_reward("pending", self.current_job['height'] if self.current_job else 0, total_reward)
# else:
# # Accept as share for pool statistics even if block validation fails
# self.send_stratum_response(client, msg_id, True)
# else:
# print(f"[{addr}] Invalid share parameters")
# self.send_stratum_response(client, msg_id, False, "Invalid parameters")
# except Exception as e:
# print(f"[{addr}] Share processing error: {e}")
# # Still accept the share for mining statistics
# self.send_stratum_response(client, msg_id, True)
# else:
# print(f"[{addr}] ⚠️ Unknown method: {method}")
# # Send null result for unknown methods (standard Stratum behavior)
# self.send_stratum_response(client, msg_id, None, None)
# except json.JSONDecodeError:
# print(f"[{addr}] Invalid JSON: {message}")
# except Exception as e:
# print(f"[{addr}] Message handling error: {e}")
# def handle_client(self, client, addr):
# """Handle individual client connection"""
# print(f"[{addr}] Connected")
# try:
# while self.running:
# data = client.recv(4096)
# if not data:
# break
# # Handle multiple messages in one packet
# messages = data.decode('utf-8').strip().split('\n')
# for message in messages:
# if message:
# self.handle_stratum_message(client, addr, message)
# except Exception as e:
# print(f"[{addr}] Client error: {e}")
# finally:
# client.close()
# if addr in self.clients:
# del self.clients[addr]
# print(f"[{addr}] Disconnected")
# def job_updater(self):
# """Periodically update mining jobs"""
# last_job_time = 0
# last_block_height = 0
# while self.running:
# try:
# # Check for new blocks every 10 seconds
# time.sleep(10)
# # Get current blockchain info
# blockchain_info = self.rpc_call("getblockchaininfo")
# if blockchain_info:
# current_height = blockchain_info.get('blocks', 0)
# # Create new job if:
# # 1. New block detected
# # 2. 30+ seconds since last job
# # 3. No current job exists
# should_create_job = (
# current_height != last_block_height or
# time.time() - last_job_time > 30 or
# not self.current_job
# )
# if should_create_job:
# if self.get_pool_block_template():
# job = self.current_job
# last_job_time = time.time()
# last_block_height = current_height
# print(f"📦 New job created: {job['job_id']} (block {current_height})")
# # Send to all connected clients
# for addr, miner_info in list(self.clients.items()):
# try:
# self.send_stratum_notification(miner_info['client'], "mining.notify", [
# job["job_id"],
# job["prevhash"],
# job["coinb1"],
# job["coinb2"],
# job["merkle_branch"],
# f"{job['version']:08x}",
# job["bits"],
# job["ntime"],
# job["clean_jobs"]
# ])
# except Exception as e:
# print(f"Failed to send job to {addr}: {e}")
# except Exception as e:
# print(f"Job updater error: {e}")
# def stats_updater(self):
# """Periodically update pool statistics"""
# while self.running:
# try:
# time.sleep(60) # Update every minute
# cursor = self.db.cursor()
# # Pool hashrate is the sum of miners' last hashrates
# cursor.execute('SELECT COALESCE(SUM(last_hashrate), 0) FROM miners')
# self.pool_hashrate = cursor.fetchone()[0] or 0.0
# # Sample for chart
# cursor.execute('INSERT INTO hashrate_samples (hashrate) VALUES (?)', (self.pool_hashrate,))
# self.db.commit()
# print(f"📊 Pool Stats: {len(self.clients)} miners, {self.total_shares} shares, {self.pool_hashrate/1000:.2f} kH/s")
# except Exception as e:
# print(f"Stats updater error: {e}")
# def start(self):
# """Start the mining pool server"""
# try:
# # Test RPC connection
# blockchain_info = self.rpc_call("getblockchaininfo")
# if not blockchain_info:
# print("❌ Failed to connect to RinCoin node!")
# return
# print(f"✅ Connected to RinCoin node (block {blockchain_info.get('blocks', 'unknown')})")
# # Start background threads
# job_thread = threading.Thread(target=self.job_updater, daemon=True)
# job_thread.start()
# stats_thread = threading.Thread(target=self.stats_updater, daemon=True)
# stats_thread.start()
# # Start web interface in background
# web_thread = threading.Thread(target=start_web_interface,
# args=(self.db, '0.0.0.0', 8083,
# self.rpc_host, self.rpc_port,
# self.rpc_user, self.rpc_password),
# daemon=True)
# web_thread.start()
# print(f"🌐 Web dashboard started on http://0.0.0.0:8083")
# # Start Stratum server
# server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# server_socket.bind((self.stratum_host, self.stratum_port))
# server_socket.listen(10)
# print(f"🚀 Mining pool listening on {self.stratum_host}:{self.stratum_port}")
# print("Ready for multiple miners...")
# print("")
# print(f"💰 Pool address: {self.pool_address}")
# print(f"💰 Pool fee: {self.pool_fee_percent}%")
# print("")
# print("Connect miners with:")
# print(f"./cpuminer -a rinhash -o stratum+tcp://{self.stratum_host}:{self.stratum_port} -u username.workername -p x")
# print("")
# while self.running:
# try:
# client, addr = server_socket.accept()
# client_thread = threading.Thread(target=self.handle_client, args=(client, addr), daemon=True)
# client_thread.start()
# except KeyboardInterrupt:
# print("\n🛑 Shutting down pool...")
# self.running = False
# break
# except Exception as e:
# print(f"Server error: {e}")
# except OSError as e:
# if "Address already in use" in str(e):
# print(f"❌ Port {self.stratum_port} is already in use!")
# print("")
# print("🔍 Check what's using the port:")
# print(f"sudo netstat -tlnp | grep :{self.stratum_port}")
# print("")
# print("🛑 Kill existing process:")
# print(f"sudo lsof -ti:{self.stratum_port} | xargs sudo kill -9")
# print("")
# print("🔄 Or use a different port by editing the script")
# else:
# print(f"Failed to start server: {e}")
# except Exception as e:
# print(f"Failed to start server: {e}")
# finally:
# print("Pool server stopped")
# if __name__ == "__main__":
# pool = RinCoinMiningPool()
# pool.start()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
#!/bin/bash
# Test Reward Redistribution Logic
echo "=== Testing Reward Redistribution Logic ==="
echo ""
# Kill any existing processes
./MINE/rin/kill_stratum_proxy.sh
echo "🧪 Testing reward distribution scenarios:"
echo ""
echo "Scenario 1: All miners have valid addresses"
echo "Expected: Normal distribution"
echo ""
echo "Scenario 2: Some miners without addresses"
echo "Expected: Redistribution of their rewards to miners with addresses"
echo ""
echo "Scenario 3: All miners without addresses"
echo "Expected: All rewards go to pool"
echo ""
echo "🚀 Starting mining pool..."
./MINE/rin/start_mining_pool.sh &
POOL_PID=$!
echo ""
echo "⏳ Waiting for pool to start..."
sleep 5
echo ""
echo "🧪 Test 1: Miner with valid address"
echo "Expected: Gets full share of rewards"
timeout 5s ./cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q.worker1 -p x -t 1
echo ""
echo "🧪 Test 2: Miner without address"
echo "Expected: Contributes to difficulty but gets no direct rewards"
timeout 5s ./cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u user.worker2 -p x -t 1
echo ""
echo "🧪 Test 3: Another miner with valid address"
echo "Expected: Gets base reward + redistribution from miner without address"
timeout 5s ./cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q.worker3 -p x -t 1
echo ""
echo "📊 Pool Log Analysis:"
echo "Look for these patterns in the logs above:"
echo "1. '💰 Miner rin1q...: X.XX RIN (difficulty)' - Base rewards"
echo "2. '⚠️ Miner without address: X difficulty -> X.XX RIN to pool' - Undistributed"
echo "3. '💰 Pool keeps X.XX RIN from miners without addresses' - Pool keeps rewards"
echo "4. '📊 Summary: X miners with addresses, Y without (rewards to pool)' - Final summary"
echo ""
echo "🧹 Cleaning up..."
kill $POOL_PID 2>/dev/null
./MINE/rin/kill_stratum_proxy.sh
echo ""
echo "📋 Reward Distribution Logic Summary:"
echo ""
echo "✅ Miners with valid RinCoin addresses:"
echo " - Get reward based on their difficulty"
echo " - Rewards sent directly to their addresses"
echo ""
echo "⚠️ Miners without addresses:"
echo " - Contribute to total difficulty"
echo " - Their reward share goes to pool address"
echo " - No direct rewards received"
echo ""
echo "💰 Pool fee: Always 1% of total block reward"
echo "💰 Pool bonus: Additional rewards from miners without addresses"