diff --git a/lib/blockManager.js b/lib/blockManager.js index 81262fd..02a286a 100644 --- a/lib/blockManager.js +++ b/lib/blockManager.js @@ -271,12 +271,12 @@ function calculatePPLNSPayments(blockHeader) { paymentData[userIdentifier].amount = paymentData[userIdentifier].amount + amountToPay; let donations = 0; if(global.config.payout.devDonation > 0){ - let devDonation = (feesToPay * (global.config.payout.devDonation / 100)); + let devDonation = Math.floor(feesToPay * (global.config.payout.devDonation / 100)); donations += devDonation; paymentData[global.coinFuncs.coinDevAddress].amount = paymentData[global.coinFuncs.coinDevAddress].amount + devDonation ; } if(global.config.payout.poolDevDonation > 0){ - let poolDevDonation = (feesToPay * (global.config.payout.poolDevDonation / 100)); + let poolDevDonation = Math.floor(feesToPay * (global.config.payout.poolDevDonation / 100)); donations += poolDevDonation; paymentData[global.coinFuncs.poolDevAddress].amount = paymentData[global.coinFuncs.poolDevAddress].amount + poolDevDonation; } diff --git a/lib/coins/xmr.js b/lib/coins/xmr.js index a493a43..40ba728 100644 --- a/lib/coins/xmr.js +++ b/lib/coins/xmr.js @@ -69,7 +69,7 @@ function Coin(data){ this.getBlockTemplate = function(walletAddress, callback){ global.support.rpcDaemon('getblocktemplate', { - reserve_size: 8, + reserve_size: 17, wallet_address: walletAddress }, function(body){ return callback(body); @@ -102,22 +102,58 @@ function Coin(data){ }; this.BlockTemplate = function(template) { + /* + Generating a block template is a simple thing. Ask for a boatload of information, and go from there. + Important things to consider. + The reserved space is 13 bytes long now in the following format: + Assuming that the extraNonce starts at byte 130: + |130-133|134-137|138-141|142-145| + |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| + This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) + Each with 4 billion clients. (clientNonce) + While being unique to this particular pool thread (instanceId) + With up to 4 billion clients (minerNonce/extraNonce) + Overkill? Sure. But that's what we do here. Overkill. + */ + + // Set this.blob equal to the BT blob that we get from upstream. this.blob = template.blocktemplate_blob; + // Set this.diff equal to the known diff for this block. this.difficulty = template.difficulty; + // Set this.height equal to the known height for this block. this.height = template.height; + // Set this.reserveOffset to the byte location of the reserved offset. this.reserveOffset = template.reserved_offset; + // Set this.buffer to the binary decoded version of the BT blob. this.buffer = new Buffer(this.blob, 'hex'); + // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); + // Generate a clean, shiny new buffer. this.previous_hash = new Buffer(32); + // Copy in bytes 7 through 39 to this.previous_hash from the current BT. this.buffer.copy(this.previous_hash, 0, 7, 39); + // Reset the Nonce. - This is the per-miner/pool nonce this.extraNonce = 0; + // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. + this.clientNonceLocation = this.reserveOffset + 12; + // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. + this.clientPoolLocation = this.reserveOffset + 8; this.nextBlob = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Convert the blob into something hashable. return global.coinFuncs.convertBlob(this.buffer).toString('hex'); }; + // Make it so you can get the raw block blob out. + this.nextBlobWithChildNonce = function () { + // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. + this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); + // Don't convert the blob to something hashable. You bad. + return this.buffer.toString('hex'); + }; }; - this.cryptoNight = multiHashing['cryptonight']; + this.cryptoNight = multiHashing.cryptonight; } diff --git a/lib/pool.js b/lib/pool.js index 4715633..bb1789a 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -23,7 +23,7 @@ let threadName; let minerCount = []; let BlockTemplate = global.coinFuncs.BlockTemplate; let hexMatch = new RegExp("^[0-9a-f]+$"); -let totalShares=0, trustedShares=0, normalShares=0, invalidShares=0; +let totalShares = 0, trustedShares = 0, normalShares = 0, invalidShares = 0; Buffer.prototype.toByteArray = function () { @@ -33,12 +33,12 @@ Buffer.prototype.toByteArray = function () { if (cluster.isMaster) { threadName = "(Master) "; - setInterval(function(){ + setInterval(function () { console.log(`Processed ${trustedShares}/${normalShares}/${invalidShares}/${totalShares} Trusted/Validated/Invalid/Total shares in the last 30 seconds`); - totalShares=0; - trustedShares=0; - normalShares=0; - invalidShares=0; + totalShares = 0; + trustedShares = 0; + normalShares = 0; + invalidShares = 0; }, 30000); } else { threadName = "(Worker " + cluster.worker.id + " - " + process.pid + ") "; @@ -59,7 +59,7 @@ function registerPool() { [global.config.pool_id, global.config.bind_ip, true, global.config.hostname, true]); global.mysql.query("DELETE FROM ports WHERE pool_id = ?", [global.config.pool_id]).then(function () { global.config.ports.forEach(function (port) { - if ('ssl' in port && port['ssl'] === true) { + if ('ssl' in port && port.ssl === true) { global.mysql.query("INSERT INTO ports (pool_id, network_port, starting_diff, port_type, description, hidden, ip_address, ssl_port) values (?, ?, ?, ?, ?, ?, ?, 1)", [global.config.pool_id, port.port, port.difficulty, port.portType, port.desc, port.hidden, global.config.bind_ip]); } else { @@ -171,7 +171,7 @@ function templateUpdate(repeating) { newBlockTemplate(rpcResponse); } } - if (repeating !== false){ + if (repeating !== false) { setTimeout(templateUpdate, 300); } } @@ -215,9 +215,13 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer let pass_split = pass.split(":"); this.error = ""; this.identifier = pass_split[0]; - if (agent && agent.includes('MinerGate')){ + this.proxy = false; + if (agent && agent.includes('MinerGate')) { this.identifier = "MinerGate"; } + if (agent && agent.includes('xmr-node-proxy')) { + this.proxy = true; + } this.paymentID = null; this.valid_miner = true; this.port = port; @@ -244,7 +248,7 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer this.fixed_diff = false; this.difficulty = startingDiff; this.connectTime = Date.now(); - if (agent && agent.includes('NiceHash')){ + if (agent && agent.includes('NiceHash')) { this.fixed_diff = true; this.difficulty = global.coinFuncs.niceHashDiff; } @@ -312,7 +316,7 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer this.error = "Exchange addresses need payment IDs"; this.valid_miner = false; } - if (!activeBlockTemplate){ + if (!activeBlockTemplate) { this.error = "No active block template"; this.valid_miner = false; } @@ -343,7 +347,7 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer }; } - this.validJobs = global.support.circularBuffer(10); + this.validJobs = global.support.circularBuffer(4); this.cachedJob = null; @@ -388,9 +392,15 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer } }; - this.updateDifficulty = function(){ + this.updateDifficulty = function () { if (this.hashes > 0) { - this.setNewDiff(Math.floor(this.hashes / (Math.floor((Date.now() - this.connectTime) / 1000))) * global.config.pool.targetTime); + let newDiff = 0; + if (this.proxy) { + newDiff = Math.floor(Math.floor(this.hashes / (Math.floor((Date.now() - this.connectTime) / 1000)))); + } else { + newDiff = Math.floor(this.hashes / (Math.floor((Date.now() - this.connectTime) / 1000))) * global.config.pool.targetTime; + } + this.setNewDiff(newDiff); } else { this.updateDifficultyOld(); } @@ -399,7 +409,7 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer this.setNewDiff = function (difficulty) { this.newDiff = Math.round(difficulty); debug(threadName + "Difficulty: " + this.newDiff + " For: " + this.logString + " Time Average: " + this.shareTimeBuffer.average(this.lastShareTime) + " Entries: " + this.shareTimeBuffer.size() + " Sum: " + this.shareTimeBuffer.sum()); - if (this.newDiff > global.config.pool.maxDifficulty) { + if (this.newDiff > global.config.pool.maxDifficulty && !this.proxy) { this.newDiff = global.config.pool.maxDifficulty; } if (this.difficulty === this.newDiff) { @@ -409,10 +419,10 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer this.newDiff = global.config.pool.minDifficulty; } debug(threadName + "Difficulty change to: " + this.newDiff + " For: " + this.logString); - if (this.hashes > 0){ - debug(threadName + "Hashes: " + this.hashes + " in: " + Math.floor((Date.now() - this.connectTime)/1000) + " seconds gives: " + - Math.floor(this.hashes/(Math.floor((Date.now() - this.connectTime)/1000))) + " hashes/second or: " + - Math.floor(this.hashes/(Math.floor((Date.now() - this.connectTime)/1000))) * global.config.pool.targetTime + " difficulty versus: " + this.newDiff); + if (this.hashes > 0) { + debug(threadName + "Hashes: " + this.hashes + " in: " + Math.floor((Date.now() - this.connectTime) / 1000) + " seconds gives: " + + Math.floor(this.hashes / (Math.floor((Date.now() - this.connectTime) / 1000))) + " hashes/second or: " + + Math.floor(this.hashes / (Math.floor((Date.now() - this.connectTime) / 1000))) * global.config.pool.targetTime + " difficulty versus: " + this.newDiff); } this.messageSender('job', this.getJob()); }; @@ -458,32 +468,65 @@ function Miner(id, login, pass, ipAddress, startingDiff, messageSender, protoVer return buffReversed.toString('hex'); }; this.getJob = function () { - + if (this.lastBlockHeight === activeBlockTemplate.height && !this.newDiff && this.cachedJob !== null) { return this.cachedJob; } - let blob = activeBlockTemplate.nextBlob(); - let target = this.getTargetHex(); - this.lastBlockHeight = activeBlockTemplate.height; + if (!this.proxy) { + let blob = activeBlockTemplate.nextBlob(); + let target = this.getTargetHex(); + this.lastBlockHeight = activeBlockTemplate.height; - let newJob = { - id: crypto.pseudoRandomBytes(21).toString('base64'), - extraNonce: activeBlockTemplate.extraNonce, - height: activeBlockTemplate.height, - difficulty: this.difficulty, - diffHex: this.diffHex, - submissions: [] - }; + let newJob = { + id: crypto.pseudoRandomBytes(21).toString('base64'), + extraNonce: activeBlockTemplate.extraNonce, + height: activeBlockTemplate.height, + difficulty: this.difficulty, + diffHex: this.diffHex, + submissions: [] + }; - this.validJobs.enq(newJob); - this.cachedJob = { - blob: blob, - job_id: newJob.id, - target: target, - id: this.id - }; + this.validJobs.enq(newJob); + this.cachedJob = { + blob: blob, + job_id: newJob.id, + target: target, + id: this.id + }; + } else { + let blob = activeBlockTemplate.nextBlobWithChildNonce(); + if (this.newDiff) { + this.difficulty = this.newDiff; + this.newDiff = null; + } + this.lastBlockHeight = activeBlockTemplate.height; + + let newJob = { + id: crypto.pseudoRandomBytes(21).toString('base64'), + extraNonce: activeBlockTemplate.extraNonce, + height: activeBlockTemplate.height, + difficulty: this.difficulty, + diffHex: this.diffHex, + clientPoolLocation: activeBlockTemplate.clientPoolLocation, + clientNonceLocation: activeBlockTemplate.clientNonceLocation, + submissions: [] + }; + this.validJobs.enq(newJob); + this.cachedJob = { + blocktemplate_blob: blob, + difficulty: activeBlockTemplate.difficulty, + height: activeBlockTemplate.height, + reserved_offset: activeBlockTemplate.reserveOffset, + client_nonce_offset: activeBlockTemplate.clientNonceLocation, + client_pool_offset: activeBlockTemplate.clientPoolLocation, + target_diff: this.difficulty, + target_diff_hex: this.diffHex, + job_id: newJob.id, + id: this.id + }; + } return this.cachedJob; }; } @@ -526,10 +569,19 @@ function recordShareData(miner, job, shareDiff, blockCandidate, hashHex, shareTy } -function processShare(miner, job, blockTemplate, nonce, resultHash) { +function processShare(miner, job, blockTemplate, params) { + let nonce = params.nonce; + let resultHash = params.result; let template = new Buffer(blockTemplate.buffer.length); - blockTemplate.buffer.copy(template); - template.writeUInt32BE(job.extraNonce, blockTemplate.reserveOffset); + if (!miner.proxy) { + blockTemplate.buffer.copy(template); + template.writeUInt32BE(job.extraNonce, blockTemplate.reserveOffset); + } else { + blockTemplate.buffer.copy(template); + template.writeUInt32BE(job.extraNonce, blockTemplate.reserveOffset); + template.writeUInt32BE(params.poolNonce, job.clientPoolLocation); + template.writeUInt32BE(params.workerNonce, job.clientNonceLocation); + } let shareBuffer = global.coinFuncs.constructNewBlob(template, new Buffer(nonce, 'hex')); let convertedBlob; @@ -549,7 +601,7 @@ function processShare(miner, job, blockTemplate, nonce, resultHash) { if (hash.toString('hex') !== resultHash) { console.error(threadName + "Bad share from miner " + miner.logString); process.send({type: 'invalidShare'}); - if (miner.incremented === false){ + if (miner.incremented === false) { miner.newDiff = miner.difficulty + 1; miner.incremented = true; } else { @@ -661,24 +713,34 @@ function handleMinerData(method, params, ip, portData, sendReply, pushMessage) { global.database.storeInvalidShare(miner.invalidShareProto); return; } - - if (job.submissions.indexOf(params.nonce) !== -1) { - console.warn(threadName + 'Duplicate share: ' + JSON.stringify(params) + ' from ' + miner.logString); - miner.checkBan(false); - sendReply('Duplicate share'); - global.database.storeInvalidShare(miner.invalidShareProto); - return; + if (!miner.proxy) { + if (job.submissions.indexOf(params.nonce) !== -1) { + console.warn(threadName + 'Duplicate share: ' + JSON.stringify(params) + ' from ' + miner.logString); + miner.checkBan(false); + sendReply('Duplicate share'); + global.database.storeInvalidShare(miner.invalidShareProto); + return; + } + job.submissions.push(params.nonce); + } else { + let nonce_test = `${params.nonce}_${params.poolNonce}_${params.workerNonce}`; + if (job.submissions.indexOf(nonce_test) !== -1) { + console.warn(threadName + 'Duplicate share: ' + JSON.stringify(params) + ' from ' + miner.logString); + miner.checkBan(false); + sendReply('Duplicate share'); + global.database.storeInvalidShare(miner.invalidShareProto); + return; + } + job.submissions.push(nonce_test); } - job.submissions.push(params.nonce); - let blockTemplate = activeBlockTemplate.height === job.height ? activeBlockTemplate : pastBlockTemplates.toarray().filter(function (t) { - return t.height === job.height; - })[0]; + return t.height === job.height; + })[0]; if (!blockTemplate) { console.warn(threadName + 'Block expired, Height: ' + job.height + ' from ' + miner.logString); - if (miner.incremented === false){ + if (miner.incremented === false) { miner.newDiff = miner.difficulty + 1; miner.incremented = true; } else { @@ -691,7 +753,7 @@ function handleMinerData(method, params, ip, portData, sendReply, pushMessage) { return; } - let shareAccepted = processShare(miner, job, blockTemplate, params.nonce, params.result); + let shareAccepted = processShare(miner, job, blockTemplate, params); miner.checkBan(shareAccepted); if (global.config.pool.trustedMiners) { @@ -768,7 +830,7 @@ if (cluster.isMaster) { workerList.push(worker); }); templateUpdate(); - global.support.sendEmail(global.config.general.adminEmail, "Pool server "+global.config.hostname+" online", "The pool server: "+global.config.hostname+" with IP: "+global.config.bind_ip+" is online"); + global.support.sendEmail(global.config.general.adminEmail, "Pool server " + global.config.hostname + " online", "The pool server: " + global.config.hostname + " with IP: " + global.config.bind_ip + " is online"); } else { setInterval(checkAliveMiners, 30000); setInterval(retargetMiners, global.config.pool.retargetTime * 1000); @@ -778,7 +840,7 @@ if (cluster.isMaster) { templateUpdate(false); }, 60000); async.each(global.config.ports, function (portData) { - if (global.config[portData.portType].enable !== true){ + if (global.config[portData.portType].enable !== true) { return; } let handleMessage = function (socket, jsonData, pushMessage) { @@ -830,7 +892,7 @@ if (cluster.isMaster) { socket.on('data', function (d) { dataBuffer += d; - if (Buffer.byteLength(dataBuffer, 'utf8') > 10240) { //10KB + if (Buffer.byteLength(dataBuffer, 'utf8') > 102400) { //10KB dataBuffer = null; console.warn(threadName + 'Excessive packet size from: ' + socket.remoteAddress); socket.destroy(); @@ -879,7 +941,7 @@ if (cluster.isMaster) { }); } - if ('ssl' in portData && portData['ssl'] === true) { + if ('ssl' in portData && portData.ssl === true) { tls.createServer({ key: fs.readFileSync('cert.key'), cert: fs.readFileSync('cert.pem')