#!/usr/bin/env node /** * PRODUCTION RIN Stratum Proxy with FULL BLOCK VALIDATION * Complete validation against RIN blockchain via RPC */ 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 ProductionRinProxy { constructor() { this.server = null; this.currentJob = null; this.jobCounter = 0; this.clients = new Map(); this.extranonceCounter = 0; this.currentDifficulty = 1e-8; // Start very low to match actual mining difficulty this.shareCount = 0; this.acceptedShares = 0; console.log('šŸ­ PRODUCTION RIN Stratum Proxy - Full Validation'); 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; } } /** * Encode integer as Bitcoin-style varint */ encodeVarint(n) { if (n < 0xfd) { return Buffer.from([n]); } else if (n <= 0xffff) { const buf = Buffer.allocUnsafe(3); buf.writeUInt8(0xfd, 0); buf.writeUInt16LE(n, 1); return buf; } else if (n <= 0xffffffff) { const buf = Buffer.allocUnsafe(5); buf.writeUInt8(0xfe, 0); buf.writeUInt32LE(n, 1); return buf; } else { const buf = Buffer.allocUnsafe(9); buf.writeUInt8(0xff, 0); buf.writeBigUInt64LE(BigInt(n), 1); return buf; } } /** * Decode RinCoin bech32 address to script */ async decodeBech32Address(address) { try { if (!address || !address.startsWith('rin1')) { throw new Error('Not a RinCoin bech32 address'); } const result = await this.rpcCall('validateaddress', [address]); if (!result || !result.isvalid) { throw new Error('Address not valid per node'); } const scriptHex = result.scriptPubKey; if (!scriptHex) { throw new Error('Node did not return scriptPubKey'); } return Buffer.from(scriptHex, 'hex'); } catch (error) { console.error(`Address decode error: ${error.message}`); return null; } } /** * Build coinbase transaction (with and without witness) - PRODUCTION VERSION */ async buildCoinbaseTransaction(template, extranonce1, extranonce2, targetAddress) { try { const hasWitnessCommitment = template.default_witness_commitment !== undefined; const value = template.coinbasevalue || 0; const scriptPubkey = await this.decodeBech32Address(targetAddress); if (!scriptPubkey) { return { wit: null, nowit: null }; } const witnessCommitment = template.default_witness_commitment; // ScriptSig (block height + tag + extranonces) const height = template.height || 0; const heightBytes = Buffer.allocUnsafe(4); heightBytes.writeUInt32LE(height, 0); const heightCompact = Buffer.concat([ Buffer.from([heightBytes.length]), heightBytes ]); const scriptsig = Buffer.concat([ heightCompact, Buffer.from('/RinCoin/'), Buffer.from(extranonce1), Buffer.from(extranonce2) ]); // Build outputs const buildOutputsBlob = () => { const outputsList = []; // Main output const valueBuffer = Buffer.allocUnsafe(8); valueBuffer.writeBigUInt64LE(BigInt(value), 0); outputsList.push(Buffer.concat([ valueBuffer, this.encodeVarint(scriptPubkey.length), scriptPubkey ])); // Witness commitment if present if (witnessCommitment) { const commitScript = Buffer.from(witnessCommitment, 'hex'); const zeroValue = Buffer.allocUnsafe(8); zeroValue.writeBigUInt64LE(0n, 0); outputsList.push(Buffer.concat([ zeroValue, this.encodeVarint(commitScript.length), commitScript ])); } return Buffer.concat([ this.encodeVarint(outputsList.length), ...outputsList ]); }; // Build coinbase transactions const versionBuffer = Buffer.allocUnsafe(4); versionBuffer.writeUInt32LE(1, 0); const prevoutHash = Buffer.alloc(32); const prevoutIndex = Buffer.from([0xff, 0xff, 0xff, 0xff]); const sequence = Buffer.from([0xff, 0xff, 0xff, 0xff]); const locktime = Buffer.allocUnsafe(4); locktime.writeUInt32LE(0, 0); // Non-witness version (for txid) const cbNowit = Buffer.concat([ versionBuffer, Buffer.from([0x01]), prevoutHash, prevoutIndex, this.encodeVarint(scriptsig.length), scriptsig, sequence, buildOutputsBlob(), locktime ]); // Witness version (for block) let cbWit = cbNowit; if (hasWitnessCommitment) { cbWit = Buffer.concat([ versionBuffer, Buffer.from([0x00, 0x01]), Buffer.from([0x01]), prevoutHash, prevoutIndex, this.encodeVarint(scriptsig.length), scriptsig, sequence, buildOutputsBlob(), Buffer.from([0x01, 0x20]), Buffer.alloc(32), locktime ]); } return { wit: cbWit, nowit: cbNowit }; } catch (error) { console.error(`Coinbase construction error: ${error.message}`); return { wit: null, nowit: null }; } } /** * Calculate merkle root - PRODUCTION VERSION */ calculateMerkleRoot(coinbaseTxid, transactions) { try { const hashes = [coinbaseTxid]; for (const tx of transactions) { hashes.push(Buffer.from(tx.hash, 'hex').reverse()); } while (hashes.length > 1) { if (hashes.length % 2 === 1) { hashes.push(hashes[hashes.length - 1]); } const nextLevel = []; for (let i = 0; i < hashes.length; i += 2) { const combined = Buffer.concat([hashes[i], hashes[i + 1]]); const hash1 = crypto.createHash('sha256').update(combined).digest(); const hash2 = crypto.createHash('sha256').update(hash1).digest(); nextLevel.push(hash2); } hashes.splice(0, hashes.length, ...nextLevel); } return hashes[0] || Buffer.alloc(32); } catch (error) { console.error(`Merkle root calculation error: ${error.message}`); return Buffer.alloc(32); } } /** * Convert bits to target - PRODUCTION VERSION */ bitsToTarget(bitsHex) { try { const bits = parseInt(bitsHex, 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)); } if (target > (1n << 256n) - 1n) { target = (1n << 256n) - 1n; } return target.toString(16).padStart(64, '0'); } catch (error) { return '0000ffff00000000000000000000000000000000000000000000000000000000'; } } /** * Calculate share difficulty from hash - PRODUCTION VERSION */ calculateShareDifficulty(hashHex) { try { console.log(`šŸ” [DIFF] Input hash: ${hashHex}`); const hashInt = BigInt('0x' + hashHex); console.log(`šŸ” [DIFF] Hash as BigInt: ${hashInt.toString(16)}`); if (hashInt === 0n) { console.log(`šŸ” [DIFF] Hash is zero, returning infinity`); return Number.POSITIVE_INFINITY; } // Bitcoin diff1 target (compact form: 0x1d00ffff expanded) const diff1Target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000n; console.log(`šŸ” [DIFF] Diff1Target: ${diff1Target.toString(16)}`); // Calculate difficulty = diff1Target / hashInt // Use string conversion to avoid precision loss const diff1Str = diff1Target.toString(); const hashStr = hashInt.toString(); console.log(`šŸ” [DIFF] Diff1 as string: ${diff1Str}`); console.log(`šŸ” [DIFF] Hash as string: ${hashStr}`); // For large numbers, calculate difficulty using scientific notation // difficulty = diff1Target / hash = (10^log10(diff1Target)) / (10^log10(hash)) // = 10^(log10(diff1Target) - log10(hash)) const logDiff1 = Math.log10(parseFloat(diff1Str)); const logHash = Math.log10(parseFloat(hashStr)); const logDifficulty = logDiff1 - logHash; console.log(`šŸ” [DIFF] log10(diff1): ${logDiff1}`); console.log(`šŸ” [DIFF] log10(hash): ${logHash}`); console.log(`šŸ” [DIFF] log10(difficulty): ${logDifficulty}`); const difficulty = Math.pow(10, logDifficulty); console.log(`šŸ” [DIFF] Calculated difficulty: ${difficulty}`); // Sanity check - if difficulty is too small, something went wrong if (difficulty < 1e-20 || !isFinite(difficulty)) { console.log(`šŸ” [DIFF] WARNING: Difficulty calculation looks wrong, using fallback`); // Fallback: calculate using BigInt division with scaling const scaledHash = hashInt; const scaledDiff1 = diff1Target; const quotient = scaledDiff1 / scaledHash; const remainder = scaledDiff1 % scaledHash; // Convert to number with scaling const integerPart = Number(quotient); const fractionalPart = Number(remainder) / Number(scaledHash); const fallbackDifficulty = integerPart + fractionalPart; console.log(`šŸ” [DIFF] Fallback difficulty: ${fallbackDifficulty}`); return fallbackDifficulty; } return difficulty; } catch (error) { console.log(`šŸ” [DIFF] ERROR: ${error.message}`); return 0.0; } } /** * Calculate network difficulty from target - PRODUCTION VERSION */ calculateNetworkDifficulty(targetHex) { try { const targetInt = BigInt('0x' + targetHex); const diff1Target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000n; return Number(diff1Target / targetInt); } catch (error) { return 1.0; } } /** * Convert difficulty to target hex */ difficultyToTarget(difficulty) { try { const diff1Target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000n; const target = diff1Target / BigInt(Math.floor(difficulty * 1000000)) * 1000000n; return target.toString(16).padStart(64, '0'); } catch (error) { return '0000ffff00000000000000000000000000000000000000000000000000000000'; } } /** * COMPLETE PRODUCTION VALIDATION - Full block validation against RIN blockchain */ async validateShareProduction(jobId, extranonce1, extranonce2, ntime, nonce, targetAddress = null) { try { if (!this.currentJob || this.currentJob.jobId !== jobId) { return { isValidShare: false, isValidBlock: false, shareDifficulty: 0, networkDifficulty: 1.0, message: 'Invalid job ID' }; } const address = targetAddress || CONFIG.mining.targetAddress; console.log(`šŸ” [VALIDATION] Building coinbase transaction...`); const coinbase = await this.buildCoinbaseTransaction(this.currentJob.template, extranonce1, extranonce2, address); if (!coinbase.wit || !coinbase.nowit) { return { isValidShare: false, isValidBlock: false, shareDifficulty: 0, networkDifficulty: 1.0, message: 'Coinbase construction failed' }; } console.log(`šŸ” [VALIDATION] Calculating coinbase txid...`); const hash1 = crypto.createHash('sha256').update(coinbase.nowit).digest(); const hash2 = crypto.createHash('sha256').update(hash1).digest(); const coinbaseTxid = hash2.reverse(); console.log(`šŸ” [VALIDATION] Calculating merkle root...`); const merkleRoot = this.calculateMerkleRoot(coinbaseTxid, this.currentJob.transactions); console.log(`šŸ” [VALIDATION] Building block header...`); const versionBuffer = Buffer.allocUnsafe(4); versionBuffer.writeUInt32LE(this.currentJob.version, 0); const prevhashBuffer = Buffer.from(this.currentJob.prevhash, 'hex').reverse(); const ntimeBuffer = Buffer.allocUnsafe(4); ntimeBuffer.writeUInt32LE(parseInt(ntime, 16), 0); const bitsBuffer = Buffer.from(this.currentJob.bits, 'hex').reverse(); const nonceBuffer = Buffer.allocUnsafe(4); nonceBuffer.writeUInt32LE(parseInt(nonce, 16), 0); const header = Buffer.concat([ versionBuffer, prevhashBuffer, merkleRoot, ntimeBuffer, bitsBuffer, nonceBuffer ]); console.log(`šŸ” [VALIDATION] Calculating block hash...`); const blockHash1 = crypto.createHash('sha256').update(header).digest(); const blockHash2 = crypto.createHash('sha256').update(blockHash1).digest(); const blockHashHex = blockHash2.reverse().toString('hex'); console.log(`šŸ” [VALIDATION] Block hash: ${blockHashHex}`); console.log(`šŸ” [VALIDATION] Header size: ${header.length} bytes`); console.log(`šŸ” [VALIDATION] Calculating difficulties...`); const target = this.bitsToTarget(this.currentJob.bits); console.log(`šŸ” [VALIDATION] Target: ${target}`); console.log(`šŸ” [VALIDATION] Bits: ${this.currentJob.bits}`); const shareDifficulty = this.calculateShareDifficulty(blockHashHex); const networkDifficulty = this.calculateNetworkDifficulty(target); console.log(`šŸ” [VALIDATION] Raw share difficulty: ${shareDifficulty}`); console.log(`šŸ” [VALIDATION] Network difficulty: ${networkDifficulty}`); const hashInt = BigInt('0x' + blockHashHex); const targetInt = BigInt('0x' + target); const poolTargetInt = BigInt('0x' + this.difficultyToTarget(this.currentDifficulty)); const isValidShare = hashInt <= poolTargetInt; const isValidBlock = hashInt <= targetInt; console.log(`šŸ” [VALIDATION] Complete! Share: ${isValidShare} | Block: ${isValidBlock}`); return { isValidShare, isValidBlock, shareDifficulty, networkDifficulty, blockHashHex, header, coinbase, target, message: 'Production validation complete' }; } catch (error) { console.error(`Production validation error: ${error.message}`); return { isValidShare: false, isValidBlock: false, shareDifficulty: 0, networkDifficulty: 1.0, message: `Validation error: ${error.message}` }; } } /** * Submit complete block to RIN network - PRODUCTION VERSION */ async submitBlockProduction(validation) { try { console.log(`šŸŽ‰ BLOCK FOUND! Submitting to RIN network...`); console.log(` Hash: ${validation.blockHashHex}`); console.log(` Height: ${this.currentJob.height}`); // Build complete block const txCount = 1 + this.currentJob.transactions.length; const block = Buffer.concat([ validation.header, this.encodeVarint(txCount), validation.coinbase.wit ]); // Add other transactions const txBuffers = []; for (const tx of this.currentJob.transactions) { txBuffers.push(Buffer.from(tx.data, 'hex')); } const fullBlock = Buffer.concat([block, ...txBuffers]); const blockHex = fullBlock.toString('hex'); console.log(` šŸ“¦ Submitting block of size ${fullBlock.length} bytes...`); // Submit to RIN network via RPC const result = await this.rpcCall('submitblock', [blockHex]); if (result === null) { console.log(` āœ… Block accepted by RIN network!`); return true; } else { console.log(` āŒ Block rejected: ${result}`); return false; } } catch (error) { console.error(`āŒ Block submission error: ${error.message}`); return false; } } async getBlockTemplate() { try { const template = await this.rpcCall('getblocktemplate', [{ rules: ['mweb', 'segwit'] }]); if (!template) return null; this.jobCounter++; 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', ntime: Math.floor(Date.now() / 1000).toString(16).padStart(8, '0'), height: template.height || 0, coinbasevalue: template.coinbasevalue || 0, transactions: template.transactions || [] }; console.log(`šŸ†• NEW JOB: ${this.currentJob.jobId} | Height: ${this.currentJob.height} | Reward: ${(this.currentJob.coinbasevalue / 100000000).toFixed(2)} RIN`); return this.currentJob; } catch (error) { console.error(`Get block template error: ${error.message}`); return null; } } adjustDifficulty() { if (this.shareCount < 10) return; const acceptanceRate = this.acceptedShares / this.shareCount; if (acceptanceRate > 0.8) { this.currentDifficulty *= 2.0; // Increase more aggressively for low difficulties console.log(`šŸ“ˆ Difficulty increased to ${this.currentDifficulty.toExponential(2)}`); } else if (acceptanceRate < 0.2) { this.currentDifficulty *= 0.5; // Decrease more aggressively console.log(`šŸ“‰ Difficulty decreased to ${this.currentDifficulty.toExponential(2)}`); } this.shareCount = 0; this.acceptedShares = 0; } 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'; const clientInfo = this.clients.get(client); if (clientInfo) { clientInfo.username = username; } 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}`); // FULL PRODUCTION VALIDATION against RIN blockchain const validation = await this.validateShareProduction(jobId, extranonce1, extranonce2, ntime, nonce); console.log(` šŸŽÆ Share Diff: ${validation.shareDifficulty.toExponential(2)} | Network Diff: ${validation.networkDifficulty.toFixed(6)}`); console.log(` šŸ“ˆ Result: ${validation.isValidShare ? 'ACCEPTED' : 'REJECTED'} | ${validation.message}`); if (validation.isValidBlock) { console.log(`šŸŽ‰ [${addr}] BLOCK FOUND! Hash: ${validation.blockHashHex}`); console.log(` šŸ’° Reward: ${(this.currentJob.coinbasevalue / 100000000).toFixed(2)} RIN`); const blockSubmitted = await this.submitBlockProduction(validation); if (blockSubmitted) { console.log(`āœ… [${addr}] Block accepted by RIN network!`); } } this.sendResponse(client, id, validation.isValidShare); this.shareCount++; if (validation.isValidShare) { this.acceptedShares++; } if (this.shareCount % 20 === 0) { this.adjustDifficulty(); for (const [clientSocket, clientInfo] of this.clients) { this.sendNotification(clientSocket, 'mining.set_difficulty', [this.currentDifficulty]); } } } 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; } 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(`šŸš€ PRODUCTION 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(' šŸ”§ Connect your mining rig:'); console.log(` ./cpuminer -a rinhash -o stratum+tcp://127.0.0.1:3333 -u worker1 -p x -t 20`); console.log(''); console.log(' šŸ­ FULL PRODUCTION VALIDATION: Every share validated against RIN blockchain!'); 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 production proxy const proxy = new ProductionRinProxy(); proxy.start().catch(error => { console.error(`āŒ Failed to start proxy: ${error.message}`); process.exit(1); });