#!/usr/bin/env python3 """ stratum_proxy.py RinCoin Stratum Proxy Server DEBUG RPC: we get node logs with 'docker logs --tail=200 rincoin-node' MINE: /mnt/shared/DEV/repos/d-popov.com/mines/rin/miner/cpuminer-opt-rin/cpuminer -a rinhash -o stratum+tcp://localhost:3334 -u x -p x -t 32 """ 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', submit_all_blocks=False, submit_threshold=0.1): 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.submit_all_blocks = submit_all_blocks # For debugging: submit blocks that meet this fraction of network difficulty self.submit_threshold = submit_threshold # Configurable percentage of network difficulty self.clients = {} self.job_counter = 0 self.current_job = None self.running = True self.extranonce1_counter = 0 # Dynamic difficulty adjustment self.share_stats = {} # Track shares per client self.last_difficulty_adjustment = time.time() self.target_share_interval = 15 # Target: 1 share every 15 seconds (optimal for 1-min blocks) # Production monitoring self.stats = { 'start_time': time.time(), 'total_shares': 0, 'accepted_shares': 0, 'rejected_shares': 0, 'blocks_found': 0, 'total_hashrate': 0, 'connections': 0, 'last_share_time': time.time(), 'shares_last_minute': [], 'current_share_rate': 0.0 } self.max_connections = 50 # Production limit # 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 calculate_merkle_branches(self, tx_hashes, tx_index): """Calculate merkle branches for a specific transaction index""" try: if not tx_hashes or tx_index >= len(tx_hashes): return [] branches = [] current_index = tx_index # Start with the full list of transaction hashes hashes = tx_hashes[:] while len(hashes) > 1: if len(hashes) % 2 == 1: hashes.append(hashes[-1]) # Duplicate last hash if odd # Find the partner for current_index partner_index = current_index ^ 1 # Flip the least significant bit if partner_index < len(hashes): # Add the partner hash to branches (in big-endian for stratum) branches.append(hashes[partner_index][::-1].hex()) else: # This shouldn't happen, but add the duplicate if it does branches.append(hashes[current_index][::-1].hex()) # Move to next level 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 current_index //= 2 return branches except Exception as e: print(f"Merkle branches calculation error: {e}") return [] 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 calculate_optimal_difficulty(self, addr, is_new_client=False): """Calculate optimal stratum difficulty for a client based on hashrate and network conditions""" try: # Get current network difficulty network_diff = self.calculate_network_difficulty(self.current_job['target']) if self.current_job else 1.0 if is_new_client: # Calculate difficulty for 4 shares per minute (15 second intervals) # Formula: difficulty = (shares_per_second * 2^32) / hashrate target_shares_per_second = 4 / 60 # 4 shares per minute assumed_hashrate = 680000 # H/s (conservative estimate) calculated_difficulty = (target_shares_per_second * (2**32)) / assumed_hashrate # Scale based on network difficulty to maintain relative percentage target_percentage = calculated_difficulty / network_diff if network_diff > 0 else 0.32 scaled_difficulty = network_diff * target_percentage # Use the calculated difficulty (should be around 421 for 680kH/s) initial_difficulty = calculated_difficulty # Ensure reasonable bounds min_difficulty = 0.0001 # Absolute minimum # IMPORTANT: DO NOT CHANGE THIS VALUE. our pool may grow big in the future, so we need to be able to handle it. max_difficulty = network_diff * 0.1 # Maximum 10% of network initial_difficulty = max(min_difficulty, min(max_difficulty, initial_difficulty)) percentage = (initial_difficulty / network_diff) * 100 print(f" ๐Ÿ“Š NEW CLIENT {addr}: Network diff {network_diff:.6f}") print(f" ๐ŸŽฏ Starting difficulty: {initial_difficulty:.6f} ({percentage:.4f}% of network)") miner_hashrate = self.clients.get(addr, {}).get('estimated_hashrate', 0) if miner_hashrate > 0: print(f" Target: 1 share every {self.target_share_interval}s @ {miner_hashrate:.0f} H/s") else: print(f" Target: 1 share every {self.target_share_interval}s (miner hashrate unknown)") print(f" ๐Ÿ”ง Per-miner adjustment: Difficulty will adapt to each device's actual hashrate") return initial_difficulty # For existing clients, adjust based on actual hashrate performance client_data = self.clients.get(addr, {}) if not client_data: return self.calculate_optimal_difficulty(addr, is_new_client=True) current_time = time.time() connect_time = client_data.get('connect_time', current_time) share_count = client_data.get('share_count', 0) current_difficulty = client_data.get('stratum_difficulty', network_diff * 0.005) estimated_hashrate = client_data.get('estimated_hashrate', 0) # Need at least 3 shares to make adjustments if share_count < 3: return current_difficulty # Calculate target difficulty based on hashrate and desired share interval # Target: 1 share every 15 seconds (self.target_share_interval) if estimated_hashrate > 0: # Formula: difficulty = (hashrate * target_time) / (2^32) diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 target_difficulty = (estimated_hashrate * self.target_share_interval) / (2**32) # Bounds checking min_difficulty = network_diff * 0.0001 # 0.01% of network max_difficulty = network_diff * 0.02 # 2% of network target_difficulty = max(min_difficulty, min(max_difficulty, target_difficulty)) # Conservative adjustment - move only 50% towards target each time adjustment_factor = 0.5 new_difficulty = current_difficulty + (target_difficulty - current_difficulty) * adjustment_factor print(f" ๐Ÿ“Š {addr}: {estimated_hashrate:.0f} H/s, {share_count} shares, adjusting {current_difficulty:.6f} โ†’ {new_difficulty:.6f}") return new_difficulty return current_difficulty except Exception as e: print(f"Difficulty calculation error: {e}") # Fallback to conservative difficulty network_diff = self.calculate_network_difficulty(self.current_job['target']) if self.current_job else 1.0 return max(network_diff * 0.001, 0.0001) def adjust_global_difficulty_if_needed(self): """Adjust difficulty globally if share rate is too high/low""" try: current_rate = self.stats['current_share_rate'] # Target: 0.5-2 shares per second (30-120 second intervals) target_min_rate = 0.008 # ~1 share per 2 minutes target_max_rate = 0.033 # ~1 share per 30 seconds if current_rate > target_max_rate: # Too many shares - increase difficulty for all clients multiplier = min(2.0, current_rate / target_max_rate) print(f" ๐Ÿšจ HIGH SHARE RATE: {current_rate:.3f}/s > {target_max_rate:.3f}/s - increasing difficulty by {multiplier:.2f}x") for addr in self.clients: current_diff = self.clients[addr].get('stratum_difficulty', 0.001) new_diff = min(0.001, current_diff * multiplier) self.clients[addr]['stratum_difficulty'] = new_diff print(f" ๐Ÿ“ˆ {addr}: {current_diff:.6f} โ†’ {new_diff:.6f}") elif current_rate < target_min_rate and current_rate > 0: # Too few shares - decrease difficulty for all clients multiplier = max(0.5, target_min_rate / current_rate) print(f" ๐ŸŒ LOW SHARE RATE: {current_rate:.3f}/s < {target_min_rate:.3f}/s - decreasing difficulty by {multiplier:.2f}x") for addr in self.clients: current_diff = self.clients[addr].get('stratum_difficulty', 0.001) new_diff = max(0.000001, current_diff / multiplier) self.clients[addr]['stratum_difficulty'] = new_diff print(f" ๐Ÿ“‰ {addr}: {current_diff:.6f} โ†’ {new_diff:.6f}") except Exception as e: print(f"Global difficulty adjustment error: {e}") def adjust_client_difficulty(self, addr): """Adjust difficulty for a specific client if needed""" try: current_time = time.time() # Only adjust every 2 minutes minimum last_adjustment = self.clients[addr].get('last_difficulty_adjustment', 0) if current_time - last_adjustment < 120: return new_difficulty = self.calculate_optimal_difficulty(addr, is_new_client=False) current_difficulty = self.clients[addr].get('stratum_difficulty', 0.001) # Only send update if difficulty changed significantly (>20%) if abs(new_difficulty - current_difficulty) / current_difficulty > 0.2: self.clients[addr]['stratum_difficulty'] = new_difficulty self.clients[addr]['last_difficulty_adjustment'] = current_time # Send new difficulty to client if 'socket' in self.clients[addr]: self.send_stratum_notification(self.clients[addr]['socket'], "mining.set_difficulty", [new_difficulty]) print(f" ๐ŸŽฏ Updated difficulty for {addr}: {current_difficulty:.6f} โ†’ {new_difficulty:.6f}") except Exception as e: print(f"Difficulty adjustment error for {addr}: {e}") def submit_share(self, job, extranonce1, extranonce2, ntime, nonce, addr=None, 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('= client_stratum_diff meets_network_difficulty = share_difficulty >= network_difficulty # Track share rate current_time = time.time() self.stats['shares_last_minute'].append(current_time) # Remove shares older than 60 seconds self.stats['shares_last_minute'] = [t for t in self.stats['shares_last_minute'] if current_time - t <= 60] self.stats['current_share_rate'] = len(self.stats['shares_last_minute']) / 60.0 # Enhanced logging timestamp = time.strftime("%Y-%m-%d %H:%M:%S") network_percentage = (share_difficulty / network_difficulty) * 100 if network_difficulty > 0 else 0 stratum_percentage = (share_difficulty / client_stratum_diff) * 100 if client_stratum_diff > 0 else 0 # Progress indicator based on difficulty comparison if meets_network_difficulty: progress_icon = "๐ŸŽ‰" # Block found! elif meets_stratum_difficulty: progress_icon = "โœ…" # Valid share for stratum elif network_percentage >= 50: progress_icon = "๐Ÿ”ฅ" # Very close to network elif network_percentage >= 10: progress_icon = "โšก" # Getting warm elif network_percentage >= 1: progress_icon = "๐Ÿ’ซ" # Some progress else: progress_icon = "๐Ÿ“Š" # Low progress # Calculate network and pool hashrates block_time = 60 # RinCoin 1-minute blocks network_hashrate = (network_difficulty * (2**32)) / block_time network_mhs = network_hashrate / 1e6 # Calculate pool hashrate (sum of all connected miners) pool_hashrate = 0 for client_addr, client_data in self.clients.items(): pool_hashrate += client_data.get('estimated_hashrate', 0) pool_mhs = pool_hashrate / 1e6 # Calculate percentages pool_network_percentage = (pool_mhs / network_mhs) * 100 if network_mhs > 0 else 0 miner_pool_percentage = (0.87 / pool_mhs) * 100 if pool_mhs > 0 else 0 # Assuming 870kH/s miner print(f"[{timestamp}] {progress_icon} SHARE: job={job['job_id']} | nonce={nonce} | hash={block_hash_hex[:16]}...") print(f" ๐ŸŽฏ Share Diff: {share_difficulty:.2e} | Stratum Diff: {client_stratum_diff:.6f} | Network Diff: {network_difficulty:.6f}") print(f" ๐Ÿ“ˆ Progress: {network_percentage:.4f}% of network, {stratum_percentage:.1f}% of stratum") print(f" ๐Ÿ“Š Share Rate: {self.stats['current_share_rate']:.2f}/s | Total: {len(self.stats['shares_last_minute'])}/min") print(f" ๐ŸŒ Network: {network_mhs:.1f} MH/s | Pool: {pool_mhs:.1f} MH/s ({pool_network_percentage:.2f}% of network)") print(f" โ›๏ธ Miner: 0.87 MH/s ({miner_pool_percentage:.1f}% of pool) | Expected solo: {network_mhs/0.87:.0f}h") print(f" ๐Ÿ“ Target: {job['target'][:16]}... | Height: {job['height']}") print(f" โฐ Time: {ntime} | Extranonce: {extranonce1}:{extranonce2}") print(f" ๐Ÿ” Difficulty Check: Share {share_difficulty:.2e} vs Stratum {client_stratum_diff:.6f} = {'โœ…' if meets_stratum_difficulty else 'โŒ'}") print(f" ๐Ÿ” Difficulty Check: Share {share_difficulty:.2e} vs Network {network_difficulty:.6f} = {'โœ…' if meets_network_difficulty else 'โŒ'}") # Initialize submission variables submit_network_difficulty = meets_network_difficulty submit_debug_threshold = share_difficulty >= (network_difficulty * self.submit_threshold) if submit_network_difficulty: submit_reason = "meets network difficulty" elif submit_debug_threshold: submit_reason = f"meets debug threshold ({self.submit_threshold*100:.0f}% of network)" else: submit_reason = "does not meet minimum requirements" # Handle submit_all_blocks mode - bypass all difficulty checks if self.submit_all_blocks: print(f" ๐Ÿงช TEST MODE: Submitting ALL shares to node for validation") # Update stats for test mode self.stats['total_shares'] += 1 self.stats['accepted_shares'] += 1 # Always submit in test mode should_submit = True else: # Normal mode - check stratum difficulty properly if not meets_stratum_difficulty: # Share doesn't meet minimum stratum difficulty - reject print(f" โŒ Share rejected (difficulty too low: {share_difficulty:.2e} < {client_stratum_diff:.6f})") self.stats['total_shares'] += 1 self.stats['rejected_shares'] += 1 return False, "Share does not meet stratum difficulty" # Valid stratum share! Update client stats if addr and addr in self.clients: current_time = time.time() self.clients[addr]['share_count'] = self.clients[addr].get('share_count', 0) + 1 self.clients[addr]['last_share_time'] = current_time # Calculate hashrate from this share # Hashrate = difficulty * 2^32 / time_to_find_share client_difficulty = self.clients[addr].get('stratum_difficulty', 0.001) last_share_time = self.clients[addr].get('last_share_time', current_time - 60) time_since_last = max(1, current_time - last_share_time) # Avoid division by zero # Calculate instantaneous hashrate for this share diff1_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 instant_hashrate = (client_difficulty * (2**32)) / time_since_last # Store hashrate sample (keep last 10 samples) if 'hashrate_samples' not in self.clients[addr]: self.clients[addr]['hashrate_samples'] = [] self.clients[addr]['hashrate_samples'].append((current_time, instant_hashrate)) # Keep only last 10 samples if len(self.clients[addr]['hashrate_samples']) > 10: self.clients[addr]['hashrate_samples'] = self.clients[addr]['hashrate_samples'][-10:] # Calculate average hashrate from recent samples if self.clients[addr]['hashrate_samples']: total_hashrate = sum(hr for _, hr in self.clients[addr]['hashrate_samples']) self.clients[addr]['estimated_hashrate'] = total_hashrate / len(self.clients[addr]['hashrate_samples']) print(f" โ›๏ธ Miner {addr}: {self.clients[addr]['estimated_hashrate']:.0f} H/s (avg of {len(self.clients[addr]['hashrate_samples'])} samples)") # Update global stats self.stats['total_shares'] += 1 self.stats['accepted_shares'] += 1 # Check if we should adjust difficulty self.adjust_client_difficulty(addr) # Global difficulty adjustment based on share rate self.adjust_global_difficulty_if_needed() should_submit = submit_network_difficulty or submit_debug_threshold # Submit block if conditions are met if should_submit: if submit_network_difficulty: print(f" ๐ŸŽ‰ VALID 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})") print(f" ๐Ÿ“‹ Submit reason: {submit_reason}") # Log the found hash - ONLY for actual network-valid blocks reward_rin = job['coinbasevalue'] / 100000000 self.log_hash_found(block_hash_hex, share_difficulty, job['height'], reward_rin, nonce, ntime) elif submit_debug_threshold: print(f" ๐Ÿงช DEBUG SUBMISSION! Hash: {block_hash_hex} ({submit_reason})") print(f" ๐Ÿ“Š Block height: {job['height']} | Difficulty: {share_difficulty:.6f}") print(f" ๐Ÿ’ก This is a test submission - not a real block") else: print(f" ๐Ÿงช TEST BLOCK SUBMISSION! Hash: {block_hash_hex} (submit_all_blocks=True)") print(f" ๐Ÿ“Š Block height: {job['height']} | Difficulty: {share_difficulty:.6f}") print(f" ๐Ÿ’ก This is a test submission - not a real block") # 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...") print(f" ๐Ÿš€ Calling RPC: submitblock([block_hex={len(block_hex)//2}_bytes])") result = self.rpc_call("submitblock", [block_hex]) print(f" ๐Ÿ“ก RPC RESPONSE: {result}") # Log the actual RPC response if result is None: print(f" โœ… Block accepted by network!") print(f" ๐ŸŽŠ SUCCESS: Block {job['height']} submitted successfully!") print(f" ๐Ÿ’ฐ Reward earned: {job['coinbasevalue']/100000000:.8f} RIN") # Update block stats self.stats['blocks_found'] += 1 # 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") print(f" ๐Ÿ“‹ Full RPC error details: {result}") return False, f"Block rejected: {result}" else: # Valid stratum share but not network-valid block (normal mode only) if not self.submit_all_blocks: print(f" โœ… Valid stratum share accepted") return True, "Valid stratum share" else: # This shouldn't happen in test mode since should_submit would be True return True, "Test mode share processed" 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: # Debug: log raw message print(f"[{addr}] Raw message: {repr(message)}") data = json.loads(message.strip()) method = data.get("method") msg_id = data.get("id") params = data.get("params", []) print(f"[{addr}] Parsed: method={method}, id={msg_id}, params={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 ]) # Calculate optimal stratum difficulty for this client initial_difficulty = self.calculate_optimal_difficulty(addr, is_new_client=True) # Store stratum difficulty for this client if addr not in self.clients: self.clients[addr] = {} self.clients[addr]['stratum_difficulty'] = initial_difficulty self.clients[addr]['last_share_time'] = time.time() self.clients[addr]['share_count'] = 0 self.clients[addr]['connect_time'] = time.time() self.clients[addr]['hashrate_samples'] = [] # Store recent hashrate measurements self.clients[addr]['estimated_hashrate'] = 0 # Current estimated hashrate self.send_stratum_notification(client, "mining.set_difficulty", [initial_difficulty]) print(f" ๐ŸŽฏ Set initial difficulty {initial_difficulty:.6f} for {addr}") # 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 == "login": # Handle xmrig's login method (JSON-RPC format with object params) if isinstance(params, dict): # xmrig format: {"login": "username", "pass": "password", "agent": "...", "algo": [...]} username = params.get('login', 'anonymous') password = params.get('pass', 'x') agent = params.get('agent', 'unknown') algorithms = params.get('algo', []) else: # Standard stratum format: ["username", "password"] username = params[0] if params else "anonymous" password = params[1] if len(params) > 1 else "x" agent = "unknown" algorithms = [] self.clients[addr]['username'] = username self.clients[addr]['password'] = password self.clients[addr]['agent'] = agent self.clients[addr]['algorithms'] = algorithms # Check if rinhash is supported rinhash_supported = any('rinhash' in algo.lower() or 'rin' in algo.lower() for algo in algorithms) if not rinhash_supported: print(f"[{addr}] Warning: rinhash not in supported algorithms: {algorithms}") self.send_stratum_response(client, msg_id, True) timestamp = time.strftime("%Y-%m-%d %H:%M:%S") print(f"[{timestamp}] ๐Ÿ” [{addr}] xmrig Login as {username} (agent: {agent})") print(f"[{addr}] Supported algorithms: {algorithms}") # Send initial job after login 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.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, addr) # 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 as e: print(f"[{addr}] Invalid JSON: {message}") print(f"[{addr}] JSON Error: {e}") except Exception as e: print(f"[{addr}] Message handling error: {e}") print(f"[{addr}] Error type: {type(e).__name__}") def send_job_to_client(self, client, job): """Send mining job to specific client with proper stratum parameters""" try: # Get network difficulty for display network_difficulty = self.calculate_network_difficulty(job['target']) # Build proper coinbase transaction parts for stratum protocol template = job.get('template', {}) height = job.get('height', 0) # Encode height as minimal push (BIP34 compliance) if height == 0: height_push = b'' else: height_bytes = struct.pack(' 1 and height_bytes[-1] == 0: height_bytes = height_bytes[:-1] height_push = bytes([len(height_bytes)]) + height_bytes # Build coinbase input script: height + arbitrary data + extranonces coinb1_script = height_push + b'/RinCoin/' # Build coinbase transaction structure # Version (4 bytes) coinb1 = struct.pack('= self.max_connections: print(f"[{addr}] Connection rejected - max connections ({self.max_connections}) reached") client.close() return print(f"[{addr}] Connected (clients: {len(self.clients) + 1}/{self.max_connections})") if addr not in self.clients: self.clients[addr] = {} self.clients[addr]['socket'] = client self.stats['connections'] = len(self.clients) 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] self.stats['connections'] = len(self.clients) print(f"[{addr}] Disconnected (clients: {len(self.clients)}/{self.max_connections})") def print_stats(self): """Print pool statistics""" try: uptime = time.time() - self.stats['start_time'] uptime_str = f"{int(uptime//3600)}h {int((uptime%3600)//60)}m" accept_rate = 0 if self.stats['total_shares'] > 0: accept_rate = (self.stats['accepted_shares'] / self.stats['total_shares']) * 100 print(f"\n๐Ÿ“Š POOL STATISTICS:") print(f" โฐ Uptime: {uptime_str}") print(f" ๐Ÿ‘ฅ Connected miners: {self.stats['connections']}") print(f" ๐Ÿ“ˆ Shares: {self.stats['accepted_shares']}/{self.stats['total_shares']} ({accept_rate:.1f}% accepted)") print(f" ๐ŸŽ‰ Blocks found: {self.stats['blocks_found']}") print(f" ๐ŸŽฏ Network difficulty: {self.calculate_network_difficulty(self.current_job['target']) if self.current_job else 'unknown':.6f}") # Calculate hashrate estimate from connected clients total_hashrate = 0 for addr, client_data in self.clients.items(): share_count = client_data.get('share_count', 0) connect_time = client_data.get('connect_time', time.time()) mining_duration = time.time() - connect_time if mining_duration > 60 and share_count > 0: stratum_diff = client_data.get('stratum_difficulty', 0.001) # Rough hashrate estimate: shares * difficulty * 2^32 / time hashrate = (share_count * stratum_diff * 4294967296) / mining_duration total_hashrate += hashrate print(f" ๐Ÿ”ฅ {addr}: ~{hashrate/1000:.0f} kH/s") if total_hashrate > 0: print(f" ๐Ÿš€ Total pool hashrate: ~{total_hashrate/1000:.0f} kH/s") print() except Exception as e: print(f"Stats error: {e}") def job_updater(self): """Periodically update mining jobs""" balance_log_counter = 0 stats_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 # Print stats every 5 minutes (10 cycles of 30 seconds) stats_counter += 1 if stats_counter >= 10: self.print_stats() stats_counter = 0 except Exception as e: print(f"Job updater error: {e}") def validate_mining_capability(self): """Validate that we can mine valid blocks against the RinCoin node""" try: print("๐Ÿ” Validating mining capability...") # Get block template template = self.rpc_call("getblocktemplate", [{"rules": ["segwit", "mweb"]}]) if not template: print("โŒ Cannot get block template") return False # Test address validation result = self.rpc_call("validateaddress", [self.target_address]) if not result or not result.get('isvalid'): print(f"โŒ Target address {self.target_address} is invalid") return False print(f"โœ… Target address {self.target_address} is valid") # Test coinbase construction coinbase_wit, coinbase_nowit = self.build_coinbase_transaction_for_address( template, "00000001", "01000000", self.target_address) if not coinbase_wit or not coinbase_nowit: print("โŒ Cannot construct coinbase transaction") return False print(f"โœ… Coinbase construction works") # Test block header construction with dummy values test_nonce = "12345678" test_ntime = f"{int(time.time()):08x}" # Calculate merkle root coinbase_txid = hashlib.sha256(hashlib.sha256(coinbase_nowit).digest()).digest()[::-1] merkle_root = self.calculate_merkle_root(coinbase_txid, template.get('transactions', [])) # Build test header header = b'' header += struct.pack('