#!/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(' bytes: outputs_blob = b'' outputs_list = [] # Main output outputs_list.append(struct.pack(' 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(' 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()