2014-03-01 17:19:10 -08:00
|
|
|
var Stratum = require('stratum-pool');
|
2014-04-06 20:11:53 -07:00
|
|
|
var redis = require('redis');
|
2014-03-14 12:02:55 -07:00
|
|
|
var net = require('net');
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
var MposCompatibility = require('./mposCompatibility.js');
|
|
|
|
var ShareProcessor = require('./shareProcessor.js');
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
module.exports = function(logger){
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-04-06 20:11:53 -07:00
|
|
|
var _this = this;
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-14 12:02:55 -07:00
|
|
|
var poolConfigs = JSON.parse(process.env.pools);
|
|
|
|
var portalConfig = JSON.parse(process.env.portalConfig);
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-30 16:04:54 -07:00
|
|
|
var forkId = process.env.forkId;
|
2014-03-14 12:02:55 -07:00
|
|
|
|
2014-03-30 16:04:54 -07:00
|
|
|
var pools = {};
|
|
|
|
|
2014-04-06 20:11:53 -07:00
|
|
|
var proxySwitch = {};
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-04 12:24:02 -08:00
|
|
|
//Handle messages from master process sent via IPC
|
2014-03-01 17:19:10 -08:00
|
|
|
process.on('message', function(message) {
|
2014-03-04 12:24:02 -08:00
|
|
|
switch(message.type){
|
2014-04-06 20:11:53 -07:00
|
|
|
|
2014-04-15 15:38:51 -07:00
|
|
|
case 'banIP':
|
|
|
|
for (var p in pools){
|
|
|
|
if (pools[p].stratumServer)
|
|
|
|
pools[p].stratumServer.addBannedIP(message.ip);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2014-03-04 12:24:02 -08:00
|
|
|
case 'blocknotify':
|
2014-04-08 11:23:48 -07:00
|
|
|
|
|
|
|
var messageCoin = message.coin.toLowerCase();
|
|
|
|
var poolTarget = Object.keys(pools).filter(function(p){
|
|
|
|
return p.toLowerCase() === messageCoin;
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
if (poolTarget)
|
2014-04-16 10:50:33 -07:00
|
|
|
pools[poolTarget].processBlockNotify(message.hash, 'blocknotify script');
|
2014-04-08 11:23:48 -07:00
|
|
|
|
2014-03-04 12:24:02 -08:00
|
|
|
break;
|
2014-04-06 20:11:53 -07:00
|
|
|
|
|
|
|
// IPC message for pool switching
|
2014-03-14 12:02:55 -07:00
|
|
|
case 'switch':
|
2014-04-06 20:11:53 -07:00
|
|
|
var logSystem = 'Proxy';
|
|
|
|
var logComponent = 'Switch';
|
|
|
|
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
|
|
|
|
2014-04-08 11:23:48 -07:00
|
|
|
var messageCoin = message.coin.toLowerCase();
|
|
|
|
var newCoin = Object.keys(pools).filter(function(p){
|
|
|
|
return p.toLowerCase() === messageCoin;
|
|
|
|
})[0];
|
2014-04-06 20:11:53 -07:00
|
|
|
|
2014-04-08 11:23:48 -07:00
|
|
|
if (!newCoin){
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
var algo = poolConfigs[newCoin].coin.algorithm;
|
2014-04-06 20:11:53 -07:00
|
|
|
var newPool = pools[newCoin];
|
|
|
|
var oldCoin = proxySwitch[algo].currentPool;
|
|
|
|
var oldPool = pools[oldCoin];
|
|
|
|
var proxyPort = proxySwitch[algo].port;
|
|
|
|
|
2014-04-16 18:53:40 -07:00
|
|
|
if (newCoin == oldCoin) {
|
2014-04-06 20:11:53 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin);
|
2014-04-16 18:53:40 -07:00
|
|
|
break;
|
|
|
|
}
|
2014-04-06 20:11:53 -07:00
|
|
|
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Proxy message for ' + algo + ' from ' + oldCoin + ' to ' + newCoin);
|
|
|
|
|
|
|
|
if (newPool) {
|
2014-03-14 12:02:55 -07:00
|
|
|
oldPool.relinquishMiners(
|
|
|
|
function (miner, cback) {
|
|
|
|
// relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there.
|
2014-04-06 20:11:53 -07:00
|
|
|
cback(miner.client.socket.localPort == proxyPort)
|
2014-03-14 12:02:55 -07:00
|
|
|
},
|
|
|
|
function (clients) {
|
2014-04-06 20:11:53 -07:00
|
|
|
newPool.attachMiners(clients);
|
2014-03-14 12:02:55 -07:00
|
|
|
}
|
2014-04-06 20:11:53 -07:00
|
|
|
);
|
|
|
|
proxySwitch[algo].currentPool = newCoin;
|
2014-04-08 18:33:46 -07:00
|
|
|
|
2014-04-15 15:38:51 -07:00
|
|
|
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host)
|
2014-04-08 18:33:46 -07:00
|
|
|
redisClient.on('ready', function(){
|
|
|
|
redisClient.hset('proxyState', algo, newCoin, function(error, obj) {
|
|
|
|
if (error) {
|
|
|
|
logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err))
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo);
|
|
|
|
}
|
2014-04-16 18:53:40 -07:00
|
|
|
});
|
|
|
|
});
|
2014-03-14 12:02:55 -07:00
|
|
|
}
|
|
|
|
break;
|
2014-03-01 17:19:10 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2014-03-03 12:51:11 -08:00
|
|
|
Object.keys(poolConfigs).forEach(function(coin) {
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-03 12:51:11 -08:00
|
|
|
var poolOptions = poolConfigs[coin];
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-22 23:16:06 -07:00
|
|
|
var logSystem = 'Pool';
|
|
|
|
var logComponent = coin;
|
2014-03-30 16:04:54 -07:00
|
|
|
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
2014-03-22 23:16:06 -07:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
var handlers = {
|
|
|
|
auth: function(){},
|
|
|
|
share: function(){},
|
|
|
|
diff: function(){}
|
|
|
|
};
|
|
|
|
|
|
|
|
var shareProcessing = poolOptions.shareProcessing;
|
|
|
|
|
|
|
|
//Functions required for MPOS compatibility
|
2014-03-26 14:08:34 -07:00
|
|
|
if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){
|
2014-03-22 23:16:06 -07:00
|
|
|
var mposCompat = new MposCompatibility(logger, poolOptions)
|
2014-03-05 14:10:50 -08:00
|
|
|
|
|
|
|
handlers.auth = function(workerName, password, authCallback){
|
|
|
|
mposCompat.handleAuth(workerName, password, authCallback);
|
|
|
|
};
|
|
|
|
|
|
|
|
handlers.share = function(isValidShare, isValidBlock, data){
|
|
|
|
mposCompat.handleShare(isValidShare, isValidBlock, data);
|
|
|
|
};
|
|
|
|
|
|
|
|
handlers.diff = function(workerName, diff){
|
|
|
|
mposCompat.handleDifficultyUpdate(workerName, diff);
|
2014-03-04 12:24:02 -08:00
|
|
|
}
|
2014-03-05 14:10:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
//Functions required for internal payment processing
|
2014-03-26 14:08:34 -07:00
|
|
|
else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){
|
2014-03-05 14:10:50 -08:00
|
|
|
|
2014-03-22 23:16:06 -07:00
|
|
|
var shareProcessor = new ShareProcessor(logger, poolOptions)
|
2014-03-05 14:10:50 -08:00
|
|
|
|
|
|
|
handlers.auth = function(workerName, password, authCallback){
|
2014-04-05 16:43:02 -07:00
|
|
|
if (shareProcessing.internal.validateWorkerAddress !== true)
|
|
|
|
authCallback(true);
|
|
|
|
else {
|
|
|
|
pool.daemon.cmd('validateaddress', [workerName], function(results){
|
|
|
|
var isValid = results.filter(function(r){return r.response.isvalid}).length > 0;
|
|
|
|
authCallback(isValid);
|
|
|
|
});
|
|
|
|
}
|
2014-03-05 14:10:50 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
handlers.share = function(isValidShare, isValidBlock, data){
|
|
|
|
shareProcessor.handleShare(isValidShare, isValidBlock, data);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var authorizeFN = function (ip, workerName, password, callback) {
|
|
|
|
handlers.auth(workerName, password, function(authorized){
|
|
|
|
|
|
|
|
var authString = authorized ? 'Authorized' : 'Unauthorized ';
|
|
|
|
|
2014-03-22 23:16:06 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, authString + ' ' + workerName + ':' + password + ' [' + ip + ']');
|
2014-03-05 14:10:50 -08:00
|
|
|
callback({
|
|
|
|
error: null,
|
|
|
|
authorized: authorized,
|
|
|
|
disconnect: false
|
|
|
|
});
|
|
|
|
});
|
2014-03-01 17:19:10 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-03-22 23:16:06 -07:00
|
|
|
var pool = Stratum.createPool(poolOptions, authorizeFN, logger);
|
2014-03-05 14:10:50 -08:00
|
|
|
pool.on('share', function(isValidShare, isValidBlock, data){
|
2014-03-01 17:19:10 -08:00
|
|
|
|
|
|
|
var shareData = JSON.stringify(data);
|
|
|
|
|
2014-04-02 12:01:05 -07:00
|
|
|
if (data.blockHash && !isValidBlock)
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'We thought a block was found but it was rejected by the daemon, share data: ' + shareData);
|
2014-03-24 13:00:18 -07:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
else if (isValidBlock)
|
2014-04-02 12:01:05 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Block found: ' + data.blockHash);
|
2014-03-01 17:19:10 -08:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
if (isValidShare)
|
2014-04-17 11:51:44 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + '/' + data.shareDiff + ' by ' + data.worker + ' [' + data.ip + ']' );
|
2014-03-24 13:00:18 -07:00
|
|
|
|
2014-03-05 14:10:50 -08:00
|
|
|
else if (!isValidShare)
|
2014-04-03 11:33:10 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Share rejected: ' + shareData);
|
2014-03-05 14:10:50 -08:00
|
|
|
|
|
|
|
handlers.share(isValidShare, isValidBlock, data)
|
2014-03-01 17:19:10 -08:00
|
|
|
|
|
|
|
|
2014-03-04 00:33:55 -08:00
|
|
|
}).on('difficultyUpdate', function(workerName, diff){
|
2014-04-06 20:11:53 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Difficulty update to diff ' + diff + ' workerName=' + JSON.stringify(workerName));
|
2014-03-05 14:10:50 -08:00
|
|
|
handlers.diff(workerName, diff);
|
2014-03-22 23:16:06 -07:00
|
|
|
}).on('log', function(severity, text) {
|
|
|
|
logger[severity](logSystem, logComponent, logSubCat, text);
|
2014-04-15 15:38:51 -07:00
|
|
|
}).on('banIP', function(ip, worker){
|
|
|
|
process.send({type: 'banIP', ip: ip});
|
2014-03-04 00:33:55 -08:00
|
|
|
});
|
2014-04-06 20:11:53 -07:00
|
|
|
|
2014-03-01 17:19:10 -08:00
|
|
|
pool.start();
|
2014-04-08 11:23:48 -07:00
|
|
|
pools[poolOptions.coin.name] = pool;
|
2014-03-01 17:19:10 -08:00
|
|
|
});
|
2014-03-14 12:02:55 -07:00
|
|
|
|
|
|
|
|
2014-04-06 20:11:53 -07:00
|
|
|
if (typeof(portalConfig.proxy) !== 'undefined') {
|
2014-03-14 12:02:55 -07:00
|
|
|
|
2014-04-06 20:11:53 -07:00
|
|
|
var logSystem = 'Proxy';
|
|
|
|
var logComponent = 'Setup';
|
|
|
|
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
2014-03-14 12:02:55 -07:00
|
|
|
|
2014-04-06 20:11:53 -07:00
|
|
|
var proxyState = {};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Load proxy state for each algorithm from redis which allows NOMP to resume operation
|
|
|
|
// on the last pool it was using when reloaded or restarted
|
|
|
|
//
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis');
|
2014-04-15 15:38:51 -07:00
|
|
|
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host)
|
2014-04-06 20:11:53 -07:00
|
|
|
redisClient.on('ready', function(){
|
|
|
|
redisClient.hgetall("proxyState", function(error, obj) {
|
2014-04-11 06:26:42 -07:00
|
|
|
if (error || obj == null) {
|
2014-04-06 20:11:53 -07:00
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis');
|
|
|
|
}
|
|
|
|
else {
|
2014-04-11 06:26:42 -07:00
|
|
|
proxyState = obj;
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis');
|
2014-04-06 20:11:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Setup proxySwitch object to control proxy operations from configuration and any restored
|
|
|
|
// state. Each algorithm has a listening port, current coin name, and an active pool to
|
|
|
|
// which traffic is directed when activated in the config.
|
2014-04-16 18:53:40 -07:00
|
|
|
//
|
|
|
|
// In addition, the proxy config also takes diff and varDiff parmeters the override the
|
|
|
|
// defaults for the standard config of the coin.
|
2014-04-06 20:11:53 -07:00
|
|
|
//
|
|
|
|
Object.keys(portalConfig.proxy).forEach(function(algorithm) {
|
|
|
|
|
|
|
|
if (portalConfig.proxy[algorithm].enabled === true) {
|
2014-04-08 18:33:46 -07:00
|
|
|
var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm);
|
2014-04-06 20:11:53 -07:00
|
|
|
proxySwitch[algorithm] = {
|
|
|
|
port: portalConfig.proxy[algorithm].port,
|
|
|
|
currentPool: initalPool,
|
|
|
|
proxy: {}
|
2014-04-08 11:23:48 -07:00
|
|
|
};
|
2014-04-06 20:11:53 -07:00
|
|
|
|
|
|
|
|
2014-04-16 18:53:40 -07:00
|
|
|
// Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up
|
|
|
|
//
|
|
|
|
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was
|
|
|
|
// routed into instead.
|
|
|
|
//
|
|
|
|
if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) {
|
2014-04-08 11:23:48 -07:00
|
|
|
proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff);
|
|
|
|
proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff;
|
2014-04-16 18:53:40 -07:00
|
|
|
}
|
2014-04-06 20:11:53 -07:00
|
|
|
Object.keys(pools).forEach(function (coinName) {
|
|
|
|
var a = poolConfigs[coinName].coin.algorithm;
|
|
|
|
var p = pools[coinName];
|
2014-04-16 18:53:40 -07:00
|
|
|
if (a === algorithm) {
|
2014-04-06 20:11:53 -07:00
|
|
|
p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff);
|
2014-04-16 18:53:40 -07:00
|
|
|
}
|
2014-04-06 20:11:53 -07:00
|
|
|
});
|
|
|
|
|
2014-04-08 11:23:48 -07:00
|
|
|
proxySwitch[algorithm].proxy = net.createServer(function(socket) {
|
2014-04-06 20:11:53 -07:00
|
|
|
var currentPool = proxySwitch[algorithm].currentPool;
|
|
|
|
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
|
|
|
|
|
|
|
logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port
|
2014-04-16 18:53:40 -07:00
|
|
|
+ ' routing to ' + currentPool);
|
2014-04-06 20:11:53 -07:00
|
|
|
pools[currentPool].getStratumServer().handleNewClient(socket);
|
|
|
|
|
|
|
|
}).listen(parseInt(proxySwitch[algorithm].port), function() {
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port
|
2014-04-16 18:53:40 -07:00
|
|
|
+ ' into ' + proxySwitch[algorithm].currentPool);
|
2014-04-06 20:11:53 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}).on('error', function(err){
|
|
|
|
logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err);
|
|
|
|
});
|
2014-03-14 12:02:55 -07:00
|
|
|
}
|
2014-04-06 20:11:53 -07:00
|
|
|
|
|
|
|
this.getFirstPoolForAlgorithm = function(algorithm) {
|
|
|
|
var foundCoin = "";
|
|
|
|
Object.keys(poolConfigs).forEach(function(coinName) {
|
|
|
|
if (poolConfigs[coinName].coin.algorithm == algorithm) {
|
|
|
|
if (foundCoin === "")
|
|
|
|
foundCoin = coinName;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return foundCoin;
|
|
|
|
};
|
2014-04-05 16:43:02 -07:00
|
|
|
};
|