#!/usr/bin/env node /** * DEBUG RIN Stratum Proxy - Send ALL shares to RIN node via RPC * No validation - just forward everything for debugging */ const net = require('net'); const axios = require('axios'); // Configuration const CONFIG = { stratum: { host: '0.0.0.0', port: 3333 }, rpc: { host: '127.0.0.1', port: 9556, user: 'rinrpc', password: '745ce784d5d537fc06105a1b935b7657903cfc71a5fb3b90' } }; class DebugRinProxy { constructor() { this.server = null; this.currentJob = null; this.jobCounter = 0; this.clients = new Map(); this.extranonceCounter = 0; this.currentDifficulty = 0.001; this.shareCount = 0; this.acceptedShares = 0; console.log('šŸ› DEBUG RIN Stratum Proxy - Send ALL shares to RIN node'); 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; } } /** * Send share directly to RIN node via submitblock RPC */ async submitShareToRinNode(jobId, extranonce1, extranonce2, ntime, nonce) { try { console.log(`šŸš€ [DEBUG] Submitting share to RIN node via RPC...`); console.log(` šŸ“Š Job: ${jobId} | Nonce: ${nonce} | Time: ${ntime}`); console.log(` šŸ”‘ Extranonce: ${extranonce1}:${extranonce2}`); // Build a minimal block for submission // We'll create a simple block structure that the node can process const blockData = { jobId: jobId, extranonce1: extranonce1, extranonce2: extranonce2, ntime: ntime, nonce: nonce, timestamp: Date.now() }; // Convert to hex string for submitblock const blockHex = Buffer.from(JSON.stringify(blockData)).toString('hex'); console.log(` šŸ“¦ Block data: ${blockHex.substring(0, 64)}...`); // Submit to RIN node const result = await this.rpcCall('submitblock', [blockHex]); if (result === null) { console.log(` āœ… Share accepted by RIN node!`); return true; } else { console.log(` āŒ Share rejected by RIN node: ${result}`); return false; } } catch (error) { console.error(`āŒ RPC 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, transactions: template.transactions || [] }; console.log(`šŸ†• NEW JOB: ${this.currentJob.jobId} | Height: ${this.currentJob.height} | Reward: ${(this.currentJob.template.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 *= 1.2; console.log(`šŸ“ˆ Difficulty increased to ${this.currentDifficulty.toFixed(6)}`); } else if (acceptanceRate < 0.2) { this.currentDifficulty *= 0.8; console.log(`šŸ“‰ Difficulty decreased to ${this.currentDifficulty.toFixed(6)}`); } 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}`); // DEBUG: Send EVERY share to RIN node via RPC console.log(`šŸ› [DEBUG] Forwarding share to RIN node...`); const submitted = await this.submitShareToRinNode(jobId, extranonce1, extranonce2, ntime, nonce); // Always accept shares for debugging (let RIN node decide) this.sendResponse(client, id, true); this.shareCount++; if (submitted) { this.acceptedShares++; console.log(`āœ… [${addr}] Share forwarded successfully!`); } else { console.log(`āš ļø [${addr}] Share forwarded but rejected by RIN node`); } // Adjust difficulty periodically 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; } // 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(`šŸš€ DEBUG 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 user -p x -t 32`); console.log(''); console.log(' šŸ› DEBUG MODE: ALL shares sent to RIN node via RPC!'); console.log(' šŸ“ˆ No local validation - RIN node decides everything'); 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 debug proxy const proxy = new DebugRinProxy(); proxy.start().catch(error => { console.error(`āŒ Failed to start proxy: ${error.message}`); process.exit(1); });