diff --git a/MINE/rin/start_stratum_proxy.sh b/MINE/rin/start_stratum_proxy.sh index 36c03d7..f6ad91f 100644 --- a/MINE/rin/start_stratum_proxy.sh +++ b/MINE/rin/start_stratum_proxy.sh @@ -31,22 +31,22 @@ python3 -c "import requests" 2>/dev/null || { echo "✅ Python dependencies ready" -# Check if port 3333 is already in use -if netstat -tln | grep -q ":3333 "; then +# Check if port 3334 is already in use +if netstat -tln | grep -q ":3334 "; then echo "" - echo "⚠️ Port 3333 is already in use!" + echo "⚠️ Port 3334 is already in use!" echo "" - echo "🔍 Process using port 3333:" - sudo netstat -tlnp | grep ":3333 " || echo "Could not determine process" + 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:3333 | xargs sudo kill -9" + 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 3333..." - sudo lsof -ti:3333 | xargs sudo kill -9 2>/dev/null || echo "No processes to kill" + 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..." @@ -59,7 +59,7 @@ echo "🚀 Starting Stratum Proxy Server..." echo "This will bridge cpuminer-opt-rin to your RinCoin node" 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:3333 -u user -p pass -t 28\"" +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 "" diff --git a/MINE/rin/stratum_pool.py b/MINE/rin/stratum_pool.py index f32b699..692b94f 100644 --- a/MINE/rin/stratum_pool.py +++ b/MINE/rin/stratum_pool.py @@ -18,28 +18,22 @@ from requests.auth import HTTPBasicAuth # Import web interface from pool_web_interface import start_web_interface -class RinCoinMiningPool: +# Import stratum base class +from stratum_proxy import RinCoinStratumBase + +class RinCoinMiningPool(RinCoinStratumBase): def __init__(self, stratum_host='0.0.0.0', stratum_port=3333, rpc_host='127.0.0.1', rpc_port=9556, rpc_user='rinrpc', rpc_password='745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90', pool_address='rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q', pool_fee_percent=1.0): - 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 + # Initialize base class + super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, pool_address) + self.pool_address = pool_address self.pool_fee_percent = pool_fee_percent - # Miner tracking - self.clients = {} # {addr: {'client': socket, 'worker': str, 'user': str, 'shares': 0, 'last_share': time}} - self.job_counter = 0 - self.current_job = None - self.running = True - # Pool statistics self.total_shares = 0 self.total_blocks = 0 @@ -107,75 +101,26 @@ class RinCoinMiningPool: self.db.commit() - 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": "mining_pool", - "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 - - job = { - "job_id": f"job_{self.job_counter}", - "template": template, - "prevhash": template.get("previousblockhash", "0" * 64), - "coinb1": "01000000" + "0" * 60, - "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 get_pool_block_template(self): + """Get new block template and create pool-style job""" + template = super().get_block_template() + if template: + # Convert to pool-style job format if needed + job = self.current_job + if job: + # Add pool-specific fields + job["coinb1"] = "01000000" + "0" * 60 + job["coinb2"] = "ffffffff" + job["merkle_branch"] = [] + job["clean_jobs"] = True + return job + return None def validate_rincoin_address(self, address): """Validate if an address is a valid RinCoin address""" - if not address or not address.startswith('rin'): - return False - try: - result = self.rpc_call("validateaddress", [address]) - return result and result.get('isvalid', False) - except Exception as e: - print(f"Address validation error: {e}") + return self.decode_bech32_address(address) is not None + except: return False def register_miner(self, user, worker, address=None): @@ -297,33 +242,7 @@ class RinCoinMiningPool: if miners_without_addresses: print(f"📊 Summary: {len(miners_with_addresses)} miners with addresses, {len(miners_without_addresses)} without (rewards to pool)") - 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}") + # Use inherited send_stratum_response and send_stratum_notification from base class def handle_stratum_message(self, client, addr, message): """Handle incoming Stratum message from miner""" @@ -347,7 +266,7 @@ class RinCoinMiningPool: self.send_stratum_notification(client, "mining.set_difficulty", [0.0001]) # Send initial job - if self.get_block_template(): + if self.get_pool_block_template(): job = self.current_job self.send_stratum_notification(client, "mining.notify", [ job["job_id"], @@ -355,8 +274,8 @@ class RinCoinMiningPool: job["coinb1"], job["coinb2"], job["merkle_branch"], - job["version"], - job["nbits"], + f"{job['version']:08x}", + job["bits"], job["ntime"], job["clean_jobs"] ]) @@ -416,7 +335,8 @@ class RinCoinMiningPool: 'miner_id': miner_id, 'address': miner_address, 'shares': 0, - 'last_share': time.time() + 'last_share': time.time(), + 'extranonce1': '00000000' # Default extranonce1 } if miner_address: @@ -432,101 +352,70 @@ class RinCoinMiningPool: if addr not in self.clients: self.send_stratum_response(client, msg_id, False, "Not authorized") return - + miner_info = self.clients[addr] - + try: if self.current_job and len(params) >= 5: - job_id = params[0] - extranonce2 = params[1] - ntime = params[2] - nonce = params[3] + username = params[0] + job_id = params[1] + extranonce2 = params[2] + ntime = params[3] + nonce = params[4] - # Calculate actual difficulty from the share submission - # The miner reports its hashrate, so we need to calculate - # the difficulty that would match that hashrate - # For a miner reporting ~381 kH/s, we need to calculate - # the difficulty that would result in that hashrate - # H = D * 2^32 / dt - # D = H * dt / 2^32 - # If miner reports 381 kH/s and submits every ~15 seconds: - # D = 381000 * 15 / 2^32 ≈ 0.00133 - actual_difficulty = 0.00133 # Calculated to match ~381 kH/s + # Use base class to validate and submit share + extranonce1 = miner_info.get('extranonce1', '00000000') + miner_address = miner_info.get('address') - # Record share with calculated difficulty - self.record_share(miner_info['miner_id'], job_id, actual_difficulty) + # For pool mining, always mine to pool address + success, message = self.submit_share( + self.current_job, extranonce1, extranonce2, ntime, nonce, + target_address=self.pool_address + ) - # Calculate instantaneous hashrate based on time between shares - now_ts = time.time() - prev_ts = miner_info.get('last_share') or now_ts - dt = max(now_ts - prev_ts, 1e-3) # Minimum 1ms to avoid division by zero - - # H = D * 2^32 / dt - miner_hashrate = actual_difficulty * (2**32) / dt - - # If this is the first share, estimate based on reported hashrate - if miner_info['shares'] == 0: - miner_hashrate = 381000 # ~381 kH/s as reported by miner - miner_info['shares'] += 1 - miner_info['last_share'] = now_ts - - # Persist miner last_hashrate - try: - cursor = self.db.cursor() - cursor.execute('UPDATE miners SET last_share = CURRENT_TIMESTAMP, last_hashrate = ? WHERE id = ?', (miner_hashrate, miner_info['miner_id'])) - self.db.commit() - except Exception as e: - print(f"DB update last_hashrate error: {e}") - - # Update pool hashrate as sum of current miners' last rates - try: - cursor = self.db.cursor() - cursor.execute('SELECT COALESCE(SUM(last_hashrate), 0) FROM miners') - total_rate = cursor.fetchone()[0] or 0.0 - self.pool_hashrate = total_rate - except Exception as e: - print(f"Pool hashrate sum error: {e}") - - print(f"[{addr}] ✅ Share accepted from {miner_info['user']}.{miner_info['worker']} (Total: {miner_info['shares']})") - - # Send acceptance response - self.send_stratum_response(client, msg_id, True, None) - - # Try to submit block if it's a valid solution - print(f"[{addr}] 🔍 Attempting to submit block solution...") - - # Use generatetoaddress to submit the mining result - # Always use pool address for block submission (rewards will be distributed later) - result = self.rpc_call("generatetoaddress", [1, self.pool_address, 1]) - - if result and len(result) > 0: - block_hash = result[0] + if success: + # Record share with estimated difficulty + actual_difficulty = 0.00133 # Estimated for ~381 kH/s + self.record_share(miner_info['miner_id'], job_id, actual_difficulty) - # Get block info - block_info = self.rpc_call("getblock", [block_hash]) - if block_info: - block_height = block_info.get('height', 0) - coinbase_tx = block_info.get('tx', [])[0] if block_info.get('tx') else None - - # Get coinbase value (simplified) - total_reward = 50.0 # Default block reward - - print(f"🎉 [{addr}] BLOCK FOUND! Hash: {block_hash}") - print(f"💰 Block reward: {total_reward} RIN") - - # Distribute rewards to miners with valid addresses - self.distribute_block_reward(block_hash, block_height, total_reward) + # Update miner stats + now_ts = time.time() + prev_ts = miner_info.get('last_share') or now_ts + dt = max(now_ts - prev_ts, 1e-3) + miner_hashrate = actual_difficulty * (2**32) / dt + if miner_info['shares'] == 0: + miner_hashrate = 381000 # Default estimate + miner_info['shares'] += 1 + miner_info['last_share'] = now_ts + + # Update database + try: + cursor = self.db.cursor() + cursor.execute('UPDATE miners SET last_share = CURRENT_TIMESTAMP, last_hashrate = ? WHERE id = ?', + (miner_hashrate, miner_info['miner_id'])) + self.db.commit() + except Exception as e: + print(f"DB update error: {e}") + + print(f"[{addr}] ✅ Share accepted from {miner_info['user']}.{miner_info['worker']} (Total: {miner_info['shares']})") self.send_stratum_response(client, msg_id, True) + + # If block was found, distribute rewards + if "Block found" in message: + print(f"🎉 [{addr}] BLOCK FOUND!") + # Get block info and distribute rewards + total_reward = self.current_job['coinbasevalue'] / 100000000 if self.current_job else 25.0 + self.distribute_block_reward("pending", self.current_job['height'] if self.current_job else 0, total_reward) else: - # Accept as share even if not a block + # Accept as share for pool statistics even if block validation fails self.send_stratum_response(client, msg_id, True) else: print(f"[{addr}] Invalid share parameters") self.send_stratum_response(client, msg_id, False, "Invalid parameters") except Exception as e: - print(f"[{addr}] Block submission error: {e}") + print(f"[{addr}] Share processing error: {e}") # Still accept the share for mining statistics self.send_stratum_response(client, msg_id, True) @@ -590,7 +479,7 @@ class RinCoinMiningPool: ) if should_create_job: - if self.get_block_template(): + if self.get_pool_block_template(): job = self.current_job last_job_time = time.time() last_block_height = current_height @@ -606,8 +495,8 @@ class RinCoinMiningPool: job["coinb1"], job["coinb2"], job["merkle_branch"], - job["version"], - job["nbits"], + f"{job['version']:08x}", + job["bits"], job["ntime"], job["clean_jobs"] ]) diff --git a/MINE/rin/stratum_proxy.py b/MINE/rin/stratum_proxy.py index 294b316..1dd5906 100644 --- a/MINE/rin/stratum_proxy.py +++ b/MINE/rin/stratum_proxy.py @@ -13,7 +13,7 @@ import hashlib import struct from requests.auth import HTTPBasicAuth -class RinCoinStratumProxy: +class RinCoinStratumBase: 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', @@ -82,93 +82,92 @@ class RinCoinStratumProxy: def decode_bech32_address(self, address): """Decode RinCoin bech32 address to script""" try: - if not address.startswith('rin1'): + if not address or not address.startswith('rin1'): raise ValueError("Not a RinCoin bech32 address") - # Use generatetoaddress to get a proper script for this address - # This ensures we create valid outputs that can actually be spent result = self.rpc_call("validateaddress", [address]) - if result and result.get('isvalid') and 'scriptPubKey' in result: - script_hex = result['scriptPubKey'] - return bytes.fromhex(script_hex) - else: - # Fallback: create a basic P2WPKH script if RPC validation fails - # Extract the witness program (assuming it's standard 20-byte) - # This is a simplified approach - in production use a proper bech32 library - print(f"Warning: Using fallback script for {address}") - return bytes([0x00, 0x14]) + bytes(20) # OP_0 + 20 zero bytes - + 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}") - # Emergency fallback - this should never happen in production - return bytes([0x00, 0x14]) + bytes(20) + return None def build_coinbase_transaction(self, template, extranonce1, extranonce2): - """Build complete coinbase transaction""" + """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: - coinbase = b'' - - # Version - coinbase += struct.pack(' bytes: + outputs_blob = b'' + outputs_list = [] + # Main output + outputs_list.append(struct.pack(' {self.target_address}") + print(f"💰 Reward: {job['coinbasevalue']/100000000:.2f} RIN -> {address}") print(f"📊 Block height: {job['height']}") return True, "Block found and submitted" else: @@ -578,6 +581,15 @@ class RinCoinStratumProxy: finally: print("Server stopped") +class RinCoinStratumProxy(RinCoinStratumBase): + """Solo mining stratum proxy implementation""" + + 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'): + super().__init__(stratum_host, stratum_port, rpc_host, rpc_port, rpc_user, rpc_password, target_address) + if __name__ == "__main__": proxy = RinCoinStratumProxy() proxy.start() \ No newline at end of file