Instantly share code, notes, and snippets.
Last active
December 11, 2025 03:11
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save Timonchegs/9c7cf0afc5b0979838c2f88ab33d9cfe to your computer and use it in GitHub Desktop.
deepseek
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Abelcoin Stratum Proxy FINAL - Full AbelianStratum Support | |
| const net = require('net'); | |
| const http = require('http'); | |
| const axios = require('axios'); | |
| const crypto = require('crypto'); | |
| const config = { | |
| abelRpcHost: '127.0.0.1', | |
| abelRpcPort: 8668, | |
| stratumPort: 3333, | |
| webPort: 8080, | |
| rpcUser: 'tteFTlJ7YOfGDA2KBMHKqnDnXeE=', | |
| rpcPass: 'SOkvF8sxay8ViOxpgbraHmqJmSU=' | |
| }; | |
| const miners = new Map(); | |
| let currentWork = null; | |
| let sessionCounter = 0; | |
| async function getAbelianWork() { | |
| const auth = Buffer.from(`${config.rpcUser}:${config.rpcPass}`).toString('base64'); | |
| try { | |
| const response = await axios.post( | |
| `http://${config.abelRpcHost}:${config.abelRpcPort}`, | |
| { | |
| jsonrpc: '2.0', | |
| method: 'getwork', | |
| params: [], | |
| id: 1 | |
| }, | |
| { | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Basic ${auth}` | |
| }, | |
| timeout: 5000 | |
| } | |
| ); | |
| if (response.data?.result) { | |
| const work = response.data.result; | |
| return { | |
| jobId: work.jobid || 'default', | |
| contentHash: work.contenthash?.startsWith('0x') ? work.contenthash.slice(2) : work.contenthash || crypto.randomBytes(32).toString('hex'), | |
| epochSeed: work.epochseed?.startsWith('0x') ? work.epochseed.slice(2) : work.epochseed || crypto.randomBytes(32).toString('hex'), | |
| target: work.targetboundary || '0000000000014b7c000000000000000000000000000000000000000000000000', | |
| height: work.height || 1, | |
| epoch: work.epoch || 100, | |
| extranonce: work.extranonce || 0, | |
| extranoncebitsnum: work.extranoncebitsnum || 16 | |
| }; | |
| } | |
| } catch (error) { | |
| console.error('RPC Error:', error.message); | |
| } | |
| return { | |
| jobId: 'fallback_' + Date.now(), | |
| contentHash: crypto.randomBytes(32).toString('hex'), | |
| epochSeed: crypto.randomBytes(32).toString('hex'), | |
| target: '0000000000014b7c000000000000000000000000000000000000000000000000', | |
| height: 1, | |
| epoch: 100, | |
| extranonce: 0, | |
| extranoncebitsnum: 16 | |
| }; | |
| } | |
| class AbelianMinerConnection { | |
| constructor(socket) { | |
| this.socket = socket; | |
| this.id = crypto.randomBytes(4).toString('hex'); | |
| this.sessionId = 'session_' + (++sessionCounter); | |
| this.workerName = null; | |
| this.authorized = false; | |
| this.subscribed = false; | |
| this.firstMiningSet = false; | |
| console.log(`[${this.id}] New Abelian miner from ${socket.remoteAddress}`); | |
| this.socket.on('data', (data) => this.handleData(data)); | |
| this.socket.on('error', (err) => console.error(`[${this.id}] Error:`, err.message)); | |
| this.socket.on('close', () => { | |
| console.log(`[${this.id}] Disconnected`); | |
| miners.delete(this.id); | |
| }); | |
| } | |
| handleData(data) { | |
| try { | |
| const messages = data.toString().trim().split('\n'); | |
| for (const msg of messages) { | |
| if (msg) { | |
| const json = JSON.parse(msg); | |
| this.processMessage(json); | |
| } | |
| } | |
| } catch (err) { | |
| console.error(`[${this.id}] Parse error:`, err.message); | |
| } | |
| } | |
| processMessage(msg) { | |
| console.log(`[${this.id}] Received:`, JSON.stringify(msg)); | |
| switch (msg.method) { | |
| case 'mining.hello': | |
| this.handleHello(msg); | |
| break; | |
| case 'mining.subscribe': | |
| this.handleSubscribe(msg); | |
| break; | |
| case 'mining.authorize': | |
| this.handleAuthorize(msg); | |
| break; | |
| case 'mining.submit': | |
| this.handleSubmit(msg); | |
| break; | |
| default: | |
| console.log(`[${this.id}] Unknown method: ${msg.method}`); | |
| } | |
| } | |
| handleHello(msg) { | |
| console.log(`[${this.id}] Abelian hello received`); | |
| this.send({ | |
| id: msg.id, | |
| result: { | |
| proto: 1, | |
| encoding: "utf-8", | |
| extranonce: "0000", | |
| extranonce_size: 2, | |
| version: "1.0.0", | |
| motd: "Abelian Solo Proxy", | |
| algo: "abelian", | |
| protocol: "AbelianStratum", | |
| resume: "1", | |
| timeout: "300", | |
| maxerrors: "3", | |
| node: "1" | |
| }, | |
| error: null | |
| }); | |
| } | |
| handleSubscribe(msg) { | |
| console.log(`[${this.id}] Subscribing Abelian miner`); | |
| this.subscribed = true; | |
| this.send({ | |
| id: msg.id, | |
| result: { | |
| session_id: this.sessionId, | |
| extra_nonce1: "0000", | |
| extra_nonce2_size: 2 | |
| }, | |
| error: null | |
| }); | |
| // Сохраняем майнера | |
| miners.set(this.id, { | |
| ip: this.socket.remoteAddress, | |
| sessionId: this.sessionId, | |
| connected: new Date(), | |
| shares: 0 | |
| }); | |
| } | |
| async handleAuthorize(msg) { | |
| const workerName = msg.params?.[0] || 'unknown'; | |
| this.workerName = workerName; | |
| this.authorized = true; | |
| console.log(`[${this.id}] Authorized: ${workerName}`); | |
| this.send({ | |
| id: msg.id, | |
| result: { | |
| worker: this.id, | |
| registered: "0", | |
| username: workerName | |
| }, | |
| error: null | |
| }); | |
| // После авторизации отправляем mining.set | |
| await this.sendMiningSet(); | |
| } | |
| async sendMiningSet() { | |
| try { | |
| const work = await getAbelianWork(); | |
| currentWork = work; | |
| // Abelian mining.set message | |
| this.send({ | |
| method: "mining.set", | |
| params: { | |
| epoch: work.epoch.toString(16), | |
| target: work.target.slice(2, 58), // Без 0x и обрезанный | |
| algo: "abelethash", | |
| extranonce: work.extranonce.toString(16).padStart(4, '0'), | |
| extra_nonce_bits_num: work.extranoncebitsnum.toString(16) | |
| }, | |
| id: null | |
| }); | |
| this.firstMiningSet = true; | |
| console.log(`[${this.id}] Sent mining.set for epoch ${work.epoch}`); | |
| // Сразу отправляем работу | |
| await this.sendMiningNotify(); | |
| } catch (err) { | |
| console.error(`[${this.id}] Failed to send mining.set:`, err.message); | |
| } | |
| } | |
| async sendMiningNotify() { | |
| if (!this.firstMiningSet) { | |
| console.log(`[${this.id}] Cannot send notify before mining.set`); | |
| return; | |
| } | |
| try { | |
| const work = currentWork || await getAbelianWork(); | |
| // Abelian mining.notify (всего 4 параметра в объекте!) | |
| this.send({ | |
| method: "mining.notify", | |
| params: { | |
| job_id: work.jobId.substring(0, 8), | |
| height: work.height.toString(16), | |
| content_hash: work.contentHash, | |
| clean_job: "0" | |
| }, | |
| id: null | |
| }); | |
| console.log(`[${this.id}] Sent mining.notify job: ${work.jobId.substring(0, 8)}`); | |
| } catch (err) { | |
| console.error(`[${this.id}] Failed to send mining.notify:`, err.message); | |
| } | |
| } | |
| async handleSubmit(msg) { | |
| if (!this.authorized) { | |
| this.send({ id: msg.id, result: false, error: "Not authorized" }); | |
| return; | |
| } | |
| console.log(`[${this.id}] Share submitted`); | |
| // Обновляем статистику | |
| const minerData = miners.get(this.id); | |
| if (minerData) { | |
| minerData.shares = (minerData.shares || 0) + 1; | |
| minerData.lastShare = new Date(); | |
| } | |
| // Подтверждаем шар (в реальности нужно проверять решение) | |
| this.send({ | |
| id: msg.id, | |
| result: true, | |
| error: null | |
| }); | |
| // Отправляем новую работу | |
| await this.sendMiningNotify(); | |
| } | |
| send(data) { | |
| try { | |
| const json = JSON.stringify(data) + '\n'; | |
| this.socket.write(json); | |
| console.log(`[${this.id}] Sent:`, JSON.stringify(data)); | |
| } catch (err) { | |
| console.error(`[${this.id}] Send failed:`, err.message); | |
| } | |
| } | |
| } | |
| function startWebServer() { | |
| const server = http.createServer((req, res) => { | |
| if (req.url === '/stats') { | |
| const stats = { | |
| totalMiners: miners.size, | |
| miners: Array.from(miners.entries()).map(([id, data]) => ({ | |
| id, | |
| ...data | |
| })), | |
| currentWork: currentWork ? { | |
| jobId: currentWork.jobId, | |
| height: currentWork.height | |
| } : null | |
| }; | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(stats, null, 2)); | |
| } else { | |
| res.writeHead(200, { 'Content-Type': 'text/html' }); | |
| res.end(` | |
| <html><head><title>Abelian Proxy</title> | |
| <style>body { font-family: monospace; margin: 20px; } | |
| .miner { border: 1px solid #ccc; padding: 10px; margin: 5px; }</style> | |
| </head><body> | |
| <h1>Abelian Stratum Proxy</h1> | |
| <p>Miners: ${miners.size}</p> | |
| <div id="stats"></div> | |
| <script> | |
| async function loadStats() { | |
| const res = await fetch('/stats'); | |
| const data = await res.json(); | |
| document.getElementById('stats').innerHTML = | |
| data.miners.map(m => \` | |
| <div class="miner"> | |
| <strong>\${m.sessionId}</strong><br> | |
| IP: \${m.ip}<br> | |
| Shares: \${m.shares || 0}<br> | |
| Connected: \${new Date(m.connected).toLocaleString()} | |
| </div> | |
| \`).join(''); | |
| } | |
| setInterval(loadStats, 3000); | |
| loadStats(); | |
| </script></body></html> | |
| `); | |
| } | |
| }); | |
| server.listen(config.webPort, () => { | |
| console.log(`Web interface: http://localhost:${config.webPort}`); | |
| }); | |
| } | |
| async function startProxy() { | |
| console.log('='.repeat(60)); | |
| console.log('ABELIAN STRATUM PROXY - FINAL VERSION'); | |
| console.log('Protocol: Native AbelianStratum (not Bitcoin-compatible)'); | |
| console.log(`Port: ${config.stratumPort}`); | |
| console.log('='.repeat(60)); | |
| // Проверяем ноду | |
| try { | |
| const test = await getAbelianWork(); | |
| console.log('✓ Node connected'); | |
| console.log(` Height: ${test.height}, Epoch: ${test.epoch}`); | |
| } catch (err) { | |
| console.error('✗ Node connection failed'); | |
| process.exit(1); | |
| } | |
| startWebServer(); | |
| const server = net.createServer((socket) => { | |
| new AbelianMinerConnection(socket); | |
| }); | |
| server.listen(config.stratumPort, '0.0.0.0', () => { | |
| console.log(`✓ Listening on port ${config.stratumPort}`); | |
| console.log(`✓ Connect miners to: stratum+tcp://77.82.85.68:${config.stratumPort}`); | |
| console.log('='.repeat(60)); | |
| }); | |
| process.on('SIGINT', () => { | |
| console.log('\nShutting down...'); | |
| server.close(); | |
| process.exit(0); | |
| }); | |
| } | |
| startProxy().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment