#!/usr/bin/env node /** * CORRECTED RIN Stratum Proxy - Based on Working Python Implementation * Fixes the share validation issue by following the exact Python logic */ const net = require('net'); const axios = require('axios'); const crypto = require('crypto'); // Configuration const CONFIG = { stratum: { host: '0.0.0.0', port: 3333 }, rpc: { host: '127.0.0.1', port: 9556, user: 'rinrpc', password: '745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90' }, mining: { targetAddress: 'rin1qahvvv9d5f3443wtckeqavwp9950wacxfmwv20q' } }; class CorrectedRinProxy { constructor() { this.server = null; this.currentJob = null; this.jobCounter = 0; this.clients = new Map(); this.extranonceCounter = 0; this.currentDifficulty = 0.001; // Start lower for testing console.log('šŸ”§ CORRECTED RIN Stratum Proxy - Following Python Logic'); console.log(`šŸ“” Stratum: ${CONFIG.stratum.host}:${CONFIG.stratum.port}`); console.log(`šŸ”— RPC: ${CONFIG.rpc.host}:${CONFIG.rpc.port}`); } async rpcCall(method, params = []) { try { const url = `http://${CONFIG.rpc.host}:${CONFIG.rpc.port}/`; const auth = Buffer.from(`${CONFIG.rpc.user}:${CONFIG.rpc.password}`).toString('base64'); const response = await axios.post(url, { jsonrpc: '1.0', id: 'proxy', method: method, params: params }, { headers: { 'Content-Type': 'text/plain', 'Authorization': `Basic ${auth}` }, timeout: 30000 }); return response.data.error ? null : response.data.result; } catch (error) { console.error(`RPC Error: ${error.message}`); return null; } } /** * CORRECTED Share Validation - Following Python Implementation Exactly */ async validateShareCorrected(jobId, extranonce1, extranonce2, ntime, nonce) { try { if (!this.currentJob || this.currentJob.jobId !== jobId) { return { isValid: false, difficulty: 0, message: 'Invalid job ID' }; } // Get job data const job = this.currentJob; const address = CONFIG.mining.targetAddress; console.log(`šŸ” [CORRECTED] Validating share for job ${jobId}`); console.log(`šŸ” [CORRECTED] ntime=${ntime}, nonce=${nonce}, extranonce=${extranonce1}:${extranonce2}`); // Simple coinbase construction (following Python simplified approach) const heightBytes = Buffer.allocUnsafe(4); heightBytes.writeUInt32LE(job.height, 0); const scriptsig = Buffer.concat([ Buffer.from([heightBytes.length]), heightBytes, Buffer.from('/RinCoin/'), Buffer.from(extranonce1, 'hex'), Buffer.from(extranonce2, 'hex') ]); // Simple coinbase transaction (minimal for testing) const version = Buffer.allocUnsafe(4); version.writeUInt32LE(1, 0); const coinbase = Buffer.concat([ version, // Version Buffer.from([0x01]), // Input count Buffer.alloc(32), // Previous output hash (null) Buffer.from([0xff, 0xff, 0xff, 0xff]), // Previous output index Buffer.from([scriptsig.length]), // Script length scriptsig, // Script Buffer.from([0xff, 0xff, 0xff, 0xff]), // Sequence Buffer.from([0x01]), // Output count Buffer.alloc(8), // Value (simplified) Buffer.from([0x00]), // Script length (empty) Buffer.alloc(4) // Locktime ]); // Calculate coinbase txid const hash1 = crypto.createHash('sha256').update(coinbase).digest(); const hash2 = crypto.createHash('sha256').update(hash1).digest(); const coinbaseTxid = hash2.reverse(); // Reverse for little-endian // Simple merkle root (just coinbase for now) const merkleRoot = coinbaseTxid; // Build block header - EXACTLY like Python const header = Buffer.concat([ // Version (little-endian) (() => { const buf = Buffer.allocUnsafe(4); buf.writeUInt32LE(job.version, 0); return buf; })(), // Previous block hash (big-endian in block) Buffer.from(job.prevhash, 'hex').reverse(), // Merkle root (already correct endian) merkleRoot, // Timestamp (little-endian) - CRITICAL: Use ntime from miner (() => { const buf = Buffer.allocUnsafe(4); buf.writeUInt32LE(parseInt(ntime, 16), 0); return buf; })(), // Bits (big-endian in block) Buffer.from(job.bits, 'hex').reverse(), // Nonce (little-endian) (() => { const buf = Buffer.allocUnsafe(4); buf.writeUInt32LE(parseInt(nonce, 16), 0); return buf; })() ]); console.log(`šŸ” [CORRECTED] Header length: ${header.length} bytes`); console.log(`šŸ” [CORRECTED] Version: ${job.version}`); console.log(`šŸ” [CORRECTED] Prevhash: ${job.prevhash}`); console.log(`šŸ” [CORRECTED] Bits: ${job.bits}`); console.log(`šŸ” [CORRECTED] ntime (from miner): ${ntime} = ${parseInt(ntime, 16)}`); console.log(`šŸ” [CORRECTED] nonce (from miner): ${nonce} = ${parseInt(nonce, 16)}`); // Calculate block hash - EXACTLY like Python const blockHash1 = crypto.createHash('sha256').update(header).digest(); const blockHash2 = crypto.createHash('sha256').update(blockHash1).digest(); const blockHashHex = blockHash2.reverse().toString('hex'); // Reverse for display console.log(`šŸ” [CORRECTED] Block hash: ${blockHashHex}`); // Calculate difficulty using target from job const hashInt = BigInt('0x' + blockHashHex); const targetInt = BigInt('0x' + job.target); console.log(`šŸ” [CORRECTED] Hash as BigInt: ${hashInt.toString(16)}`); console.log(`šŸ” [CORRECTED] Target: ${job.target}`); console.log(`šŸ” [CORRECTED] Target as BigInt: ${targetInt.toString(16)}`); // Calculate share difficulty const diff1Target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000n; const shareDifficulty = hashInt === 0n ? Number.POSITIVE_INFINITY : Number(diff1Target / hashInt); console.log(`šŸ” [CORRECTED] Share difficulty: ${shareDifficulty}`); // Validate share const isValidShare = hashInt <= targetInt; console.log(`šŸ” [CORRECTED] Hash <= Target? ${isValidShare} (${hashInt <= targetInt})`); return { isValid: isValidShare, difficulty: shareDifficulty, blockHash: blockHashHex, message: 'Corrected validation complete' }; } catch (error) { console.error(`šŸ” [CORRECTED] Validation error: ${error.message}`); return { isValid: false, difficulty: 0, message: `Error: ${error.message}` }; } } async getBlockTemplate() { try { const template = await this.rpcCall('getblocktemplate', [{ rules: ['mweb', 'segwit'] }]); if (!template) return null; this.jobCounter++; // Convert bits to target (Bitcoin-style) const bits = parseInt(template.bits, 16); const exponent = bits >> 24; const mantissa = bits & 0xffffff; let target; if (exponent <= 3) { target = BigInt(mantissa) >> BigInt(8 * (3 - exponent)); } else { target = BigInt(mantissa) << BigInt(8 * (exponent - 3)); } const targetHex = target.toString(16).padStart(64, '0'); this.currentJob = { jobId: `job_${this.jobCounter.toString(16).padStart(8, '0')}`, template: template, prevhash: template.previousblockhash || '0'.repeat(64), version: template.version || 1, bits: template.bits || '1d00ffff', target: targetHex, ntime: Math.floor(Date.now() / 1000).toString(16).padStart(8, '0'), height: template.height || 0, transactions: template.transactions || [] }; console.log(`šŸ†• NEW JOB: ${this.currentJob.jobId} | Height: ${this.currentJob.height}`); console.log(` šŸŽÆ Target: ${targetHex.substring(0, 16)}...`); return this.currentJob; } catch (error) { console.error(`Get block template error: ${error.message}`); return null; } } sendResponse(client, id, result, error = null) { try { const response = { id: id, result: result, error: error }; client.write(JSON.stringify(response) + '\n'); } catch (error) { console.error(`Send response error: ${error.message}`); } } sendNotification(client, method, params) { try { const notification = { id: null, method: method, params: params }; client.write(JSON.stringify(notification) + '\n'); } catch (error) { console.error(`Send notification error: ${error.message}`); } } async handleMessage(client, addr, message) { try { const data = JSON.parse(message.trim()); const method = data.method; const id = data.id; const params = data.params || []; console.log(`šŸ“Ø [${addr}] ${method}: ${JSON.stringify(params)}`); if (method === 'mining.subscribe') { this.extranonceCounter++; const extranonce1 = this.extranonceCounter.toString(16).padStart(8, '0'); this.clients.set(client, { addr: addr, extranonce1: extranonce1, username: null }); this.sendResponse(client, id, [ [['mining.set_difficulty', 'subscription_id'], ['mining.notify', 'subscription_id']], extranonce1, 4 ]); console.log(`šŸ“ [${addr}] Subscription: extranonce1=${extranonce1}`); this.sendNotification(client, 'mining.set_difficulty', [this.currentDifficulty]); if (this.currentJob) { this.sendJobToClient(client); } } else if (method === 'mining.authorize') { const username = params[0] || 'anonymous'; this.sendResponse(client, id, true); console.log(`šŸ” [${addr}] Authorized as ${username}`); if (this.currentJob) { this.sendJobToClient(client); } } else if (method === 'mining.submit') { if (params.length >= 5) { const [username, jobId, extranonce2, ntime, nonce] = params; const clientInfo = this.clients.get(client); const extranonce1 = clientInfo ? clientInfo.extranonce1 : '00000000'; console.log(`šŸ“Š [${addr}] Submit: ${username} | job=${jobId} | nonce=${nonce}`); // CORRECTED VALIDATION const validation = await this.validateShareCorrected(jobId, extranonce1, extranonce2, ntime, nonce); const timestamp = new Date().toLocaleTimeString(); console.log(`[${timestamp}] šŸŽÆ SHARE: job=${jobId} | nonce=${nonce}`); console.log(` šŸ“ˆ Share Diff: ${validation.difficulty.toExponential(2)}`); console.log(` šŸ“ˆ Result: ${validation.isValid ? 'ACCEPTED' : 'REJECTED'} | ${validation.message}`); if (validation.isValid) { console.log(`āœ… [${addr}] Share ACCEPTED!`); } else { console.log(`āŒ [${addr}] Share REJECTED: ${validation.message}`); } this.sendResponse(client, id, validation.isValid); } else { this.sendResponse(client, id, false, 'Invalid parameters'); } } else { console.log(`ā“ [${addr}] Unknown method: ${method}`); this.sendResponse(client, id, null, 'Unknown method'); } } catch (error) { console.error(`[${addr}] Message error: ${error.message}`); this.sendResponse(client, null, null, 'Invalid JSON'); } } sendJobToClient(client) { if (!this.currentJob) return; try { this.sendNotification(client, 'mining.notify', [ this.currentJob.jobId, this.currentJob.prevhash, '', '', [], this.currentJob.version.toString(16).padStart(8, '0'), this.currentJob.bits, this.currentJob.ntime, true ]); } catch (error) { console.error(`Failed to send job: ${error.message}`); } } handleClient(client, addr) { console.log(`šŸ”Œ [${addr}] Connected`); client.on('data', (data) => { const messages = data.toString().trim().split('\n'); for (const message of messages) { if (message) { this.handleMessage(client, addr, message); } } }); client.on('close', () => { console.log(`šŸ”Œ [${addr}] Disconnected`); this.clients.delete(client); }); client.on('error', (error) => { console.error(`āŒ [${addr}] Error: ${error.message}`); this.clients.delete(client); }); } async start() { try { const blockchainInfo = await this.rpcCall('getblockchaininfo'); if (!blockchainInfo) { console.error('āŒ Failed to connect to RIN node!'); return; } console.log('āœ… Connected to RIN node'); console.log(`šŸ“Š Current height: ${blockchainInfo.blocks || 'unknown'}`); if (!(await this.getBlockTemplate())) { console.error('āŒ Failed to get initial block template!'); return; } // Job updater setInterval(async () => { try { const oldHeight = this.currentJob ? this.currentJob.height : 0; if (await this.getBlockTemplate()) { const newHeight = this.currentJob.height; if (newHeight > oldHeight) { console.log('šŸ”„ Broadcasting new job...'); for (const [client, clientInfo] of this.clients) { this.sendJobToClient(client); } } } } catch (error) { console.error(`Job updater error: ${error.message}`); } }, 30000); this.server = net.createServer((client) => { const addr = `${client.remoteAddress}:${client.remotePort}`; this.handleClient(client, addr); }); this.server.listen(CONFIG.stratum.port, CONFIG.stratum.host, () => { console.log(`šŸš€ CORRECTED RIN Proxy ready!`); console.log(` šŸ“” Listening on ${CONFIG.stratum.host}:${CONFIG.stratum.port}`); console.log(` šŸ“Š Current job: ${this.currentJob ? this.currentJob.jobId : 'None'}`); console.log(''); console.log(' šŸ”§ Test with:'); console.log(` ./cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u user -p x -t 8`); console.log(''); console.log(' šŸ”§ CORRECTED: Following Python implementation exactly!'); console.log(''); }); } catch (error) { console.error(`āŒ Failed to start: ${error.message}`); } } } // Handle shutdown process.on('SIGINT', () => { console.log('\nšŸ›‘ Shutting down...'); process.exit(0); }); // Start corrected proxy const proxy = new CorrectedRinProxy(); proxy.start().catch(error => { console.error(`āŒ Failed to start proxy: ${error.message}`); process.exit(1); });