diff --git a/lib/pool.js b/lib/pool.js index e5f52c9d7..dffb02de3 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -13,302 +13,399 @@ var util = require('./util.js'); throw err; });*/ -var pool = module.exports = function pool(options, authorizeFn){ - +var pool = module.exports = function pool(options, authorizeFn) { + this.options = options; - var _this = this; var blockPollingIntervalId; - - + var emitLog = function(text) { _this.emit('log', 'debug' , text); }; var emitWarningLog = function(text) { _this.emit('log', 'warning', text); }; var emitErrorLog = function(text) { _this.emit('log', 'error' , text); }; var emitSpecialLog = function(text) { _this.emit('log', 'special', text); }; - - - - if (!(options.coin.algorithm in algos)){ + + if (options.coin.algorithm in algos === false) { + emitErrorLog('The ' + options.coin.algorithm + ' hashing algorithm is not supported.'); throw new Error(); - } - - - - this.start = function(){ + + }; + + this.start = function() { + SetupVarDiff(); SetupApi(); - SetupDaemonInterface(function(){ - DetectCoinData(function(){ + SetupDaemonInterface(function() { + + DetectCoinData(function() { + SetupRecipients(); SetupJobManager(); - OnBlockchainSynced(function(){ - GetFirstJob(function(){ + OnBlockchainSynced(function() { + + GetFirstJob(function() { + SetupBlockPolling(); SetupPeer(); - StartStratumServer(function(){ + StartStratumServer(function() { + OutputPoolInfo(); _this.emit('started'); + }); + }); + }); + }); + }); + }; - - - - function GetFirstJob(finishedCallback){ - - GetBlockTemplate(function(error, result){ + + function GetFirstJob(finishedCallback) { + + GetBlockTemplate(function(error, result) { + if (error) { + emitErrorLog('Error with getblocktemplate on creating first job, server cannot start'); return; - } - + + }; var portWarnings = []; - var networkDiffAdjusted = options.initStats.difficulty; - - Object.keys(options.ports).forEach(function(port){ + Object.keys(options.ports).forEach(function(port) { + var portDiff = options.ports[port].diff; - if (networkDiffAdjusted < portDiff) + if (networkDiffAdjusted < portDiff) { + portWarnings.push('port ' + port + ' w/ diff ' + portDiff); + + }; + }); //Only let the first fork show synced status or the log wil look flooded with it if (portWarnings.length > 0 && (!process.env.forkId || process.env.forkId === '0')) { + var warnMessage = 'Network diff of ' + networkDiffAdjusted + ' is lower than ' + portWarnings.join(' and '); emitWarningLog(warnMessage); - } - + + }; finishedCallback(); - + }); - } - - - function OutputPoolInfo(){ - - var startMessage = 'Stratum Pool Server Started for ' + options.coin.name + - ' [' + options.coin.symbol.toUpperCase() + '] {' + options.coin.algorithm + '}'; - if (process.env.forkId && process.env.forkId !== '0'){ + + }; + + function OutputPoolInfo() { + + var startMessage = 'Stratum Pool Server Started for ' +options.coin.name +' [' + options.coin.symbol.toUpperCase() + '] {' + options.coin.algorithm + '}'; + if (process.env.forkId && process.env.forkId !== '0') { + emitLog(startMessage); return; + } var infoLines = [startMessage, - 'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'), - 'Detected Reward Type:\t' + options.coin.reward, - 'Current Block Height:\t' + _this.jobManager.currentJob.rpcData.height, - 'Current Connect Peers:\t' + options.initStats.connections, - 'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier, - 'Network Difficulty:\t' + options.initStats.difficulty, - 'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate), - 'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '), - 'Pool Fee Percent:\t' + _this.options.feePercent + '%' + 'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'), + 'Detected Reward Type:\t' + options.coin.reward, + 'Current Block Height:\t' + _this.jobManager.currentJob.rpcData.height, + 'Current Connect Peers:\t' + options.initStats.connections, + 'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier, + 'Network Difficulty:\t' + options.initStats.difficulty, + 'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate), + 'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '), + 'Pool Fee Percent:\t' + _this.options.feePercent + '%' ]; - - if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0) + + if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0) { infoLines.push('Block polling every:\t' + options.blockRefreshInterval + ' ms'); - + }; emitSpecialLog(infoLines.join('\n\t\t\t\t\t\t')); } - function OnBlockchainSynced(syncedCallback){ + function OnBlockchainSynced(syncedCallback) { - var checkSynced = function(displayNotSynced){ - _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ], "rules": [ "segwit" ]}], function(results){ - var synced = results.every(function(r){ + var checkSynced = function(displayNotSynced) { + + _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ], "rules": [ "segwit" ]}], function(results) { + + var synced = results.every(function(r) { + return !r.error || r.error.code !== -10; + }); if (synced){ + syncedCallback(); - } - else{ + + } else { + if (displayNotSynced) displayNotSynced(); setTimeout(checkSynced, 5000); //Only let the first fork show synced status or the log wil look flooded with it - if (!process.env.forkId || process.env.forkId === '0') + if (!process.env.forkId || process.env.forkId === '0') { + generateProgress(); - } - + + }; + + }; + }); + }; - checkSynced(function(){ + checkSynced(function() { + //Only let the first fork show synced status or the log wil look flooded with it - if (!process.env.forkId || process.env.forkId === '0') + if (!process.env.forkId || process.env.forkId === '0') { + emitErrorLog('Daemon is still syncing with network (download blockchain) - server will be started once synced'); + + }; + }); - var generateProgress = function(){ + var generateProgress = function() { var cmd = options.coin.hasGetInfo ? 'getinfo' : 'getblockchaininfo'; _this.daemon.cmd(cmd, [], function(results) { + var blockCount = results.sort(function (a, b) { + return b.response.blocks - a.response.blocks; + })[0].response.blocks; - + //get list of peers and their highest block height to compare to ours - _this.daemon.cmd('getpeerinfo', [], function(results){ - + _this.daemon.cmd('getpeerinfo', [], function(results) { + var peers = results[0].response; - var totalBlocks = peers.sort(function(a, b){ + var totalBlocks = peers.sort(function(a, b) { + return b.startingheight - a.startingheight; + })[0].startingheight; - + var percent = (blockCount / totalBlocks * 100).toFixed(2); emitWarningLog('Downloaded ' + percent + '% of blockchain from ' + peers.length + ' peers'); + }); }); + }; - + } - - + function SetupApi() { if (typeof(options.api) !== 'object' || typeof(options.api.start) !== 'function') { + return; + } else { + options.api.start(_this); + } + } - function SetupPeer(){ - if (!options.p2p || !options.p2p.enabled) + function SetupPeer() { + if (!options.p2p || !options.p2p.enabled) { + return; + + }; - if (options.testnet && !options.coin.peerMagicTestnet){ + if (options.testnet && !options.coin.peerMagicTestnet) { + emitErrorLog('p2p cannot be enabled in testnet without peerMagicTestnet set in coin configuration'); return; + } - else if (!options.coin.peerMagic){ + else if (!options.coin.peerMagic) { + emitErrorLog('p2p cannot be enabled without peerMagic set in coin configuration'); return; + } _this.peer = new peer(options); _this.peer.on('connected', function() { + emitLog('p2p connection successful'); - }).on('connectionRejected', function(){ + + }).on('connectionRejected', function() { + emitErrorLog('p2p connection failed - likely incorrect p2p magic value'); - }).on('disconnected', function(){ + + }).on('disconnected', function() { + emitWarningLog('p2p peer node disconnected - attempting reconnection...'); - }).on('connectionFailed', function(e){ + + }).on('connectionFailed', function(e) { + emitErrorLog('p2p connection failed - likely incorrect host or port'); - }).on('socketError', function(e){ + + }).on('socketError', function(e) { + emitErrorLog('p2p had a socket error ' + JSON.stringify(e)); - }).on('error', function(msg){ + + }).on('error', function(msg) { + emitWarningLog('p2p had an error ' + msg); - }).on('blockFound', function(hash){ + + }).on('blockFound', function(hash) { + _this.processBlockNotify(hash, 'p2p'); + }); + } - - - function SetupVarDiff(){ + + function SetupVarDiff() { + _this.varDiff = {}; Object.keys(options.ports).forEach(function(port) { - if (options.ports[port].varDiff) + + if (options.ports[port].varDiff) { _this.setVarDiff(port, options.ports[port].varDiff); + }; + }); + } /* Coin daemons either use submitblock or getblocktemplate for submitting new blocks */ - function SubmitBlock(blockHex, callback){ + function SubmitBlock(blockHex, callback) { var rpcCommand, rpcArgs; - if (options.hasSubmitMethod){ + if (options.hasSubmitMethod) { + rpcCommand = 'submitblock'; rpcArgs = [blockHex]; - } - else{ + + } else { + rpcCommand = 'getblocktemplate'; rpcArgs = [{'mode': 'submit', 'data': blockHex}]; + } - - + _this.daemon.cmd(rpcCommand, rpcArgs, - function(results){ - for (var i = 0; i < results.length; i++){ + function(results) { + + for (var i = 0; i < results.length; i++) { + var result = results[i]; if (result.error) { - emitErrorLog('rpc error with daemon instance ' + - result.instance.index + ' when submitting block with ' + rpcCommand + ' ' + - JSON.stringify(result.error) - ); + + emitErrorLog('rpc error with daemon instance ' +result.instance.index + ' when submitting block with ' + rpcCommand + ' ' +JSON.stringify(result.error); return; - } - else if (result.response === 'rejected') { + + } else if (result.response === 'rejected') { + emitErrorLog('Daemon instance ' + result.instance.index + ' rejected a supposedly valid block'); return; + } + } emitLog('Submitted Block using ' + rpcCommand + ' successfully to daemon instance(s)'); callback(); + } + ); } - function SetupRecipients(){ + function SetupRecipients() { + var recipients = []; options.feePercent = 0; options.rewardRecipients = options.rewardRecipients || {}; - for (var r in options.rewardRecipients){ + for (var r in options.rewardRecipients) { + var percent = options.rewardRecipients[r]; var rObj = { percent: percent / 100 }; try { - if (r.length === 40) + + if (r.length === 40) { + rObj.script = util.miningKeyToScript(r); - else + + } else { + rObj.script = util.addressToScript(r); + + }; recipients.push(rObj); options.feePercent += percent; + } catch(e){ + emitErrorLog('Error generating transaction output script for ' + r + ' in rewardRecipients'); + } + } - if (recipients.length === 0){ + if (recipients.length === 0) { + emitErrorLog('No rewardRecipients have been setup which means no fees will be taken'); } options.recipients = recipients; + } - function SetupJobManager(){ - + function SetupJobManager() { + _this.jobManager = new jobManager(options); - - _this.jobManager.on('newBlock', function(blockTemplate){ + _this.jobManager.on('newBlock', function(blockTemplate) { + //Check if stratumServer has been initialized yet if (_this.stratumServer) { + _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); + } - }).on('updatedBlock', function(blockTemplate){ + + }).on('updatedBlock', function(blockTemplate) { + //Check if stratumServer has been initialized yet if (_this.stratumServer) { + var job = blockTemplate.getJobParams(); job[8] = false; _this.stratumServer.broadcastMiningJobs(job); + } - }).on('share', function(shareData, blockHex){ + + }).on('share', function(shareData, blockHex) { + var isValidShare = !shareData.error; var isValidBlock = !!blockHex; - var emitShare = function(){ + var emitShare = function() { + _this.emit('share', isValidShare, isValidBlock, shareData); + }; /* @@ -316,368 +413,448 @@ var pool = module.exports = function pool(options, authorizeFn){ before we emit the share, lets submit the block, then check if it was accepted using RPC getblock */ - if (!isValidBlock) + if (!isValidBlock) { + emitShare(); - else{ - SubmitBlock(blockHex, function(){ - CheckBlockAccepted(shareData.blockHash, function(isAccepted, tx){ + + } else { + + SubmitBlock(blockHex, function() { + + CheckBlockAccepted(shareData.blockHash, function(isAccepted, tx) { + isValidBlock = isAccepted; shareData.txHash = tx; emitShare(); - - GetBlockTemplate(function(error, result, foundNewBlock){ - if (foundNewBlock) + GetBlockTemplate(function(error, result, foundNewBlock) { + + if (foundNewBlock) { + emitLog('Block notification via RPC after block submission'); + + }; }); }); }); } - }).on('log', function(severity, message){ + }).on('log', function(severity, message) { + _this.emit('log', severity, message); + }); } - function SetupDaemonInterface(finishedCallback){ + function SetupDaemonInterface(finishedCallback) { - if (!Array.isArray(options.daemons) || options.daemons.length < 1){ + if (!Array.isArray(options.daemons) || options.daemons.length < 1) { + emitErrorLog('No daemons have been configured - pool cannot start'); return; + } - _this.daemon = new daemon.interface(options.daemons, function(severity, message){ + _this.daemon = new daemon.interface(options.daemons, function(severity, message) { + _this.emit('log', severity , message); + }); - - _this.daemon.once('online', function(){ + _this.daemon.once('online', function() { + finishedCallback(); - }).on('connectionFailed', function(error){ + }).on('connectionFailed', function(error) { + emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error)); - }).on('error', function(message){ + }).on('error', function(message) { + emitErrorLog(message); }); - _this.daemon.init(); } - function DetectCoinData(finishedCallback){ - + function DetectCoinData(finishedCallback) { + var batchRpcCalls = [ - ['validateaddress', [options.address]], - ['getdifficulty', []], - ['getmininginfo', []], - ['submitblock', []] - ]; - + ['validateaddress', [options.address]], + ['getdifficulty', []], + ['getmininginfo', []], + ['submitblock', []] ]; + if (options.coin.hasGetInfo) { + batchRpcCalls.push(['getinfo', []]); + } else { + batchRpcCalls.push(['getblockchaininfo', []], ['getnetworkinfo', []]); + } _this.daemon.batchCmd(batchRpcCalls, function(error, results){ - if (error || !results){ + if (error || !results) { + emitErrorLog('Could not start pool, error with init batch RPC call: ' + JSON.stringify(error)); return; + } - var rpcResults = {}; - - for (var i = 0; i < results.length; i++){ + for (var i = 0; i < results.length; i++) { + var rpcCall = batchRpcCalls[i][0]; var r = results[i]; rpcResults[rpcCall] = r.result || r.error; - - if (rpcCall !== 'submitblock' && (r.error || !r.result)){ + if (rpcCall !== 'submitblock' && (r.error || !r.result)) { + emitErrorLog('Could not start pool, error with init RPC ' + rpcCall + ' - ' + JSON.stringify(r.error)); return; + } + } - - if (!rpcResults.validateaddress.isvalid){ + if (!rpcResults.validateaddress.isvalid) { + emitErrorLog('Daemon reports address is not valid'); return; + } - if (!options.coin.reward) { - if (isNaN(rpcResults.getdifficulty) && 'proof-of-stake' in rpcResults.getdifficulty) + + if (isNaN(rpcResults.getdifficulty) && 'proof-of-stake' in rpcResults.getdifficulty) { + options.coin.reward = 'POS'; - else + + } else { + options.coin.reward = 'POW'; + + }; + } /* POS coins must use the pubkey in coinbase transaction, and pubkey is only given if address is owned by wallet.*/ if (options.coin.reward === 'POS' && typeof(rpcResults.validateaddress.pubkey) == 'undefined') { + emitErrorLog('The address provided is not from the daemon wallet - this is required for POS coins.'); return; + } - - options.poolAddressScript = (function(){ - switch(options.coin.reward){ + options.poolAddressScript = (function() { + switch(options.coin.reward) { + case 'POS': return util.pubkeyToScript(rpcResults.validateaddress.pubkey); case 'POW': return util.addressToScript(rpcResults.validateaddress.address); + } + })(); - options.testnet = options.coin.hasGetInfo ? rpcResults.getinfo.testnet : (rpcResults.getblockchaininfo.chain === 'test') ? true : false; - options.protocolVersion = options.coin.hasGetInfo ? rpcResults.getinfo.protocolversion : rpcResults.getnetworkinfo.protocolversion; - var difficulty = options.coin.hasGetInfo ? rpcResults.getinfo.difficulty : rpcResults.getblockchaininfo.difficulty; if (typeof(difficulty) == 'object') { + difficulty = difficulty['proof-of-work']; + } - options.initStats = { + connections: (options.coin.hasGetInfo ? rpcResults.getinfo.connections : rpcResults.getnetworkinfo.connections), difficulty: difficulty * algos[options.coin.algorithm].multiplier, networkHashRate: rpcResults.getmininginfo.networkhashps + }; - - - if (rpcResults.submitblock.message === 'Method not found'){ + if (rpcResults.submitblock.message === 'Method not found') { + options.hasSubmitMethod = false; - } - else if (rpcResults.submitblock.code === -1){ + + } else if (rpcResults.submitblock.code === -1) { + options.hasSubmitMethod = true; - } - else { + + } else { + emitErrorLog('Could not detect block submission RPC method, ' + JSON.stringify(results)); return; + } - finishedCallback(); - + }); + } - function StartStratumServer(finishedCallback){ + function StartStratumServer(finishedCallback) { + _this.stratumServer = new stratum.Server(options, authorizeFn); - - _this.stratumServer.on('started', function(){ + _this.stratumServer.on('started', function() { + options.initStats.stratumPorts = Object.keys(options.ports); _this.stratumServer.broadcastMiningJobs(_this.jobManager.currentJob.getJobParams()); finishedCallback(); - - }).on('broadcastTimeout', function(){ + + }).on('broadcastTimeout', function() { + emitLog('No new blocks for ' + options.jobRebroadcastTimeout + ' seconds - updating transactions & rebroadcasting work'); - GetBlockTemplate(function(error, rpcData, processedBlock){ if (error || processedBlock) return; _this.jobManager.updateCurrentJob(rpcData); }); - - }).on('client.connected', function(client){ + + }).on('client.connected', function(client) { + if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') { + _this.varDiff[client.socket.localPort].manageClient(client); + } - - client.on('difficultyChanged', function(diff){ + client.on('difficultyChanged', function(diff) { + _this.emit('difficultyUpdate', client.workerName, diff); - - }).on('subscription', function(params, resultCallback){ - + + }).on('subscription', function(params, resultCallback) { + var extraNonce = _this.jobManager.extraNonceCounter.next(); var extraNonce2Size = _this.jobManager.extraNonce2Size; - resultCallback(null, - extraNonce, - extraNonce2Size - ); - + resultCallback(null, extraNonce, extraNonce2Size); if (typeof(options.ports[client.socket.localPort]) !== 'undefined' && options.ports[client.socket.localPort].diff) { + this.sendDifficulty(options.ports[client.socket.localPort].diff); + } else { + this.sendDifficulty(8); + } - this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); - - }).on('submit', function(params, resultCallback){ - var result =_this.jobManager.processShare( - params.jobId, - client.previousDifficulty, - client.difficulty, - client.extraNonce1, - params.extraNonce2, - params.nTime, - params.nonce, - client.remoteAddress, - client.socket.localPort, - params.name - ); - + + }).on('submit', function(params, resultCallback) { + + var result =_this.jobManager.processShare(params.jobId, client.previousDifficulty, client.difficulty, client.extraNonce1, params.extraNonce2, params.nTime, params.nonce, client.remoteAddress, client.socket.localPort, params.name ); resultCallback(result.error, result.result ? true : null); - + }).on('malformedMessage', function (message) { + emitWarningLog('Malformed message from ' + client.getLabel() + ': ' + message); - + }).on('socketError', function(err) { + emitWarningLog('Socket error from ' + client.getLabel() + ': ' + JSON.stringify(err)); - - }).on('socketTimeout', function(reason){ - emitWarningLog('Connected timed out for ' + client.getLabel() + ': ' + reason) - + + }).on('socketTimeout', function(reason) { + + emitWarningLog('Connected timed out for ' + client.getLabel() + ': ' + reason); + }).on('socketDisconnect', function() { - //emitLog('Socket disconnected from ' + client.getLabel()); - + + emitLog('Socket disconnected from ' + client.getLabel()); + }).on('kickedBannedIP', function(remainingBanTime){ + emitLog('Rejected incoming connection from ' + client.remoteAddress + ' banned for ' + remainingBanTime + ' more seconds'); - - }).on('forgaveBannedIP', function(){ + + }).on('forgaveBannedIP', function() { + emitLog('Forgave banned IP ' + client.remoteAddress); - + }).on('unknownStratumMethod', function(fullMessage) { + emitLog('Unknown stratum method from ' + client.getLabel() + ': ' + fullMessage.method); - + }).on('socketFlooded', function() { + emitWarningLog('Detected socket flooding from ' + client.getLabel()); - + }).on('tcpProxyError', function(data) { + emitErrorLog('Client IP detection failed, tcpProxyProtocol is enabled yet did not receive proxy protocol message, instead got data: ' + data); - - }).on('bootedBannedWorker', function(){ + + }).on('bootedBannedWorker', function() { + emitWarningLog('Booted worker ' + client.getLabel() + ' who was connected from an IP address that was just banned'); - - }).on('triggerBan', function(reason){ + + }).on('triggerBan', function(reason) { + emitWarningLog('Banned triggered for ' + client.getLabel() + ': ' + reason); _this.emit('banIP', client.remoteAddress, client.workerName); + }); + }); + } - - - - function SetupBlockPolling(){ - if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0){ + + function SetupBlockPolling() { + + if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0) { + emitLog('Block template polling has been disabled'); return; + } - var pollingInterval = options.blockRefreshInterval; - blockPollingIntervalId = setInterval(function () { - GetBlockTemplate(function(error, result, foundNewBlock){ - if (foundNewBlock) + + GetBlockTemplate(function(error, result, foundNewBlock) { + + if (foundNewBlock) { + emitLog('Block notification via RPC polling'); + + }; + }); + }, pollingInterval); + } - function GetBlockTemplate(callback){ - _this.daemon.cmd('getblocktemplate', - [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ], "rules": [ "segwit" ]}], - function(result){ - if (result.error){ - emitErrorLog('getblocktemplate call failed for daemon instance ' + - result.instance.index + ' with error ' + JSON.stringify(result.error)); + function GetBlockTemplate(callback) { + + _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ], "rules": [ "segwit" ]}], + function(result) { + + if (result.error) { + + emitErrorLog('getblocktemplate call failed for daemon instance ' +result.instance.index + ' with error ' + JSON.stringify(result.error)); callback(result.error); + } else { + var processedNewBlock = _this.jobManager.processTemplate(result.response); callback(null, result.response, processedNewBlock); callback = function(){}; + } + }, true + ); + } - - - - function CheckBlockAccepted(blockHash, callback){ - //setTimeout(function(){ - _this.daemon.cmd('getblock', - [blockHash], - function(results){ - var validResults = results.filter(function(result){ + function CheckBlockAccepted(blockHash, callback) { + + setTimeout(function() { + + _this.daemon.cmd('getblock', [blockHash], + function(results) { + + var validResults = results.filter(function(result) { + return result.response && (result.response.hash === blockHash) + }); - - if (validResults.length >= 1){ + if (validResults.length >= 1) { + callback(true, validResults[0].response.tx[0]); - } - else{ + + } else { + callback(false); + } + } + ); - //}, 500); + + }, 500); + } - - - /** * This method is being called from the blockNotify so that when a new block is discovered by the daemon * We can inform our miners about the newly found block **/ this.processBlockNotify = function(blockHash, sourceTrigger) { + emitLog('Block notification via ' + sourceTrigger); - if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ - GetBlockTemplate(function(error, result){ - if (error) + if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash) { + + GetBlockTemplate(function(error, result) { + + if (error) { + emitErrorLog('Block notify error getting block template for ' + options.coin.name); + + }; + }) + } + }; this.relinquishMiners = function(filterFn, resultCback) { + var origStratumClients = this.stratumServer.getStratumClients(); - var stratumClients = []; Object.keys(origStratumClients).forEach(function (subId) { + stratumClients.push({subId: subId, client: origStratumClients[subId]}); + }); - async.filter( - stratumClients, - filterFn, + async.filter(stratumClients, filterFn, function (clientsToRelinquish) { + clientsToRelinquish.forEach(function(cObj) { + cObj.client.removeAllListeners(); _this.stratumServer.removeStratumClientBySubId(cObj.subId); + }); - process.nextTick(function () { + resultCback( clientsToRelinquish.map( function (item) { + return item.client; + } + ) + ); + }); + } + ) + }; - - this.attachMiners = function(miners) { + miners.forEach(function (clientObj) { + _this.stratumServer.manuallyAddStratumClient(clientObj); + }); _this.stratumServer.broadcastMiningJobs(_this.jobManager.currentJob.getJobParams()); - + }; - - this.getStratumServer = function() { + return _this.stratumServer; + }; - - this.setVarDiff = function(port, varDiffConfig) { + if (typeof(_this.varDiff[port]) != 'undefined' ) { + _this.varDiff[port].removeAllListeners(); + } var varDiffInstance = new varDiff(port, varDiffConfig); _this.varDiff[port] = varDiffInstance; @@ -687,7 +864,8 @@ var pool = module.exports = function pool(options, authorizeFn){ (which should happen when a new job comes in - AKA BLOCK) */ client.enqueueNextDifficulty(newDiff); - /*if (options.varDiff.mode === 'fast'){ + if (options.varDiff.mode === 'fast') { + //Send new difficulty, then force miner to use new diff by resending the //current job parameters but with the "clean jobs" flag set to false //so the miner doesn't restart work and submit duplicate shares @@ -695,9 +873,11 @@ var pool = module.exports = function pool(options, authorizeFn){ var job = _this.jobManager.currentJob.getJobParams(); job[8] = false; client.sendMiningJob(job); - }*/ + + } }); + }; };