Better error handling when dealing with multiple RPC daemons
This commit is contained in:
parent
16807d3226
commit
6cc97aff5b
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
204
lib/pool.js
204
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user