235 lines
8.9 KiB
JavaScript
235 lines
8.9 KiB
JavaScript
var net = require('net');
|
|
var events = require('events');
|
|
var fs = require('fs');
|
|
var async = require('async');
|
|
var daemon = require('./daemon.js');
|
|
var stratum = require('./stratum.js');
|
|
var jobManager = require('./jobManager.js');
|
|
var util = require('./util.js');
|
|
|
|
/**
|
|
* Main pool object. It emits the following events:
|
|
* - 'started'() - when the pool is effectively started.
|
|
* - 'share'(isValid, dataObj) - In case it's valid the dataObj variable will contain (TODO) and in case it's invalid (TODO)
|
|
*/
|
|
var pool = module.exports = function pool(options, authorizeFn){
|
|
|
|
this.options = options;
|
|
var _this = this;
|
|
var publicKeyBuffer;
|
|
|
|
|
|
var emitLog = function(key, text) { _this.emit('log', 'debug' , key, text); };
|
|
var emitWarningLog = function(key, text) { _this.emit('log', 'warning', key, text); };
|
|
var emitErrorLog = function(key, text) { _this.emit('log', 'error' , key, text); };
|
|
|
|
(function Init(){
|
|
SetupJobManager();
|
|
SetupDaemonInterface();
|
|
})();
|
|
|
|
|
|
|
|
function SetupJobManager(){
|
|
_this.jobManager = new jobManager({
|
|
algorithm : options.algorithm,
|
|
address : options.address
|
|
});
|
|
_this.jobManager.on('newBlock', function(blockTemplate){
|
|
if ( typeof(_this.stratumServer ) === 'undefined') {
|
|
emitWarningLog("Stratum server still not started! cannot broadcast block!");
|
|
} else {
|
|
emitLog('system', 'Detected new block');
|
|
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
|
|
}
|
|
}).on('blockFound', function(blockHex, blockHash){
|
|
if (options.hasSubmitMethod) {
|
|
_this.daemon.cmd('submitblock',
|
|
[blockHex],
|
|
function(error, result){
|
|
emitLog('submitblock', 'Submitted Block using submitblock :'+blockHash);
|
|
}
|
|
);
|
|
} else {
|
|
_this.daemon.cmd('getblocktemplate',
|
|
[{'mode': 'submit', 'data': blockHex}],
|
|
function(error, result){
|
|
emitLog('submitblock', 'Submitted Block using getblocktemplate: '+blockHash);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function SetupDaemonInterface(){
|
|
emitLog('system','Connecting to daemon');
|
|
_this.daemon = new daemon.interface(options.daemon);
|
|
_this.daemon.on('online', function(){
|
|
async.parallel({
|
|
addressInfo: function(callback){
|
|
_this.daemon.cmd('validateaddress',
|
|
[options.address],
|
|
function(error, result){
|
|
if (error){
|
|
emitLog('system','validateaddress rpc error');
|
|
callback(error);
|
|
} else if (!result.isvalid) {
|
|
emitLog('system','address is not valid');
|
|
callback("address-not-valid");
|
|
} else {
|
|
callback(error, result);
|
|
}
|
|
}
|
|
);
|
|
},
|
|
submitMethod: function(callback){
|
|
_this.daemon.cmd('submitblock',
|
|
[],
|
|
function(error, result){
|
|
if (error && error.message === 'Method not found')
|
|
callback(null, false);
|
|
else
|
|
callback(null, true);
|
|
}
|
|
);
|
|
}
|
|
}, function(err, results){
|
|
if (err) return;
|
|
|
|
emitLog('system','Connected to daemon');
|
|
options.hasSubmitMethod = results.submitMethod;
|
|
|
|
publicKeyBuffer = options.reward === 'POW' ?
|
|
util.script_to_address(results.addressInfo.address) :
|
|
util.script_to_pubkey(results.addressInfo.pubkey);
|
|
|
|
StartStratumServer();
|
|
SetupBlockPolling();
|
|
|
|
});
|
|
|
|
}).on('startFailed', function(){
|
|
emitErrorLog('system','Failed to start daemon');
|
|
});
|
|
}
|
|
|
|
|
|
function StartStratumServer(){
|
|
emitLog('system', 'Stratum server starting on port ' + options.stratumPort);
|
|
_this.stratumServer = new stratum.Server({
|
|
port: options.stratumPort,
|
|
authorizeFn: authorizeFn
|
|
});
|
|
_this.stratumServer.on('started', function(){
|
|
emitLog('system','Stratum server started on port ' + options.stratumPort);
|
|
}).on('client.connected', function(client){
|
|
client.on('subscription', function(params, resultCallback){
|
|
|
|
var extraNonce = _this.jobManager.extraNonceCounter.next();
|
|
var extraNonce2Size = _this.jobManager.extraNonce2Size;
|
|
resultCallback(null,
|
|
extraNonce,
|
|
extraNonce2Size
|
|
);
|
|
|
|
this.sendAndSetDifficultyIfNew(options.difficulty);
|
|
if (typeof(_this.jobManager.currentJob) !== 'undefined') {
|
|
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
|
|
} else {
|
|
emitWarningLog('client', "A miner subscribed but no job to dispatch!");
|
|
}
|
|
|
|
|
|
}).on('submit', function(params, resultCallback){
|
|
var result =_this.jobManager.processShare(
|
|
params.jobId,
|
|
client.difficulty,
|
|
client.extraNonce1,
|
|
params.extraNonce2,
|
|
params.nTime,
|
|
params.nonce
|
|
);
|
|
|
|
resultCallback(result.error, result.result ? true : null);
|
|
|
|
_this.emit('share', !result.error, {
|
|
job: params.jobId,
|
|
ip: client.socket.remoteAddress,
|
|
worker: params.name,
|
|
solution: result.solution,
|
|
error: result.error ? result.error[1] : undefined,
|
|
difficulty: client.difficulty,
|
|
timestamp: Date.now() / 1000 | 0,
|
|
accepted: !!result.result,
|
|
extraNonce2: params.extraNonce2,
|
|
nTime: params.nTime,
|
|
nonce: params.nonce
|
|
});
|
|
|
|
}).on('malformedMessage', function (message) {
|
|
emitWarningLog('client', client.workerName+" has sent us a malformed message: "+message);
|
|
}).on('socketError', function() {
|
|
emitWarningLog('client', client.workerName+" has somehow had a socket error");
|
|
}).on('socketDisconnect', function() {
|
|
emitLog('client', "Client '"+client.workerName+"' disconnected!");
|
|
}).on('unknownStratumMethod', function(fullMessage) {
|
|
emitLog('client', "Client '"+client.workerName+"' has sent us an unknown stratum method: "+fullMessage.method);
|
|
});
|
|
});
|
|
}
|
|
|
|
function SetupBlockPolling(){
|
|
|
|
if (options.blockRefreshInterval === 0){
|
|
emitLog('system', 'Block template polling has been disabled');
|
|
return;
|
|
}
|
|
|
|
var pollingInterval = options.blockRefreshInterval * 1000;
|
|
var pollTimeout;
|
|
var setPoll;
|
|
|
|
setInterval(function () {
|
|
GetBlockTemplate(function(error, result) {
|
|
if (error) {
|
|
emitErrorLog('system', "Block polling error getting block template for " + options.name)
|
|
}
|
|
|
|
});
|
|
|
|
}, pollingInterval);
|
|
emitLog('system', 'Block polling setup for every ' + pollingInterval + ' milliseconds');
|
|
}
|
|
|
|
|
|
function GetBlockTemplate(callback){
|
|
_this.daemon.cmd('getblocktemplate',
|
|
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
|
|
function(error, result){
|
|
if (error) {
|
|
callback(error);
|
|
} else {
|
|
_this.jobManager.processTemplate(result, publicKeyBuffer);
|
|
callback(null, result);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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){
|
|
if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
|
|
GetBlockTemplate(function(error, result){
|
|
if (error)
|
|
emitErrorLog('system', 'Block notify error getting block template for ' + options.name);
|
|
})
|
|
}
|
|
}
|
|
|
|
};
|
|
pool.prototype.__proto__ = events.EventEmitter.prototype; |