diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..10d83bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM ubuntu:16.04 +LABEL maintainer="Pedro Lobo " +LABEL Name="Dockerized xmr-node-proxy" +LABEL Version="1.4" + +RUN export BUILD_DEPS="cmake \ + pkg-config \ + git \ + build-essential \ + curl" \ + + && apt-get update && apt-get upgrade -qqy \ + && apt-get install --no-install-recommends -qqy \ + ${BUILD_DEPS} python-virtualenv \ + python3-virtualenv ntp screen \ + libboost-all-dev libevent-dev \ + libunbound-dev libminiupnpc-dev \ + libunwind8-dev liblzma-dev libldns-dev \ + libexpat1-dev libgtest-dev libzmq3-dev \ + + && curl -o- https://deb.nodesource.com/setup_6.x| bash \ + && apt-get install nodejs \ + + && git clone https://github.com/Snipa22/xmr-node-proxy /app \ + && cd /app && npm install \ + + && openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.proxy" \ + -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 \ + + && apt-get --auto-remove purge -qqy ${BUILD_DEPS} \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && chown -R proxy.proxy /app \ + && mkdir /logs && chown -R proxy.proxy /logs + +USER proxy +WORKDIR /app + +ENTRYPOINT ["node","proxy.js"] \ No newline at end of file diff --git a/README.md b/README.md index a390fe1..6110024 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ The proxy is pre-configured for a 1% donation. This is easily toggled inside of If you need help installing the pool from scratch, please have your servers ready, which would be Ubuntu 16.04 servers, blank and clean, DNS records pointed. These need to be x86_64 boxes with AES-NI Available. -Installation asstiance is 4 XMR, with a 2 XMR deposit, with remainder to be paid on completion. +Installation assistance is 4 XMR, with a 2 XMR deposit, with remainder to be paid on completion. Configuration assistance is 2 XMR with a 1 XMR deposit, and includes debugging your proxy configurations, ensuring that everything is running, and tuning for your uses/needs. SSH access with a sudo-enabled user will be needed for installs, preferably the user that is slated to run the pool. diff --git a/lib/xmr.js b/lib/xmr.js index da914ca..90471b0 100644 --- a/lib/xmr.js +++ b/lib/xmr.js @@ -1,248 +1,269 @@ "use strict"; -const multiHashing = require('multi-hashing'); +const multiHashing = require("cryptonight-hashing"); const cnUtil = require('cryptonote-util'); const bignum = require('bignum'); const support = require('./support.js')(); const crypto = require('crypto'); let debug = { - pool: require('debug')('pool'), - diff: require('debug')('diff'), - blocks: require('debug')('blocks'), - shares: require('debug')('shares'), - miners: require('debug')('miners'), - workers: require('debug')('workers') + pool: require('debug')('pool'), + diff: require('debug')('diff'), + blocks: require('debug')('blocks'), + shares: require('debug')('shares'), + miners: require('debug')('miners'), + workers: require('debug')('workers') }; let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); Buffer.prototype.toByteArray = function () { - return Array.prototype.slice.call(this, 0); + return Array.prototype.slice.call(this, 0); }; function blockHeightCheck(nodeList, callback) { - let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); + let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); } function getRemoteNodes() { - let knownNodes = [ - '162.213.38.245:18081', - '116.93.119.79:18081', - '85.204.96.231:18081', - '107.167.87.242:18081', - '107.167.93.58:18081', - '199.231.85.122:18081', - '192.110.160.146:18081' - ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. + let knownNodes = [ + '162.213.38.245:18081', + '116.93.119.79:18081', + '85.204.96.231:18081', + '107.167.87.242:18081', + '107.167.93.58:18081', + '199.231.85.122:18081', + '192.110.160.146:18081' + ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. } function BlockTemplate(template) { - /* - We receive something identical to the result portions of the monero GBT call. - Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. - You know. Just in case amirite? - */ - this.id = template.id; - this.blob = template.blocktemplate_blob; - this.difficulty = template.difficulty; - this.height = template.height; - this.reservedOffset = template.reserved_offset; - this.workerOffset = template.worker_offset; // clientNonceLocation - this.targetDiff = template.target_diff; - this.targetHex = template.target_diff_hex; - this.buffer = new Buffer(this.blob, 'hex'); - this.previousHash = new Buffer(32); - this.workerNonce = 0; - this.solo = false; - if (typeof(this.workerOffset) === 'undefined') { - this.solo = true; - global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); - this.buffer.copy(this.previousHash, 0, 7, 39); + /* + We receive something identical to the result portions of the monero GBT call. + Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. + You know. Just in case amirite? + */ + this.id = template.id; + this.blob = template.blocktemplate_blob; + this.difficulty = template.difficulty; + this.height = template.height; + this.reservedOffset = template.reserved_offset; + this.workerOffset = template.worker_offset; // clientNonceLocation + this.targetDiff = template.target_diff; + this.targetHex = template.target_diff_hex; + this.buffer = new Buffer(this.blob, 'hex'); + this.previousHash = new Buffer(32); + this.workerNonce = 0; + this.solo = false; + this.seedHash = template.seed_hash ? Buffer.from(template.seed_hash, 'hex') : Buffer.from('00', 'hex'); + if (typeof (this.workerOffset) === 'undefined') { + this.solo = true; + global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); + this.buffer.copy(this.previousHash, 0, 7, 39); + } + this.nextBlob = function () { + if (this.solo) { + // This is running in solo mode. + this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); + } else { + this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); } - this.nextBlob = function () { - if (this.solo) { - // This is running in solo mode. - this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); - } else { - this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); - } - return cnUtil.convert_blob(this.buffer).toString('hex'); - }; + return cnUtil.convert_blob(this.buffer).toString('hex'); + }; } function MasterBlockTemplate(template) { - /* - We receive something identical to the result portions of the monero GBT call. - Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. - You know. Just in case amirite? - */ - this.blob = template.blocktemplate_blob; - this.difficulty = template.difficulty; - this.height = template.height; - this.reservedOffset = template.reserved_offset; // reserveOffset - this.workerOffset = template.client_nonce_offset; // clientNonceLocation - this.poolOffset = template.client_pool_offset; // clientPoolLocation - this.targetDiff = template.target_diff; - this.targetHex = template.target_diff_hex; - this.buffer = new Buffer(this.blob, 'hex'); - this.previousHash = new Buffer(32); - this.job_id = template.job_id; - this.workerNonce = 0; - this.poolNonce = 0; - this.solo = false; - if (typeof(this.workerOffset) === 'undefined') { - this.solo = true; - global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); - this.buffer.copy(this.previousHash, 0, 7, 39); - } - this.blobForWorker = function () { - this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); - return this.buffer.toString('hex'); - }; + /* + We receive something identical to the result portions of the monero GBT call. + Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. + You know. Just in case amirite? + */ + this.blob = template.blocktemplate_blob; + this.difficulty = template.difficulty; + this.height = template.height; + this.reservedOffset = template.reserved_offset; // reserveOffset + this.workerOffset = template.client_nonce_offset; // clientNonceLocation + this.poolOffset = template.client_pool_offset; // clientPoolLocation + this.targetDiff = template.target_diff; + this.targetHex = template.target_diff_hex; + this.buffer = new Buffer(this.blob, 'hex'); + this.previousHash = new Buffer(32); + this.job_id = template.job_id; + this.workerNonce = 0; + this.poolNonce = 0; + this.solo = false; + this.seedHash = template.seed_hash ? Buffer.from(template.seed_hash, 'hex') : Buffer.from('00', 'hex'); + if (typeof (this.workerOffset) === 'undefined') { + this.solo = true; + global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); + this.buffer.copy(this.previousHash, 0, 7, 39); + } + this.blobForWorker = function () { + this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); + return this.buffer.toString('hex'); + }; } function getJob(miner, activeBlockTemplate, bashCache) { - if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { - return miner.cachedJob; - } - - let blob = activeBlockTemplate.nextBlob(); - let target = getTargetHex(miner); - miner.lastBlockHeight = activeBlockTemplate.height; - - let newJob = { - id: crypto.pseudoRandomBytes(21).toString('base64'), - extraNonce: activeBlockTemplate.workerNonce, - height: activeBlockTemplate.height, - difficulty: miner.difficulty, - diffHex: miner.diffHex, - submissions: [], - templateID: activeBlockTemplate.id - }; - - miner.validJobs.enq(newJob); - miner.cachedJob = { - blob: blob, - job_id: newJob.id, - target: target, - id: miner.id - }; + if (miner.validJobs.size() > 0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { return miner.cachedJob; + } + + let blob = activeBlockTemplate.nextBlob(); + let target = getTargetHex(miner); + miner.lastBlockHeight = activeBlockTemplate.height; + + let newJob = { + id: crypto.pseudoRandomBytes(21).toString('base64'), + extraNonce: activeBlockTemplate.workerNonce, + height: activeBlockTemplate.height, + difficulty: miner.difficulty, + diffHex: miner.diffHex, + submissions: [], + seed_hash: activeBlockTemplate.seedHash ? activeBlockTemplate.seedHash.toString('hex') : null, + templateID: activeBlockTemplate.id + }; + + miner.validJobs.enq(newJob); + miner.cachedJob = { + blob: blob, + job_id: newJob.id, + target: target, + id: miner.id, + seed_hash: activeBlockTemplate.seedHash ? activeBlockTemplate.seedHash.toString('hex') : null, + height: activeBlockTemplate.height + }; + return miner.cachedJob; } function getMasterJob(pool, workerID) { - let activeBlockTemplate = pool.activeBlocktemplate; - let btBlob = activeBlockTemplate.blobForWorker(); - let workerData = { - id: crypto.pseudoRandomBytes(21).toString('base64'), - blocktemplate_blob: btBlob, - difficulty: activeBlockTemplate.difficulty, - height: activeBlockTemplate.height, - reserved_offset: activeBlockTemplate.reservedOffset, - worker_offset: activeBlockTemplate.workerOffset, - target_diff: activeBlockTemplate.targetDiff, - target_diff_hex: activeBlockTemplate.targetHex - }; - let localData = { - id: workerData.id, - masterJobID: activeBlockTemplate.job_id, - poolNonce: activeBlockTemplate.poolNonce - }; - if (!(workerID in pool.poolJobs)) { - pool.poolJobs[workerID] = support.circularBuffer(4); - } - pool.poolJobs[workerID].enq(localData); - return workerData; + let activeBlockTemplate = pool.activeBlocktemplate; + let btBlob = activeBlockTemplate.blobForWorker(); + let workerData = { + id: crypto.pseudoRandomBytes(21).toString('base64'), + blocktemplate_blob: btBlob, + difficulty: activeBlockTemplate.difficulty, + height: activeBlockTemplate.height, + reserved_offset: activeBlockTemplate.reservedOffset, + worker_offset: activeBlockTemplate.workerOffset, + target_diff: activeBlockTemplate.targetDiff, + seed_hash: activeBlockTemplate.seedHash ? activeBlockTemplate.seedHash.toString('hex') : null, + target_diff_hex: activeBlockTemplate.targetHex + }; + let localData = { + id: workerData.id, + masterJobID: activeBlockTemplate.job_id, + seed_hash: activeBlockTemplate.seedHash ? activeBlockTemplate.seedHash.toString('hex') : null, + poolNonce: activeBlockTemplate.poolNonce + }; + if (!(workerID in pool.poolJobs)) { + pool.poolJobs[workerID] = support.circularBuffer(4); + } + pool.poolJobs[workerID].enq(localData); + return workerData; } function getTargetHex(miner) { - if (miner.newDiff) { - miner.difficulty = miner.newDiff; - miner.newDiff = null; - } - let padded = Buffer.alloc(32); - let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); - diffBuff.copy(padded, 32 - diffBuff.length); - - let buff = padded.slice(0, 4); - let buffArray = buff.toByteArray().reverse(); - let buffReversed = new Buffer(buffArray); - miner.target = buffReversed.readUInt32BE(0); - return buffReversed.toString('hex'); + if (miner.newDiff) { + miner.difficulty = miner.newDiff; + miner.newDiff = null; + } + let padded = Buffer.alloc(32); + let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); + diffBuff.copy(padded, 32 - diffBuff.length); + + let buff = padded.slice(0, 4); + let buffArray = buff.toByteArray().reverse(); + let buffReversed = new Buffer(buffArray); + miner.target = buffReversed.readUInt32BE(0); + return buffReversed.toString('hex'); } function processShare(miner, job, blockTemplate, nonce, resultHash) { - let template = new Buffer(blockTemplate.buffer.length); - blockTemplate.buffer.copy(template); - if (blockTemplate.solo) { - template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); - } else { - template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); - } + let template = new Buffer(blockTemplate.buffer.length); + blockTemplate.buffer.copy(template); + if (blockTemplate.solo) { + template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); + } else { + template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); + } + + let hash = new Buffer(resultHash, 'hex'); + let hashArray = hash.toByteArray().reverse(); + let hashNum = bignum.fromBuffer(new Buffer(hashArray)); + let hashDiff = baseDiff.div(hashNum); - let hash = new Buffer(resultHash, 'hex'); - let hashArray = hash.toByteArray().reverse(); - let hashNum = bignum.fromBuffer(new Buffer(hashArray)); - let hashDiff = baseDiff.div(hashNum); - - if (hashDiff.ge(blockTemplate.targetDiff)) { - // Validate share with CN hash, then if valid, blast it up to the master. - let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex')); - let convertedBlob = cnUtil.convert_blob(shareBuffer); - hash = multiHashing.cryptonight(convertedBlob); - if (hash.toString('hex') !== resultHash) { - console.error(global.threadName + "Bad share from miner " + miner.logString); - miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); - return false; - } - miner.blocks += 1; - process.send({ - type: 'shareFind', - host: miner.pool, - data: { - btID: blockTemplate.id, - nonce: nonce, - resultHash: resultHash, - workerNonce: job.extraNonce - } - }); + if (hashDiff.ge(blockTemplate.targetDiff)) { + // Validate share with CN hash, then if valid, blast it up to the master. + let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex')); + let convertedBlob = cnUtil.convert_blob(shareBuffer); + if (blockTemplate.seedHash == null || blockTemplate.seedHash.length != 32) { + hash = multiHashing.cryptonight(convertedBlob, convertedBlob[0] >= 10 ? 13 : 8, job.height); + } else { + hash = multiHashing.randomx(convertedBlob, blockTemplate.seedHash, 0); } - else if (hashDiff.lt(job.difficulty)) { - process.send({type: 'invalidShare'}); - console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + - miner.identifier + " IP: " + miner.ipAddress); - return false; + if (hash.toString("hex") !== resultHash) { + if (multiHashing.cryptonight(convertedBlob, 0).toString("hex") === resultHash) { + console.error(`${global.threadName} Invalid version of CN hashing algo (CN/0) used by miner ${miner.logString}`); + } else if (multiHashing.cryptonight(convertedBlob, 1) === resultHash) { + console.error(`${global.threadName} Invalid version of CN hashing algo (CN/1) used by miner ${miner.logString}`); + } else if (multiHashing.cryptonight(convertedBlob, 8) === resultHash) { + console.error(`${global.threadName} Invalid version of CN hashing algo (CN/2) used by miner ${miner.logString}`); + } else { + console.error(`${global.threadName} Bad share from ${miner.logString}`) + } + miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); + return false; } - miner.shares += 1; - miner.hashes += job.difficulty; - return true; + miner.blocks += 1; + process.send({ + type: 'shareFind', + host: miner.pool, + data: { + btID: blockTemplate.id, + nonce: nonce, + resultHash: resultHash, + workerNonce: job.extraNonce + } + }); + } else if (hashDiff.lt(job.difficulty)) { + process.send({ type: 'invalidShare' }); + console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + + miner.identifier + " IP: " + miner.ipAddress); + return false; + } + miner.shares += 1; + miner.hashes += job.difficulty; + return true; } let devPool = { - "hostname": "xmr-donations.snipanet.com", - "port": 7777, - "ssl": false, - "share": 0, - "username": "44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr", - "password": "proxy_donations", - "keepAlive": true, - "coin": "xmr", - "default": false, - "devPool": true + "hostname": "xmr-donations.snipanet.com", + "port": 7777, + "ssl": false, + "share": 0, + "username": "44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr", + "password": "proxy_donations", + "keepAlive": true, + "coin": "xmr", + "default": false, + "devPool": true }; +function syncHash(convertedBlob) { + return multiHashing.cryptonight(convertedBlob, convertedBlob[0] >= 7 ? convertedBlob[0] - 6 : 0); +} + module.exports = function () { - return { - devPool: devPool, - hashSync: multiHashing.cryptonight, - hashAsync: multiHashing.CNAsync, - blockHeightCheck: blockHeightCheck, - getRemoteNodes: getRemoteNodes, - BlockTemplate: BlockTemplate, - getJob: getJob, - processShare: processShare, - MasterBlockTemplate: MasterBlockTemplate, - getMasterJob: getMasterJob - }; -}; \ No newline at end of file + return { + devPool: devPool, + hashSync: syncHash, + blockHeightCheck: blockHeightCheck, + getRemoteNodes: getRemoteNodes, + BlockTemplate: BlockTemplate, + getJob: getJob, + processShare: processShare, + MasterBlockTemplate: MasterBlockTemplate, + getMasterJob: getMasterJob + }; +}; diff --git a/package.json b/package.json index 0591e7c..936aa1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmr-node-proxy", - "version": "0.0.1", + "version": "0.0.2", "description": "Node proxy for XMR pools based on nodejs-pool, should support any coins that nodejs-pool does with little work", "main": "proxy.js", "scripts": { @@ -37,6 +37,7 @@ }, "optionalDependencies": { "cryptonote-util": "git://github.com/Snipa22/node-cryptonote-util.git#xmr-Nan-2.0", - "multi-hashing": "git+https://github.com/Snipa22/node-multi-hashing-aesni.git" + "multi-hashing": "git+https://github.com/Snipa22/node-multi-hashing-aesni.git#v0.1", + "cryptonight-hashing": "git+https://github.com/MoneroOcean/node-cryptonight-hashing.git#865a1f18e7dd5cf0513ae6becfdbeba3a10c9fb9" } } diff --git a/proxy.js b/proxy.js index beceb35..063b1c3 100644 --- a/proxy.js +++ b/proxy.js @@ -260,7 +260,7 @@ function Pool(poolData){ this.sendData('login', { login: this.username, pass: this.password, - agent: 'xmr-node-proxy/0.0.1' + agent: 'xmr-node-proxy/0.0.3' }); this.active = true; for (let worker in cluster.workers){ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy