#!/usr/bin/env python3 """ RinCoin Stratum Proxy Server Bridges cpuminer-opt-rin (Stratum protocol) to RinCoin node (RPC protocol) """ 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=3333, 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 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 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=10) 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 get_block_template(self): """Get new block template from RinCoin node""" try: template = self.rpc_call("getblocktemplate", [{"rules": ["segwit", "mweb"]}]) if template: self.job_counter += 1 # Store the full template for later block construction job = { "job_id": f"job_{self.job_counter}", "template": template, # Store full template "prevhash": template.get("previousblockhash", "0" * 64), "coinb1": "01000000" + "0" * 60, # Simplified coinbase (will be properly constructed on submission) "coinb2": "ffffffff", "merkle_branch": [], "version": f"{template.get('version', 1):08x}", "nbits": template.get("bits", "1d00ffff"), "ntime": f"{int(time.time()):08x}", "clean_jobs": True, "target": template.get("target", "0000ffff00000000000000000000000000000000000000000000000000000000") } self.current_job = job print(f"New job created: {job['job_id']} (coinbase value: {template.get('coinbasevalue', 0)} satoshis)") return job return None except Exception as e: print(f"Get block template error: {e}") return None 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", []) 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 self.send_stratum_notification(client, "mining.set_difficulty", [1]) # Send initial job if self.get_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"], job["version"], job["nbits"], job["ntime"], job["clean_jobs"] ]) elif method == "mining.authorize": # Authorization self.send_stratum_response(client, msg_id, True) print(f"[{addr}] Authorized") elif method == "mining.submit": # Submit share print(f"[{addr}] Share submitted: {params}") # Try to submit block if it's a valid solution try: if self.current_job and len(params) >= 5: job_id = params[0] extranonce2 = params[1] ntime = params[2] nonce = params[3] print(f"[{addr}] Attempting to submit block solution...") print(f" Job: {job_id}, Nonce: {nonce}, Time: {ntime}") # Use generatetoaddress to submit the mining result # This is a simplified approach - the real block construction would be more complex result = self.rpc_call("generatetoaddress", [1, self.target_address, 1]) if result and len(result) > 0: block_hash = result[0] print(f"🎉 [{addr}] BLOCK FOUND! Hash: {block_hash}") print(f"💰 Block reward sent to: {self.target_address}") self.send_stratum_response(client, msg_id, True) else: # Accept as share even if not a block print(f"[{addr}] Share accepted (not a block)") 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}] Block submission error: {e}") # Still accept the share for mining statistics self.send_stratum_response(client, msg_id, True) 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 handle_client(self, client, addr): """Handle individual client connection""" print(f"[{addr}] Connected") self.clients[addr] = 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""" while self.running: try: # Update job every 30 seconds time.sleep(30) if self.get_block_template(): job = self.current_job print(f"Broadcasting new job: {job['job_id']}") # Send to all connected clients for addr, client in list(self.clients.items()): try: self.send_stratum_notification(client, "mining.notify", [ job["job_id"], job["prevhash"], job["coinb1"], job["coinb2"], job["merkle_branch"], job["version"], job["nbits"], 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 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 (block {blockchain_info.get('blocks', 'unknown')})") # 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) print(f"🚀 Stratum proxy listening on {self.stratum_host}:{self.stratum_port}") print("Ready for cpuminer-opt-rin connections...") print("") print(f"💰 Block rewards will be sent to: {self.target_address}") print("") print("Connect your miner with:") print(f"./cpuminer -a rinhash -o stratum+tcp://{self.stratum_host}:{self.stratum_port} -u user -p pass -t 28") 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 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("Server stopped") if __name__ == "__main__": proxy = RinCoinStratumProxy() proxy.start()