Skip to content

Instantly share code, notes, and snippets.

@Timonchegs
Created December 30, 2025 02:12
Show Gist options
  • Select an option

  • Save Timonchegs/52891cc693ab6fcd4f548453fb5ce887 to your computer and use it in GitHub Desktop.

Select an option

Save Timonchegs/52891cc693ab6fcd4f548453fb5ce887 to your computer and use it in GitHub Desktop.
gemini
const net = require('net');
const axios = require('axios');
// ================= НАСТРОЙКИ =================
const PROXY_PORT = 3333;
const NODE_RPC_URL = 'http://127.0.0.1:8668';
const NODE_RPC_USER = 'tteFTlJ7YOfGDA2KBMHKqnDnXeE=';
const NODE_RPC_PASS = 'SOkvF8sxay8ViOxpgbraHmqJmSU=';
// =============================================
const auth = Buffer.from(`${NODE_RPC_USER}:${NODE_RPC_PASS}`).toString('base64');
let currentJob = null;
let clients = [];
// Чистит хекс от 0x
const fixHex = (hex) => hex ? hex.replace(/^0x/, '').toLowerCase() : '';
// Таргет обязан быть 64 символа (32 байта). Добиваем нулями слева.
const pad64 = (hex) => fixHex(hex).padStart(64, '0');
async function fetchWork() {
try {
const response = await axios.post(NODE_RPC_URL, {
jsonrpc: '1.0', id: 'getwork', method: 'getwork', params: [""]
}, { headers: { 'Authorization': `Basic ${auth}` }, timeout: 2000 });
const r = response.data.result;
// Если ответ пустой или нет jobid - выходим
if (!r || !r.jobid) return;
const nodeJobId = String(r.jobid);
if (!currentJob || currentJob.nodeJobId !== nodeJobId) {
// --- ЗАЩИТА ОТ NULL И NAN (ГЛАВНЫЙ ФИКС) ---
// 1. Высота: Если пришел null/undefined, ставим безопасную заглушку (например 1), лишь бы не null
let safeHeight = (r.height !== undefined && r.height !== null) ? Number(r.height) : 1;
if (isNaN(safeHeight)) safeHeight = 1;
// 2. Время: Если пришло null/undefined, берем текущее время сервера
let safeTimeNum = (r.time !== undefined && r.time !== null) ? Number(r.time) : Math.floor(Date.now() / 1000);
if (isNaN(safeTimeNum)) safeTimeNum = Math.floor(Date.now() / 1000);
// Переводим время в HEX-строку (как требует TRM)
const timeHex = safeTimeNum.toString(16).toLowerCase();
// 3. Таргет: строго 64 символа
const target64 = pad64(r.targetboundary);
currentJob = {
id: Date.now().toString(), // ID задания для майнера
nodeJobId: nodeJobId, // ID задания в ноде
headerHash: fixHex(r.contenthash),
seedHash: fixHex(r.epochseed),
target: target64,
height: safeHeight, // Теперь это точно Число
time: timeHex // Теперь это точно HEX-строка (не "nan")
};
console.log(`[NEW JOB] ID:${currentJob.nodeJobId} H:${currentJob.height} T:${currentJob.time}`);
broadcastJob(currentJob);
}
} catch (e) {
// Ошибки соединения игнорим, чтобы не спамить, но можно включить для отладки
// console.error("RPC Poll Error:", e.message);
}
}
function broadcastJob(job) {
// Формируем пакет.
// Гарантируем, что null здесь не окажется благодаря проверкам выше.
const msg = JSON.stringify({
id: null,
method: "mining.notify",
params: [
job.id, // Job ID
job.headerHash, // Header
job.seedHash, // Seed
job.target, // Target (64 chars)
true, // Clean Jobs
job.height, // Height (Number)
job.time, // Time (Hex String)
"00000000", // ExtraNonce2
"00" // Reserved
]
}) + '\n';
clients.forEach(s => {
if (!s.destroyed && s.isReady) s.write(msg);
});
}
setInterval(fetchWork, 500); // Опрашиваем чаще (500мс), чтобы быстрее подхватить работу
const server = net.createServer((socket) => {
socket.isReady = false;
clients.push(socket);
let buffer = '';
console.log(`[CONN] Miner connected: ${socket.remoteAddress}`);
socket.on('data', async (chunk) => {
buffer += chunk.toString();
while (buffer.indexOf('\n') !== -1) {
const lineIndex = buffer.indexOf('\n');
const line = buffer.substring(0, lineIndex);
buffer = buffer.substring(lineIndex + 1);
if (!line.trim()) continue;
try {
const req = JSON.parse(line);
if (req.method === 'mining.hello') {
socket.write(JSON.stringify({id: req.id, result: { version: "1.0.0", algo: "abelian" }, error: null}) + '\n');
}
else if (req.method === 'mining.subscribe') {
socket.write(JSON.stringify({id: req.id, result: [[["mining.notify", "1"]], "000000", 4], error: null}) + '\n');
}
else if (req.method === 'mining.authorize') {
socket.write(JSON.stringify({ id: req.id, result: true, error: null }) + '\n');
socket.write(JSON.stringify({ method: "mining.set_difficulty", params: [1] }) + '\n');
socket.isReady = true;
// Если работа уже есть, шлем сразу
if (currentJob) {
const msg = JSON.stringify({
id: null,
method: "mining.notify",
params: [currentJob.id, currentJob.headerHash, currentJob.seedHash, currentJob.target, true, currentJob.height, currentJob.time, "00000000", "00"]
}) + '\n';
socket.write(msg);
}
}
else if (req.method === 'mining.submit') {
const [worker, jid, nonce] = req.params;
if (currentJob && currentJob.id === jid) {
// 1. Реверс Nonce (Little Endian <-> Big Endian)
const revNonce = nonce.match(/../g).reverse().join('');
// 2. MIXHASH FIX: Шлем 64 нуля, а не хедер
const mixHashZero = "0000000000000000000000000000000000000000000000000000000000000000";
console.log(`[SUBMIT] Nonce: ${nonce} -> Sending to Node...`);
axios.post(NODE_RPC_URL, {
jsonrpc: '1.0', id: 's', method: 'submitwork',
params: [
currentJob.nodeJobId,
revNonce,
mixHashZero
]
}, { headers: { 'Authorization': `Basic ${auth}` } })
.then(r => {
const success = r.data.result === true;
console.log(`[SHARE] Node Response: ${success ? 'ACCEPTED' : 'REJECTED'}`);
socket.write(JSON.stringify({ id: req.id, result: success, error: null }) + '\n');
})
.catch(e => {
console.log(`[SHARE ERR] ${e.message}`);
socket.write(JSON.stringify({ id: req.id, result: true, error: null }) + '\n');
});
} else {
// Работа устарела
socket.write(JSON.stringify({ id: req.id, result: true, error: null }) + '\n');
}
}
} catch (e) {
console.log(`JSON Error: ${e.message}`);
}
}
});
socket.on('close', () => { clients = clients.filter(c => c !== socket); });
socket.on('error', (e) => {});
});
server.listen(PROXY_PORT, '0.0.0.0', () => {
console.log(`V39 - FINAL FIX (NULL/NAN PROTECTION + SUBMIT FIX)`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment