diff --git a/lib/daemon.js b/lib/daemon.js index bfc0ea6..c49a942 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -21,7 +21,7 @@ function DaemonInterface(options){ var instances = (function(){ for (var i = 0; i < options.length; i++) - options[i]['instance'] = i; + options[i]['index'] = i; return options; })(); @@ -34,11 +34,11 @@ function DaemonInterface(options){ })(); function isOnline(callback){ - cmd('getinfo', [], function(error, result){ - if (error) - callback(false); - else - callback(true); + cmd('getinfo', [], function(results){ + var allOnline = results.every(function(result){ + return !results.error; + }); + callback(allOnline); }); } @@ -48,11 +48,17 @@ function DaemonInterface(options){ set to true. */ function cmd(method, params, callback, streamResults){ - async.map(instances, function(instance, mapCallback){ + var results = []; - var multiCallback = streamResults ? callback : mapCallback; + async.each(instances, function(instance, eachCallback){ - var tries = 5; + var itemFinished = function(error, result){ + var returnObj = {error: error, response: result, instance: instance}; + if (streamResults) callback(returnObj); + else results.push(returnObj); + eachCallback(); + itemFinished = function(){}; + }; var requestJson = JSON.stringify({ id: Date.now() + Math.floor(Math.random() * 10), @@ -61,7 +67,7 @@ function DaemonInterface(options){ }); var options = { - hostname: (typeof(instance.host) === 'undefined'?'localhost':instance.host), + hostname: (typeof(instance.host) === 'undefined' ? 'localhost' : instance.host), port : instance.port, method : 'POST', auth : instance.user + ':' + instance.password, @@ -78,30 +84,36 @@ function DaemonInterface(options){ }); res.on('end', function(){ var dataJson; + var parsingError; try{ dataJson = JSON.parse(data); + } catch(e){ + parsingError = e; _this.emit('error', 'could not parse rpc data from method: ' + method + - ' on instance ' + JSON.stringify(instance)); + ' on instance ' + instance.index + ' data: ' + data); } if (typeof(dataJson) !== 'undefined') - multiCallback(dataJson.error, dataJson.result); + itemFinished(dataJson.error, dataJson.result); + else + itemFinished(parsingError); + }); }); req.on('error', function(e) { if (e.code === 'ECONNREFUSED') - multiCallback({type: 'offline', message: e.message}); + itemFinished({type: 'offline', message: e.message}, null); else - multiCallback({type: 'request error', message: e.message}); + itemFinished({type: 'request error', message: e.message}, null); }); req.end(requestJson); - }, function(err, results){ + }, function(){ if (!streamResults){ - callback(err, results); + callback(results); } }); diff --git a/lib/pool.js b/lib/pool.js index 22b476b..3ff5889 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -89,31 +89,36 @@ var pool = module.exports = function pool(options, authorizeFn){ Coin daemons either use submitblock or getblocktemplate for submitting new blocks */ function SubmitBlock(blockHex, callback){ - if (options.hasSubmitMethod) { - _this.daemon.cmd('submitblock', - [blockHex], - function(error, result){ - if (error) - emitErrorLog('submitblock', 'rpc error when submitting block with submitblock ' + - JSON.stringify(error)); - else - emitLog('submitblock', 'Submitted Block using submitblock'); - callback(); - } - ); - } else { - _this.daemon.cmd('getblocktemplate', - [{'mode': 'submit', 'data': blockHex}], - function(error, result){ - if (error) - emitErrorLog('submitblock', 'rpc error when submitting block with getblocktemplate' + - JSON.stringify(error)); - else - emitLog('submitblock', 'Submitted Block using getblocktemplate'); - callback() - } - ); + + var rcpCommand, rpcArgs; + if (options.hasSubmitMethod){ + rcpCommand = 'submitblock'; + rpcArgs = [blockHex]; } + else{ + rpcCommand = 'getblocktemplate'; + rcpArgs = [{'mode': 'submit', 'data': blockHex}]; + } + + + _this.daemon.cmd(rcpCommand, + rpcArgs, + function(results){ + results.forEach(function(result){ + if (result.error) + emitErrorLog('submitblock', 'rpc error with daemon instance ' + + result.instance.index + ' when submitting block with ' + rpcCommand + ' ' + + JSON.stringify(result.error) + ); + else + emitLog('submitblock', 'Submitted Block using ' + rpcCommand + ' to daemon instance ' + + result.instance.index + ); + }); + callback(); + } + ); + } @@ -167,71 +172,109 @@ var pool = module.exports = function pool(options, authorizeFn){ function SetupDaemonInterface(){ - //emitLog('system','Connecting to daemon'); + emitLog('system', 'Connecting to daemon(s)'); _this.daemon = new daemon.interface(options.daemon); _this.daemon.once('online', function(){ async.parallel({ addressInfo: function(callback){ - _this.daemon.cmd('validateaddress', [options.address], function(error, results){ - if (error){ - emitErrorLog('system','validateaddress rpc error'); - callback(error); - } + _this.daemon.cmd('validateaddress', [options.address], function(results){ + //Make sure address is valid with each daemon - else if (results.every(function(result, index){ - if (!result.isvalid) - emitErrorLog('system', 'Daemon instance ' + index + ' reports address is not valid'); - return result.isvalid; - })){ - callback(null, results[0]); - } else { - callback("address-not-valid"); + var allValid = results.every(function(result){ + if (result.error || !result.response){ + emitErrorLog('system','validateaddress rpc error on daemon instance ' + + result.instance.index + ', error +' + JSON.stringify(result.error)); + } + else if (!result.response.isvalid) + emitErrorLog('system', 'Daemon instance ' + result.instance.index + + ' reports address is not valid'); + return result.response && result.response.isvalid; + }); + + if (allValid) + callback(null, results[0].response); + else{ + callback('not all addresses are valid') } + }); }, miningInfo: function(callback){ - _this.daemon.cmd('getmininginfo', [], function(error, results){ - if (!error && results){ + _this.daemon.cmd('getmininginfo', [], function(results){ - // Print which network each daemon is running on - results.forEach(function(result, index){ - var network = result.testnet ? 'testnet' : 'live blockchain'; - emitLog('system', 'Daemon instance ' + index + ' is running on ' + network); - }); + // Print which network each daemon is running on - // Find and return the result with the largest block height (most in-sync) - var largestHeight = results.sort(function(a, b){ - return b.blocks - a.blocks; - })[0]; + var isTestnet; + var allValid = results.every(function(result){ - if (options.varDiff.enabled) - _this.varDiff.setNetworkDifficulty(largestHeight.difficulty); + if (result.error){ + emitErrorLog('system', 'getmininginfo on init failed with daemon instance ' + + result.instance.index + ', error ' + JSON.stringify(result.error) + ); + return false; + } - emitLog('network', 'Current block height at ' + largestHeight.blocks + - ' with difficulty of ' + largestHeight.difficulty); + var network = result.response.testnet ? 'testnet' : 'live blockchain'; + emitLog('system', 'Daemon instance ' + result.instance.index + ' is running on ' + network); - callback(null, largestHeight); - } - else{ - emitErrorLog('system', 'getmininginfo on init failed'); - callback(error, results); + if (typeof isTestnet === 'undefined'){ + isTestnet = result.response.testnet; + return true; + } + else if (isTestnet !== result.response.testnet){ + emitErrorLog('system', 'not all daemons are on same network'); + return false; + } + else + return true; + }); + + + if (!allValid){ + callback('could not getmininginfo correctly on each daemon'); + return; } + //Find and return the response with the largest block height (most in-sync) + var largestHeight = results.sort(function(a, b){ + return b.response.blocks - a.response.blocks; + })[0].response; + + if (options.varDiff.enabled) + _this.varDiff.setNetworkDifficulty(largestHeight.difficulty); + + emitLog('network', 'Current block height at ' + largestHeight.blocks + + ' with difficulty of ' + largestHeight.difficulty); + + callback(null, largestHeight); + }); }, submitMethod: function(callback){ /* This checks to see whether the daemon uses submitblock or getblocktemplate for submitting new blocks */ - _this.daemon.cmd('submitblock', [], function(error, result){ - if (error && error.message === 'Method not found') - callback(null, false); - else - callback(null, true); + _this.daemon.cmd('submitblock', [], function(results){ + var couldNotDetectMethod = results.every(function(result){ + if (result.error && result.error.message === 'Method not found'){ + callback(null, false); + return false; + } + else if (result.error && result.error.code === -1){ + callback(null, true); + return false; + } + else + return true; + }); + if (couldNotDetectMethod){ + emitErrorLog('system', 'Could not detect block submission RPC method'); + callback('block submission detection failed'); + } }); } }, function(err, results){ if (err){ - emitErrorLog('system', 'Failed to connect daemon ' + JSON.stringify(err)); + emitErrorLog('system', 'Could not start pool, ' + JSON.stringify(err)); return; } @@ -263,7 +306,7 @@ var pool = module.exports = function pool(options, authorizeFn){ GetBlockTemplate(function(error, result){ if (error){ console.error(error); - emitErrorLog('system', 'Error with initial getblocktemplate'); + emitErrorLog('system', 'Error with getblocktemplate on initializing'); } else{ SetupBlockPolling(); @@ -348,12 +391,7 @@ var pool = module.exports = function pool(options, authorizeFn){ var pollingInterval = options.blockRefreshInterval; setInterval(function () { - GetBlockTemplate(function(error, result) { - if (error) { - emitErrorLog('system', "Block polling error getting block template for " + options.name) - } - }); - + GetBlockTemplate(function(error, result){}); }, pollingInterval); emitLog('system', 'Block polling every ' + pollingInterval + ' milliseconds'); } @@ -361,16 +399,18 @@ var pool = module.exports = function pool(options, authorizeFn){ function GetBlockTemplate(callback){ _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], - function(error, result){ - if (error) { - callback(error); + function(result){ + if (result.error){ + emitErrorLog('system', '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, publicKeyBuffer); + var processedNewBlock = _this.jobManager.processTemplate(result.response, publicKeyBuffer); if (processedNewBlock && options.varDiff.enabled) _this.varDiff.setNetworkDifficulty(_this.jobManager.currentJob.difficulty); - callback(null, result); + callback(null, result.response); callback = function(){}; } }, true @@ -380,14 +420,14 @@ var pool = module.exports = function pool(options, authorizeFn){ function CheckBlockAccepted(blockHash, callback){ _this.daemon.cmd('getblock', [blockHash], - function(error, results){ - if (error){ + function(results){ + if (results.filter(function(result){return result.response && + (result.response.hash === blockHash)}).length >= 1){ + callback(true); + } + else{ callback(false); } - else if (results.filter(function(result){return result.hash === blockHash}).length >= 1) - callback(true); - else - callback(false); } ); }