Better error handling when dealing with multiple RPC daemons

This commit is contained in:
Matt 2014-02-27 11:27:09 -07:00
parent 16807d3226
commit 6cc97aff5b
2 changed files with 150 additions and 98 deletions

View File

@ -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);
}
});

View File

@ -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);
}
);
}