wip-broken

This commit is contained in:
Dobromir Popov
2025-09-21 21:22:18 +03:00
parent 69a5fa82c2
commit e22f776e43
24 changed files with 4244 additions and 1 deletions

View File

@@ -0,0 +1,74 @@
version: '3.8'
services:
rincoin-stratum-proxy:
build:
context: .
dockerfile: Dockerfile
container_name: rincoin-stratum-proxy
ports:
- "3333:3333" # Stratum mining port
- "1337:1337" # RPC interface port
environment:
# RinCoin node connection
- RINCOIN_RPC_HOST=${RINCOIN_RPC_HOST:-127.0.0.1}
- RINCOIN_RPC_PORT=${RINCOIN_RPC_PORT:-9556}
- RINCOIN_RPC_USER=${RINCOIN_RPC_USER:-rinrpc}
- RINCOIN_RPC_PASS=${RINCOIN_RPC_PASS:-745ce784d5d537fc06105a1b935b7657903cfc71a1b935b7657903cfc71a5fb3b90}
# Pool configuration
- RINCOIN_TARGET_ADDRESS=${RINCOIN_TARGET_ADDRESS:-rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q}
- POOL_DIFFICULTY=${POOL_DIFFICULTY:-100}
- MAX_TIME_DIFF=${MAX_TIME_DIFF:-7200}
- AUTHORIZED_WORKERS=${AUTHORIZED_WORKERS:-worker1,worker2}
# Debug settings
- DEBUG=${DEBUG:-stratum}
volumes:
- ./logs:/app/logs
restart: unless-stopped
networks:
- rincoin-network
depends_on:
- rincoin-node
healthcheck:
test: ["CMD", "node", "-e", "const http = require('http'); const options = {host: 'localhost', port: 1337, path: '/', timeout: 5000}; const req = http.request(options, (res) => { process.exit(res.statusCode === 200 ? 0 : 1); }); req.on('error', () => process.exit(1)); req.end();"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
rincoin-node:
image: rincoin/rincoin:latest
container_name: rincoin-node
ports:
- "9556:9556" # RPC port
environment:
- RPC_USER=${RINCOIN_RPC_USER:-rinrpc}
- RPC_PASSWORD=${RINCOIN_RPC_PASS:-745ce784d5d537fc06105a1b935b7657903cfc71a1b935b7657903cfc71a5fb3b90}
- RPC_PORT=9556
- RPC_ALLOW_IP=0.0.0.0/0
volumes:
- rincoin-data:/home/rincoin/.rincoin
restart: unless-stopped
networks:
- rincoin-network
command: >
rincoind
-server=1
-rpcuser=${RINCOIN_RPC_USER:-rinrpc}
-rpcpassword=${RINCOIN_RPC_PASS:-745ce784d5d537fc06105a1b935b7657903cfc71a1b935b7657903cfc71a5fb3b90}
-rpcport=9556
-rpcallowip=0.0.0.0/0
-rpcbind=0.0.0.0
-txindex=1
-server=1
-daemon=0
networks:
rincoin-network:
driver: bridge
volumes:
rincoin-data:

View File

@@ -0,0 +1,9 @@
================================================================================
RinCoin Mining Log
================================================================================
Started: 2025-09-16 09:32:19
Target Address: rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q
Stratum: 0.0.0.0:3334
RPC: 127.0.0.1:9556
================================================================================

View File

@@ -0,0 +1,77 @@
#!/bin/bash
# Start Fixed RinCoin Stratum Proxy Server
# This version fixes block hash calculation and validation issues
echo "=== RinCoin Stratum Proxy Server - FIXED VERSION ==="
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 FIXED Stratum Proxy Server..."
echo ""
echo "FIXES APPLIED:"
echo "1. ✅ Fixed block header endianness issues"
echo "2. ✅ Fixed target comparison (hash <= target instead of hash > target)"
echo "3. ✅ Fixed bits-to-target conversion"
echo "4. ✅ Improved debugging output with hash vs target comparison"
echo "5. ✅ Lowered mining difficulty for testing (0.00001)"
echo ""
echo "This version will properly validate blocks and find them with your hashrate!"
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

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

View File

@@ -0,0 +1,705 @@
#!/usr/bin/env python3
"""
RinCoin Stratum Proxy Server - FIXED VERSION
Fixed block hash calculation and validation issues
"""
import socket
import threading
import json
import time
import requests
import hashlib
import struct
from requests.auth import HTTPBasicAuth
class RinCoinStratumProxy:
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
rpc_host='127.0.0.1', rpc_port=9556,
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
target_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q'):
self.stratum_host = stratum_host
self.stratum_port = stratum_port
self.rpc_host = rpc_host
self.rpc_port = rpc_port
self.rpc_user = rpc_user
self.rpc_password = rpc_password
self.target_address = target_address
self.clients = {}
self.job_counter = 0
self.current_job = None
self.running = True
self.extranonce1_counter = 0
# Logging setup
self.log_file = "mining_log.txt"
self.init_log_file()
print(f"RinCoin Stratum Proxy Server")
print(f"Stratum: {stratum_host}:{stratum_port}")
print(f"RPC: {rpc_host}:{rpc_port}")
print(f"Target: {target_address}")
def init_log_file(self):
"""Initialize mining log file with header"""
try:
with open(self.log_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write("RinCoin Mining Log\n")
f.write("=" * 80 + "\n")
f.write(f"Started: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Target Address: {self.target_address}\n")
f.write(f"Stratum: {self.stratum_host}:{self.stratum_port}\n")
f.write(f"RPC: {self.rpc_host}:{self.rpc_port}\n")
f.write("=" * 80 + "\n\n")
except Exception as e:
print(f"Failed to initialize log file: {e}")
def log_hash_found(self, hash_hex, difficulty, height, reward, nonce, ntime):
"""Log found hash to file"""
try:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with open(self.log_file, 'a') as f:
f.write(f"[{timestamp}] 🎉 HASH FOUND!\n")
f.write(f" Hash: {hash_hex}\n")
f.write(f" Difficulty: {difficulty:.6f}\n")
f.write(f" Height: {height}\n")
f.write(f" Reward: {reward:.8f} RIN\n")
f.write(f" Nonce: {nonce}\n")
f.write(f" Time: {ntime}\n")
f.write("-" * 40 + "\n")
except Exception as e:
print(f"Failed to log hash: {e}")
def log_wallet_balance(self):
"""Log current wallet balance to file"""
try:
balance = self.rpc_call("getbalance")
if balance is not None:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with open(self.log_file, 'a') as f:
f.write(f"[{timestamp}] 💰 Wallet Balance: {balance:.8f} RIN\n")
except Exception as e:
print(f"Failed to log wallet balance: {e}")
def rpc_call(self, method, params=[]):
"""Make RPC call to RinCoin node"""
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": "stratum_proxy",
"method": method,
"params": params
}
response = requests.post(url, json=payload, headers=headers, auth=auth, timeout=30)
if response.status_code == 200:
result = response.json()
if 'error' in result and result['error'] is not None:
print(f"RPC Error: {result['error']}")
return None
return result.get('result')
else:
print(f"HTTP Error: {response.status_code}")
return None
except Exception as e:
print(f"RPC Call Error: {e}")
return None
def encode_varint(self, n):
"""Encode integer as Bitcoin-style varint"""
if n < 0xfd:
return bytes([n])
elif n <= 0xffff:
return b"\xfd" + struct.pack('<H', n)
elif n <= 0xffffffff:
return b"\xfe" + struct.pack('<I', n)
else:
return b"\xff" + struct.pack('<Q', n)
def decode_bech32_address(self, address):
"""Decode RinCoin bech32 address to script"""
try:
if not address or not address.startswith('rin1'):
raise ValueError("Not a RinCoin bech32 address")
result = self.rpc_call("validateaddress", [address])
if not result or not result.get('isvalid'):
raise ValueError("Address not valid per node")
script_hex = result.get('scriptPubKey')
if not script_hex:
raise ValueError("Node did not return scriptPubKey")
return bytes.fromhex(script_hex)
except Exception as e:
print(f"Address decode error: {e}")
return None
def build_coinbase_transaction(self, template, extranonce1, extranonce2):
"""Build coinbase transaction variants (with and without witness) for default address"""
return self.build_coinbase_transaction_for_address(template, extranonce1, extranonce2, self.target_address)
def build_coinbase_transaction_for_address(self, template, extranonce1, extranonce2, target_address):
"""Build coinbase transaction variants (with and without witness)"""
try:
has_witness_commitment = template.get('default_witness_commitment') is not None
# Common parts
value = template.get('coinbasevalue', 0)
script_pubkey = self.decode_bech32_address(target_address)
if not script_pubkey:
return None, None
witness_commitment = template.get('default_witness_commitment')
# ScriptSig (block height minimal push + tag + extranonces)
height = template.get('height', 0)
height_bytes = struct.pack('<I', height)
height_compact = bytes([len(height_bytes.rstrip(b'\x00'))]) + height_bytes.rstrip(b'\x00')
scriptsig = height_compact + b'/RinCoin/' + extranonce1.encode() + extranonce2.encode()
# Helper to build outputs blob
def build_outputs_blob() -> bytes:
outputs_blob = b''
outputs_list = []
# Main output
outputs_list.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
# Witness commitment OP_RETURN output if present
if witness_commitment:
commit_script = bytes.fromhex(witness_commitment)
outputs_list.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
outputs_blob += self.encode_varint(len(outputs_list))
for out in outputs_list:
outputs_blob += out
return outputs_blob
# Build non-witness serialization (txid serialization)
cb_nowit = b''
cb_nowit += struct.pack('<I', 1) # version
cb_nowit += b'\x01' # input count
cb_nowit += b'\x00' * 32 # prevout hash
cb_nowit += b'\xff\xff\xff\xff' # prevout index
cb_nowit += self.encode_varint(len(scriptsig)) + scriptsig
cb_nowit += b'\xff\xff\xff\xff' # sequence
cb_nowit += build_outputs_blob() # outputs
cb_nowit += struct.pack('<I', 0) # locktime
# Build with-witness serialization (block serialization)
if has_witness_commitment:
cb_wit = b''
cb_wit += struct.pack('<I', 1) # version
cb_wit += b'\x00\x01' # segwit marker+flag
cb_wit += b'\x01' # input count
cb_wit += b'\x00' * 32 # prevout hash
cb_wit += b'\xff\xff\xff\xff' # prevout index
cb_wit += self.encode_varint(len(scriptsig)) + scriptsig
cb_wit += b'\xff\xff\xff\xff' # sequence
cb_wit += build_outputs_blob() # outputs
# Witness stack for coinbase (32-byte reserved value)
cb_wit += b'\x01' # witness stack count
cb_wit += b'\x20' # item length
cb_wit += b'\x00' * 32 # reserved value
cb_wit += struct.pack('<I', 0) # locktime
else:
cb_wit = cb_nowit
return cb_wit, cb_nowit
except Exception as e:
print(f"Coinbase construction error: {e}")
return None, None
def calculate_merkle_root(self, coinbase_txid, transactions):
"""Calculate merkle root with coinbase at index 0"""
try:
# Start with all transaction hashes (coinbase + others)
hashes = [coinbase_txid]
for tx in transactions:
hashes.append(bytes.fromhex(tx['hash'])[::-1]) # Reverse for little-endian
# Build merkle tree
while len(hashes) > 1:
if len(hashes) % 2 == 1:
hashes.append(hashes[-1]) # Duplicate last hash if odd
next_level = []
for i in range(0, len(hashes), 2):
combined = hashes[i] + hashes[i + 1]
next_level.append(hashlib.sha256(hashlib.sha256(combined).digest()).digest())
hashes = next_level
return hashes[0] if hashes else b'\x00' * 32
except Exception as e:
print(f"Merkle root calculation error: {e}")
return b'\x00' * 32
def bits_to_target(self, bits_hex):
"""Convert bits to target - FIXED VERSION"""
try:
bits = int(bits_hex, 16)
exponent = bits >> 24
mantissa = bits & 0xffffff
# Bitcoin target calculation
if exponent <= 3:
target = mantissa >> (8 * (3 - exponent))
else:
target = mantissa << (8 * (exponent - 3))
return f"{target:064x}"
except Exception as e:
print(f"Bits to target error: {e}")
return "0000ffff00000000000000000000000000000000000000000000000000000000"
def get_block_template(self):
"""Get new block template and create Stratum job"""
try:
template = self.rpc_call("getblocktemplate", [{"rules": ["segwit", "mweb"]}])
if not template:
return None
self.job_counter += 1
job = {
"job_id": f"job_{self.job_counter:08x}",
"template": template,
"prevhash": template.get("previousblockhash", "0" * 64),
"version": template.get('version', 1),
"bits": template.get('bits', '1d00ffff'),
"ntime": f"{int(time.time()):08x}",
"target": self.bits_to_target(template.get('bits', '1d00ffff')),
"height": template.get('height', 0),
"coinbasevalue": template.get('coinbasevalue', 0),
"transactions": template.get('transactions', [])
}
self.current_job = job
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
network_difficulty = self.calculate_network_difficulty(job['target'])
print(f"[{timestamp}] 🆕 NEW JOB: {job['job_id']} | Height: {job['height']} | Reward: {job['coinbasevalue']/100000000:.2f} RIN")
print(f" 🎯 Network Difficulty: {network_difficulty:.6f} | Bits: {job['bits']}")
print(f" 📍 Target: {job['target'][:16]}... | Transactions: {len(job['transactions'])}")
return job
except Exception as e:
print(f"Get block template error: {e}")
return None
def calculate_share_difficulty(self, hash_hex, target_hex):
"""Calculate actual share difficulty from hash - FIXED"""
try:
hash_int = int(hash_hex, 16)
if hash_int == 0:
return float('inf') # Perfect hash
# Bitcoin-style difficulty calculation using difficulty 1 target
# Difficulty 1 target for mainnet
diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
# Share difficulty = how much harder this hash was compared to diff 1
difficulty = diff1_target / hash_int
return difficulty
except Exception as e:
print(f"Difficulty calculation error: {e}")
return 0.0
def calculate_network_difficulty(self, target_hex):
"""Calculate network difficulty from target - FIXED"""
try:
target_int = int(target_hex, 16)
# Bitcoin difficulty 1.0 target
diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
# Network difficulty = how much harder than difficulty 1.0
network_difficulty = diff1_target / target_int
return network_difficulty
except Exception as e:
print(f"Network difficulty calculation error: {e}")
return 1.0
def submit_share(self, job, extranonce1, extranonce2, ntime, nonce, target_address=None):
"""Validate share and submit block if valid - FIXED VERSION"""
try:
# Use provided address or default
address = target_address or self.target_address
# Build coinbase (with and without witness)
coinbase_wit, coinbase_nowit = self.build_coinbase_transaction_for_address(
job['template'], extranonce1, extranonce2, address)
if not coinbase_wit or not coinbase_nowit:
return False, "Coinbase construction failed"
# Calculate coinbase txid (non-witness serialization)
coinbase_txid = hashlib.sha256(hashlib.sha256(coinbase_nowit).digest()).digest()[::-1]
# Calculate merkle root
merkle_root = self.calculate_merkle_root(coinbase_txid, job['transactions'])
# Build block header - FIXED ENDIANNESS
header = b''
header += struct.pack('<I', job['version']) # Version (little-endian)
header += bytes.fromhex(job['prevhash'])[::-1] # Previous block hash (big-endian in block)
header += merkle_root # Merkle root (already in correct endian)
header += struct.pack('<I', int(ntime, 16)) # Timestamp (little-endian)
header += bytes.fromhex(job['bits'])[::-1] # Bits (big-endian in block)
header += struct.pack('<I', int(nonce, 16)) # Nonce (little-endian)
# Calculate block hash - FIXED DOUBLE SHA256
block_hash = hashlib.sha256(hashlib.sha256(header).digest()).digest()
block_hash_hex = block_hash[::-1].hex() # Reverse for display/comparison
# Calculate real difficulties
share_difficulty = self.calculate_share_difficulty(block_hash_hex, job['target'])
network_difficulty = self.calculate_network_difficulty(job['target'])
# Check if hash meets target - FIXED COMPARISON
hash_int = int(block_hash_hex, 16)
target_int = int(job['target'], 16)
meets_target = hash_int <= target_int # FIXED: less than or equal
# Enhanced logging
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
difficulty_percentage = (share_difficulty / network_difficulty) * 100 if network_difficulty > 0 else 0
# Progress indicator based on percentage
if meets_target:
progress_icon = "🎉" # Block found!
elif difficulty_percentage >= 50:
progress_icon = "🔥" # Very close
elif difficulty_percentage >= 10:
progress_icon = "" # Getting warm
elif difficulty_percentage >= 1:
progress_icon = "💫" # Some progress
else:
progress_icon = "📊" # Low progress
print(f"[{timestamp}] {progress_icon} SHARE: job={job['job_id']} | nonce={nonce} | hash={block_hash_hex[:16]}...")
print(f" 🎯 Share Diff: {share_difficulty:.2e} | Network Diff: {network_difficulty:.6f}")
print(f" 📈 Progress: {difficulty_percentage:.4f}% of network difficulty")
print(f" 📍 Target: {job['target'][:16]}... | Height: {job['height']}")
print(f" ⏰ Time: {ntime} | Extranonce: {extranonce1}:{extranonce2}")
print(f" 🔍 Hash vs Target: {hash_int} {'<=' if meets_target else '>'} {target_int}")
if not meets_target:
# Share doesn't meet target - reject but still useful for debugging
print(f" ❌ Share rejected (hash > target)")
return False, "Share too high"
# Valid block! Build full block and submit
print(f" 🎉 BLOCK FOUND! Hash: {block_hash_hex}")
print(f" 💰 Reward: {job['coinbasevalue']/100000000:.2f} RIN -> {address}")
print(f" 📊 Block height: {job['height']}")
print(f" 🔍 Difficulty: {share_difficulty:.6f} (target: {network_difficulty:.6f})")
# Log the found hash
reward_rin = job['coinbasevalue'] / 100000000
self.log_hash_found(block_hash_hex, share_difficulty, job['height'], reward_rin, nonce, ntime)
# Build complete block
block = header
# Transaction count
tx_count = 1 + len(job['transactions'])
block += self.encode_varint(tx_count)
# Add coinbase transaction (witness variant for block body)
block += coinbase_wit
# Add other transactions
for tx in job['transactions']:
block += bytes.fromhex(tx['data'])
# Submit block
block_hex = block.hex()
print(f" 📦 Submitting block of size {len(block_hex)//2} bytes...")
result = self.rpc_call("submitblock", [block_hex])
if result is None:
print(f" ✅ Block accepted by network!")
# Log wallet balance after successful block submission
self.log_wallet_balance()
return True, "Block found and submitted"
else:
print(f" ❌ Block rejected: {result}")
print(f" 🔍 Debug: Block size {len(block_hex)//2} bytes, {len(job['transactions'])} transactions")
return False, f"Block rejected: {result}"
except Exception as e:
print(f"Share submission error: {e}")
return False, f"Submission error: {e}"
def send_stratum_response(self, client, msg_id, result, error=None):
"""Send Stratum response to client"""
try:
response = {
"id": msg_id,
"result": result,
"error": error
}
message = json.dumps(response) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send response error: {e}")
def send_stratum_notification(self, client, method, params):
"""Send Stratum notification to client"""
try:
notification = {
"id": None,
"method": method,
"params": params
}
message = json.dumps(notification) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send notification error: {e}")
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", [])
if method == "mining.subscribe":
# Generate unique extranonce1 for this connection
self.extranonce1_counter += 1
extranonce1 = f"{self.extranonce1_counter:08x}"
# Store extranonce1 for this client
if addr not in self.clients:
self.clients[addr] = {}
self.clients[addr]['extranonce1'] = extranonce1
# Subscribe response
self.send_stratum_response(client, msg_id, [
[["mining.set_difficulty", "subscription_id"], ["mining.notify", "subscription_id"]],
extranonce1,
4 # extranonce2 size
])
# Send difficulty - MUCH LOWER for testing
self.send_stratum_notification(client, "mining.set_difficulty", [0.00001])
# Send initial job
if self.current_job:
self.send_job_to_client(client, self.current_job)
else:
if self.get_block_template():
self.send_job_to_client(client, self.current_job)
elif method == "mining.authorize":
username = params[0] if params else "anonymous"
self.clients[addr]['username'] = username
self.send_stratum_response(client, msg_id, True)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 🔐 [{addr}] Authorized as {username}")
elif method == "mining.extranonce.subscribe":
self.send_stratum_response(client, msg_id, True)
elif method == "mining.submit":
if len(params) >= 5:
username = params[0]
job_id = params[1]
extranonce2 = params[2]
ntime = params[3]
nonce = params[4]
print(f"[{addr}] Submit: {username} | job={job_id} | nonce={nonce}")
# Always validate against current job
if self.current_job:
extranonce1 = self.clients[addr].get('extranonce1', '00000000')
# Submit share
success, message = self.submit_share(self.current_job, extranonce1, extranonce2, ntime, nonce)
# Always accept shares for debugging, even if they don't meet target
self.send_stratum_response(client, msg_id, True)
if success and "Block found" in message:
# Get new job after block found
threading.Thread(target=self.update_job_after_block, daemon=True).start()
else:
self.send_stratum_response(client, msg_id, True)
else:
self.send_stratum_response(client, msg_id, False, "Invalid parameters")
else:
print(f"[{addr}] Unknown method: {method}")
self.send_stratum_response(client, msg_id, None, "Unknown method")
except json.JSONDecodeError:
print(f"[{addr}] Invalid JSON: {message}")
except Exception as e:
print(f"[{addr}] Message handling error: {e}")
def send_job_to_client(self, client, job):
"""Send mining job to specific client"""
try:
self.send_stratum_notification(client, "mining.notify", [
job["job_id"],
job["prevhash"],
"", # coinb1 (empty for now - miner handles coinbase)
"", # coinb2 (empty for now - miner handles coinbase)
[], # merkle_branch (empty for now - we calculate merkle root)
f"{job['version']:08x}",
job["bits"],
job["ntime"],
True # clean_jobs
])
except Exception as e:
print(f"Failed to send job: {e}")
def update_job_after_block(self):
"""Update job after a block is found"""
time.sleep(2) # Brief delay to let network propagate
if self.get_block_template():
self.broadcast_new_job()
def broadcast_new_job(self):
"""Broadcast new job to all connected clients"""
if not self.current_job:
return
print(f"Broadcasting new job to {len(self.clients)} clients")
for addr, client_data in list(self.clients.items()):
try:
if 'socket' in client_data:
self.send_job_to_client(client_data['socket'], self.current_job)
except Exception as e:
print(f"Failed to send job to {addr}: {e}")
def handle_client(self, client, addr):
"""Handle individual client connection"""
print(f"[{addr}] Connected")
if addr not in self.clients:
self.clients[addr] = {}
self.clients[addr]['socket'] = client
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"""
balance_log_counter = 0
while self.running:
try:
time.sleep(30) # Update every 30 seconds
old_height = self.current_job['height'] if self.current_job else 0
if self.get_block_template():
new_height = self.current_job['height']
if new_height > old_height:
print(f"New block detected! Broadcasting new job...")
self.broadcast_new_job()
# Log wallet balance every 10 minutes (20 cycles of 30 seconds)
balance_log_counter += 1
if balance_log_counter >= 20:
self.log_wallet_balance()
balance_log_counter = 0
except Exception as e:
print(f"Job updater error: {e}")
def start(self):
"""Start the Stratum proxy 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")
print(f"Current height: {blockchain_info.get('blocks', 'unknown')}")
print(f"Chain: {blockchain_info.get('chain', 'unknown')}")
# Log initial wallet balance
self.log_wallet_balance()
# Get initial block template
if not self.get_block_template():
print("Failed to get initial block template!")
return
# Start job updater thread
job_thread = threading.Thread(target=self.job_updater, daemon=True)
job_thread.start()
# 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)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 🚀 Mining Stratum proxy ready!")
print(f" 📡 Listening on {self.stratum_host}:{self.stratum_port}")
print(f" 💰 Mining to: {self.target_address}")
print(f" 📊 Current job: {self.current_job['job_id'] if self.current_job else 'None'}")
print(f" 📝 Mining log: {self.log_file}")
print("")
print(" 🔧 Miner command:")
print(f" ./cpuminer -a rinhash -o stratum+tcp://{self.stratum_host}:{self.stratum_port} -u worker1 -p x -t 4")
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("\nShutting down...")
self.running = False
break
except Exception as e:
print(f"Server error: {e}")
except Exception as e:
print(f"Failed to start server: {e}")
finally:
print("Server stopped")
if __name__ == "__main__":
proxy = RinCoinStratumProxy()
proxy.start()

View File

@@ -0,0 +1,705 @@
#!/usr/bin/env python3
"""
RinCoin Stratum Proxy Server - DEBUG VERSION
Fixed block hash calculation and validation issues
"""
import socket
import threading
import json
import time
import requests
import hashlib
import struct
from requests.auth import HTTPBasicAuth
class RinCoinStratumProxy:
def __init__(self, stratum_host='0.0.0.0', stratum_port=3334,
rpc_host='127.0.0.1', rpc_port=9556,
rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90',
target_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q'):
self.stratum_host = stratum_host
self.stratum_port = stratum_port
self.rpc_host = rpc_host
self.rpc_port = rpc_port
self.rpc_user = rpc_user
self.rpc_password = rpc_password
self.target_address = target_address
self.clients = {}
self.job_counter = 0
self.current_job = None
self.running = True
self.extranonce1_counter = 0
# Logging setup
self.log_file = "mining_log.txt"
self.init_log_file()
print(f"RinCoin Stratum Proxy Server")
print(f"Stratum: {stratum_host}:{stratum_port}")
print(f"RPC: {rpc_host}:{rpc_port}")
print(f"Target: {target_address}")
def init_log_file(self):
"""Initialize mining log file with header"""
try:
with open(self.log_file, 'w') as f:
f.write("=" * 80 + "\n")
f.write("RinCoin Mining Log\n")
f.write("=" * 80 + "\n")
f.write(f"Started: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Target Address: {self.target_address}\n")
f.write(f"Stratum: {self.stratum_host}:{self.stratum_port}\n")
f.write(f"RPC: {self.rpc_host}:{self.rpc_port}\n")
f.write("=" * 80 + "\n\n")
except Exception as e:
print(f"Failed to initialize log file: {e}")
def log_hash_found(self, hash_hex, difficulty, height, reward, nonce, ntime):
"""Log found hash to file"""
try:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with open(self.log_file, 'a') as f:
f.write(f"[{timestamp}] 🎉 HASH FOUND!\n")
f.write(f" Hash: {hash_hex}\n")
f.write(f" Difficulty: {difficulty:.6f}\n")
f.write(f" Height: {height}\n")
f.write(f" Reward: {reward:.8f} RIN\n")
f.write(f" Nonce: {nonce}\n")
f.write(f" Time: {ntime}\n")
f.write("-" * 40 + "\n")
except Exception as e:
print(f"Failed to log hash: {e}")
def log_wallet_balance(self):
"""Log current wallet balance to file"""
try:
balance = self.rpc_call("getbalance")
if balance is not None:
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with open(self.log_file, 'a') as f:
f.write(f"[{timestamp}] 💰 Wallet Balance: {balance:.8f} RIN\n")
except Exception as e:
print(f"Failed to log wallet balance: {e}")
def rpc_call(self, method, params=[]):
"""Make RPC call to RinCoin node"""
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": "stratum_proxy",
"method": method,
"params": params
}
response = requests.post(url, json=payload, headers=headers, auth=auth, timeout=30)
if response.status_code == 200:
result = response.json()
if 'error' in result and result['error'] is not None:
print(f"RPC Error: {result['error']}")
return None
return result.get('result')
else:
print(f"HTTP Error: {response.status_code}")
return None
except Exception as e:
print(f"RPC Call Error: {e}")
return None
def encode_varint(self, n):
"""Encode integer as Bitcoin-style varint"""
if n < 0xfd:
return bytes([n])
elif n <= 0xffff:
return b"\xfd" + struct.pack('<H', n)
elif n <= 0xffffffff:
return b"\xfe" + struct.pack('<I', n)
else:
return b"\xff" + struct.pack('<Q', n)
def decode_bech32_address(self, address):
"""Decode RinCoin bech32 address to script"""
try:
if not address or not address.startswith('rin1'):
raise ValueError("Not a RinCoin bech32 address")
result = self.rpc_call("validateaddress", [address])
if not result or not result.get('isvalid'):
raise ValueError("Address not valid per node")
script_hex = result.get('scriptPubKey')
if not script_hex:
raise ValueError("Node did not return scriptPubKey")
return bytes.fromhex(script_hex)
except Exception as e:
print(f"Address decode error: {e}")
return None
def build_coinbase_transaction(self, template, extranonce1, extranonce2):
"""Build coinbase transaction variants (with and without witness) for default address"""
return self.build_coinbase_transaction_for_address(template, extranonce1, extranonce2, self.target_address)
def build_coinbase_transaction_for_address(self, template, extranonce1, extranonce2, target_address):
"""Build coinbase transaction variants (with and without witness)"""
try:
has_witness_commitment = template.get('default_witness_commitment') is not None
# Common parts
value = template.get('coinbasevalue', 0)
script_pubkey = self.decode_bech32_address(target_address)
if not script_pubkey:
return None, None
witness_commitment = template.get('default_witness_commitment')
# ScriptSig (block height minimal push + tag + extranonces)
height = template.get('height', 0)
height_bytes = struct.pack('<I', height)
height_compact = bytes([len(height_bytes.rstrip(b'\x00'))]) + height_bytes.rstrip(b'\x00')
scriptsig = height_compact + b'/RinCoin/' + extranonce1.encode() + extranonce2.encode()
# Helper to build outputs blob
def build_outputs_blob() -> bytes:
outputs_blob = b''
outputs_list = []
# Main output
outputs_list.append(struct.pack('<Q', value) + self.encode_varint(len(script_pubkey)) + script_pubkey)
# Witness commitment OP_RETURN output if present
if witness_commitment:
commit_script = bytes.fromhex(witness_commitment)
outputs_list.append(struct.pack('<Q', 0) + self.encode_varint(len(commit_script)) + commit_script)
outputs_blob += self.encode_varint(len(outputs_list))
for out in outputs_list:
outputs_blob += out
return outputs_blob
# Build non-witness serialization (txid serialization)
cb_nowit = b''
cb_nowit += struct.pack('<I', 1) # version
cb_nowit += b'\x01' # input count
cb_nowit += b'\x00' * 32 # prevout hash
cb_nowit += b'\xff\xff\xff\xff' # prevout index
cb_nowit += self.encode_varint(len(scriptsig)) + scriptsig
cb_nowit += b'\xff\xff\xff\xff' # sequence
cb_nowit += build_outputs_blob() # outputs
cb_nowit += struct.pack('<I', 0) # locktime
# Build with-witness serialization (block serialization)
if has_witness_commitment:
cb_wit = b''
cb_wit += struct.pack('<I', 1) # version
cb_wit += b'\x00\x01' # segwit marker+flag
cb_wit += b'\x01' # input count
cb_wit += b'\x00' * 32 # prevout hash
cb_wit += b'\xff\xff\xff\xff' # prevout index
cb_wit += self.encode_varint(len(scriptsig)) + scriptsig
cb_wit += b'\xff\xff\xff\xff' # sequence
cb_wit += build_outputs_blob() # outputs
# Witness stack for coinbase (32-byte reserved value)
cb_wit += b'\x01' # witness stack count
cb_wit += b'\x20' # item length
cb_wit += b'\x00' * 32 # reserved value
cb_wit += struct.pack('<I', 0) # locktime
else:
cb_wit = cb_nowit
return cb_wit, cb_nowit
except Exception as e:
print(f"Coinbase construction error: {e}")
return None, None
def calculate_merkle_root(self, coinbase_txid, transactions):
"""Calculate merkle root with coinbase at index 0"""
try:
# Start with all transaction hashes (coinbase + others)
hashes = [coinbase_txid]
for tx in transactions:
hashes.append(bytes.fromhex(tx['hash'])[::-1]) # Reverse for little-endian
# Build merkle tree
while len(hashes) > 1:
if len(hashes) % 2 == 1:
hashes.append(hashes[-1]) # Duplicate last hash if odd
next_level = []
for i in range(0, len(hashes), 2):
combined = hashes[i] + hashes[i + 1]
next_level.append(hashlib.sha256(hashlib.sha256(combined).digest()).digest())
hashes = next_level
return hashes[0] if hashes else b'\x00' * 32
except Exception as e:
print(f"Merkle root calculation error: {e}")
return b'\x00' * 32
def bits_to_target(self, bits_hex):
"""Convert bits to target - FIXED VERSION"""
try:
bits = int(bits_hex, 16)
exponent = bits >> 24
mantissa = bits & 0xffffff
# Bitcoin target calculation
if exponent <= 3:
target = mantissa >> (8 * (3 - exponent))
else:
target = mantissa << (8 * (exponent - 3))
return f"{target:064x}"
except Exception as e:
print(f"Bits to target error: {e}")
return "0000ffff00000000000000000000000000000000000000000000000000000000"
def get_block_template(self):
"""Get new block template and create Stratum job"""
try:
template = self.rpc_call("getblocktemplate", [{"rules": ["segwit", "mweb"]}])
if not template:
return None
self.job_counter += 1
job = {
"job_id": f"job_{self.job_counter:08x}",
"template": template,
"prevhash": template.get("previousblockhash", "0" * 64),
"version": template.get('version', 1),
"bits": template.get('bits', '1d00ffff'),
"ntime": f"{int(time.time()):08x}",
"target": self.bits_to_target(template.get('bits', '1d00ffff')),
"height": template.get('height', 0),
"coinbasevalue": template.get('coinbasevalue', 0),
"transactions": template.get('transactions', [])
}
self.current_job = job
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
network_difficulty = self.calculate_network_difficulty(job['target'])
print(f"[{timestamp}] 🆕 NEW JOB: {job['job_id']} | Height: {job['height']} | Reward: {job['coinbasevalue']/100000000:.2f} RIN")
print(f" 🎯 Network Difficulty: {network_difficulty:.6f} | Bits: {job['bits']}")
print(f" 📍 Target: {job['target'][:16]}... | Transactions: {len(job['transactions'])}")
return job
except Exception as e:
print(f"Get block template error: {e}")
return None
def calculate_share_difficulty(self, hash_hex, target_hex):
"""Calculate actual share difficulty from hash - FIXED"""
try:
hash_int = int(hash_hex, 16)
if hash_int == 0:
return float('inf') # Perfect hash
# Bitcoin-style difficulty calculation using difficulty 1 target
# Difficulty 1 target for mainnet
diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
# Share difficulty = how much harder this hash was compared to diff 1
difficulty = diff1_target / hash_int
return difficulty
except Exception as e:
print(f"Difficulty calculation error: {e}")
return 0.0
def calculate_network_difficulty(self, target_hex):
"""Calculate network difficulty from target - FIXED"""
try:
target_int = int(target_hex, 16)
# Bitcoin difficulty 1.0 target
diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
# Network difficulty = how much harder than difficulty 1.0
network_difficulty = diff1_target / target_int
return network_difficulty
except Exception as e:
print(f"Network difficulty calculation error: {e}")
return 1.0
def submit_share(self, job, extranonce1, extranonce2, ntime, nonce, target_address=None):
"""Validate share and submit block if valid - FIXED VERSION"""
try:
# Use provided address or default
address = target_address or self.target_address
# Build coinbase (with and without witness)
coinbase_wit, coinbase_nowit = self.build_coinbase_transaction_for_address(
job['template'], extranonce1, extranonce2, address)
if not coinbase_wit or not coinbase_nowit:
return False, "Coinbase construction failed"
# Calculate coinbase txid (non-witness serialization)
coinbase_txid = hashlib.sha256(hashlib.sha256(coinbase_nowit).digest()).digest()[::-1]
# Calculate merkle root
merkle_root = self.calculate_merkle_root(coinbase_txid, job['transactions'])
# Build block header - FIXED ENDIANNESS
header = b''
header += struct.pack('<I', job['version']) # Version (little-endian)
header += bytes.fromhex(job['prevhash'])[::-1] # Previous block hash (big-endian in block)
header += merkle_root # Merkle root (already in correct endian)
header += struct.pack('<I', int(ntime, 16)) # Timestamp (little-endian)
header += bytes.fromhex(job['bits'])[::-1] # Bits (big-endian in block)
header += struct.pack('<I', int(nonce, 16)) # Nonce (little-endian)
# Calculate block hash - FIXED DOUBLE SHA256
block_hash = hashlib.sha256(hashlib.sha256(header).digest()).digest()
block_hash_hex = block_hash[::-1].hex() # Reverse for display/comparison
# Calculate real difficulties
share_difficulty = self.calculate_share_difficulty(block_hash_hex, job['target'])
network_difficulty = self.calculate_network_difficulty(job['target'])
# Check if hash meets target - FIXED COMPARISON
hash_int = int(block_hash_hex, 16)
target_int = int(job['target'], 16)
meets_target = hash_int <= target_int # FIXED: less than or equal
# Enhanced logging
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
difficulty_percentage = (share_difficulty / network_difficulty) * 100 if network_difficulty > 0 else 0
# Progress indicator based on percentage
if meets_target:
progress_icon = "🎉" # Block found!
elif difficulty_percentage >= 50:
progress_icon = "🔥" # Very close
elif difficulty_percentage >= 10:
progress_icon = "" # Getting warm
elif difficulty_percentage >= 1:
progress_icon = "💫" # Some progress
else:
progress_icon = "📊" # Low progress
print(f"[{timestamp}] {progress_icon} SHARE: job={job['job_id']} | nonce={nonce} | hash={block_hash_hex[:16]}...")
print(f" 🎯 Share Diff: {share_difficulty:.2e} | Network Diff: {network_difficulty:.6f}")
print(f" 📈 Progress: {difficulty_percentage:.4f}% of network difficulty")
print(f" 📍 Target: {job['target'][:16]}... | Height: {job['height']}")
print(f" ⏰ Time: {ntime} | Extranonce: {extranonce1}:{extranonce2}")
print(f" 🔍 Hash vs Target: {hash_int} {'<=' if meets_target else '>'} {target_int}")
if not meets_target:
# Share doesn't meet target - reject but still useful for debugging
print(f" ❌ Share rejected (hash > target)")
return False, "Share too high"
# Valid block! Build full block and submit
print(f" 🎉 BLOCK FOUND! Hash: {block_hash_hex}")
print(f" 💰 Reward: {job['coinbasevalue']/100000000:.2f} RIN -> {address}")
print(f" 📊 Block height: {job['height']}")
print(f" 🔍 Difficulty: {share_difficulty:.6f} (target: {network_difficulty:.6f})")
# Log the found hash
reward_rin = job['coinbasevalue'] / 100000000
self.log_hash_found(block_hash_hex, share_difficulty, job['height'], reward_rin, nonce, ntime)
# Build complete block
block = header
# Transaction count
tx_count = 1 + len(job['transactions'])
block += self.encode_varint(tx_count)
# Add coinbase transaction (witness variant for block body)
block += coinbase_wit
# Add other transactions
for tx in job['transactions']:
block += bytes.fromhex(tx['data'])
# Submit block
block_hex = block.hex()
print(f" 📦 Submitting block of size {len(block_hex)//2} bytes...")
result = self.rpc_call("submitblock", [block_hex])
if result is None:
print(f" ✅ Block accepted by network!")
# Log wallet balance after successful block submission
self.log_wallet_balance()
return True, "Block found and submitted"
else:
print(f" ❌ Block rejected: {result}")
print(f" 🔍 Debug: Block size {len(block_hex)//2} bytes, {len(job['transactions'])} transactions")
return False, f"Block rejected: {result}"
except Exception as e:
print(f"Share submission error: {e}")
return False, f"Submission error: {e}"
def send_stratum_response(self, client, msg_id, result, error=None):
"""Send Stratum response to client"""
try:
response = {
"id": msg_id,
"result": result,
"error": error
}
message = json.dumps(response) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send response error: {e}")
def send_stratum_notification(self, client, method, params):
"""Send Stratum notification to client"""
try:
notification = {
"id": None,
"method": method,
"params": params
}
message = json.dumps(notification) + "\n"
client.send(message.encode('utf-8'))
except Exception as e:
print(f"Send notification error: {e}")
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", [])
if method == "mining.subscribe":
# Generate unique extranonce1 for this connection
self.extranonce1_counter += 1
extranonce1 = f"{self.extranonce1_counter:08x}"
# Store extranonce1 for this client
if addr not in self.clients:
self.clients[addr] = {}
self.clients[addr]['extranonce1'] = extranonce1
# Subscribe response
self.send_stratum_response(client, msg_id, [
[["mining.set_difficulty", "subscription_id"], ["mining.notify", "subscription_id"]],
extranonce1,
4 # extranonce2 size
])
# Send difficulty - MUCH LOWER for testing
self.send_stratum_notification(client, "mining.set_difficulty", [0.00001])
# Send initial job
if self.current_job:
self.send_job_to_client(client, self.current_job)
else:
if self.get_block_template():
self.send_job_to_client(client, self.current_job)
elif method == "mining.authorize":
username = params[0] if params else "anonymous"
self.clients[addr]['username'] = username
self.send_stratum_response(client, msg_id, True)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 🔐 [{addr}] Authorized as {username}")
elif method == "mining.extranonce.subscribe":
self.send_stratum_response(client, msg_id, True)
elif method == "mining.submit":
if len(params) >= 5:
username = params[0]
job_id = params[1]
extranonce2 = params[2]
ntime = params[3]
nonce = params[4]
print(f"[{addr}] Submit: {username} | job={job_id} | nonce={nonce}")
# Always validate against current job
if self.current_job:
extranonce1 = self.clients[addr].get('extranonce1', '00000000')
# Submit share
success, message = self.submit_share(self.current_job, extranonce1, extranonce2, ntime, nonce)
# Always accept shares for debugging, even if they don't meet target
self.send_stratum_response(client, msg_id, True)
if success and "Block found" in message:
# Get new job after block found
threading.Thread(target=self.update_job_after_block, daemon=True).start()
else:
self.send_stratum_response(client, msg_id, True)
else:
self.send_stratum_response(client, msg_id, False, "Invalid parameters")
else:
print(f"[{addr}] Unknown method: {method}")
self.send_stratum_response(client, msg_id, None, "Unknown method")
except json.JSONDecodeError:
print(f"[{addr}] Invalid JSON: {message}")
except Exception as e:
print(f"[{addr}] Message handling error: {e}")
def send_job_to_client(self, client, job):
"""Send mining job to specific client"""
try:
self.send_stratum_notification(client, "mining.notify", [
job["job_id"],
job["prevhash"],
"", # coinb1 (empty for now - miner handles coinbase)
"", # coinb2 (empty for now - miner handles coinbase)
[], # merkle_branch (empty for now - we calculate merkle root)
f"{job['version']:08x}",
job["bits"],
job["ntime"],
True # clean_jobs
])
except Exception as e:
print(f"Failed to send job: {e}")
def update_job_after_block(self):
"""Update job after a block is found"""
time.sleep(2) # Brief delay to let network propagate
if self.get_block_template():
self.broadcast_new_job()
def broadcast_new_job(self):
"""Broadcast new job to all connected clients"""
if not self.current_job:
return
print(f"Broadcasting new job to {len(self.clients)} clients")
for addr, client_data in list(self.clients.items()):
try:
if 'socket' in client_data:
self.send_job_to_client(client_data['socket'], self.current_job)
except Exception as e:
print(f"Failed to send job to {addr}: {e}")
def handle_client(self, client, addr):
"""Handle individual client connection"""
print(f"[{addr}] Connected")
if addr not in self.clients:
self.clients[addr] = {}
self.clients[addr]['socket'] = client
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"""
balance_log_counter = 0
while self.running:
try:
time.sleep(30) # Update every 30 seconds
old_height = self.current_job['height'] if self.current_job else 0
if self.get_block_template():
new_height = self.current_job['height']
if new_height > old_height:
print(f"New block detected! Broadcasting new job...")
self.broadcast_new_job()
# Log wallet balance every 10 minutes (20 cycles of 30 seconds)
balance_log_counter += 1
if balance_log_counter >= 20:
self.log_wallet_balance()
balance_log_counter = 0
except Exception as e:
print(f"Job updater error: {e}")
def start(self):
"""Start the Stratum proxy 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")
print(f"Current height: {blockchain_info.get('blocks', 'unknown')}")
print(f"Chain: {blockchain_info.get('chain', 'unknown')}")
# Log initial wallet balance
self.log_wallet_balance()
# Get initial block template
if not self.get_block_template():
print("Failed to get initial block template!")
return
# Start job updater thread
job_thread = threading.Thread(target=self.job_updater, daemon=True)
job_thread.start()
# 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)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 🚀 Mining Stratum proxy ready!")
print(f" 📡 Listening on {self.stratum_host}:{self.stratum_port}")
print(f" 💰 Mining to: {self.target_address}")
print(f" 📊 Current job: {self.current_job['job_id'] if self.current_job else 'None'}")
print(f" 📝 Mining log: {self.log_file}")
print("")
print(" 🔧 Miner command:")
print(f" ./cpuminer -a rinhash -o stratum+tcp://{self.stratum_host}:{self.stratum_port} -u worker1 -p x -t 4")
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("\nShutting down...")
self.running = False
break
except Exception as e:
print(f"Server error: {e}")
except Exception as e:
print(f"Failed to start server: {e}")
finally:
print("Server stopped")
if __name__ == "__main__":
proxy = RinCoinStratumProxy()
proxy.start()