NOMP CLI gives replies now. Profit switching transitioned to using CLI port rather than old profitListener port.
This commit is contained in:
parent
52108e3b7a
commit
66d7640c9b
|
@ -45,7 +45,8 @@ coins at once. The pools use clustering to load balance across multiple CPU core
|
|||
|
||||
* For reward/payment processing, shares are inserted into Redis (a fast NoSQL key/value store). The PROP (proportional)
|
||||
reward system is used with [Redis Transactions](http://redis.io/topics/transactions) for secure and super speedy payouts.
|
||||
Each and every share will be rewarded - even for rounds resulting in orphaned blocks.
|
||||
There is zero risk to the pool operator. Shares from rounds resulting in orphaned blocks will be merged into share in the
|
||||
current round so that each and every share will be rewarded
|
||||
|
||||
* This portal does not have user accounts/logins/registrations. Instead, miners simply use their coin address for stratum
|
||||
authentication. A minimalistic HTML5 front-end connects to the portals statistics API to display stats from from each
|
||||
|
@ -108,10 +109,12 @@ If your pool uses NOMP let us know and we will list your website here.
|
|||
* http://teamdoge.com
|
||||
* http://miningwith.us
|
||||
* http://kryptochaos.com
|
||||
* http://pool.uberpools.org
|
||||
* http://uberpools.org
|
||||
* http://onebtcplace.com
|
||||
* http://minr.es
|
||||
* http://mining.theminingpools.com
|
||||
* http://www.omargpools.ca/pools.html
|
||||
* http://pool.trademybit.com/
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
|
148
init.js
148
init.js
|
@ -22,6 +22,7 @@ if (!fs.existsSync('config.json')){
|
|||
}
|
||||
|
||||
var portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'})));
|
||||
var poolConfigs;
|
||||
|
||||
|
||||
var logger = new PoolLogger({
|
||||
|
@ -156,7 +157,7 @@ var buildPoolConfigs = function(){
|
|||
|
||||
|
||||
|
||||
var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
||||
var spawnPoolWorkers = function(){
|
||||
|
||||
Object.keys(poolConfigs).forEach(function(coin){
|
||||
var p = poolConfigs[coin];
|
||||
|
@ -179,9 +180,6 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
|||
return;
|
||||
}
|
||||
|
||||
for (var p in poolConfigs){
|
||||
|
||||
}
|
||||
|
||||
var serializedConfigs = JSON.stringify(poolConfigs);
|
||||
|
||||
|
@ -238,61 +236,111 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startCliListener = function(cliPort){
|
||||
var startCliListener = function(){
|
||||
|
||||
var cliPort = portalConfig.cliPort;
|
||||
|
||||
var listener = new CliListener(cliPort);
|
||||
listener.on('log', function(text){
|
||||
logger.debug('Master', 'CLI', text);
|
||||
}).on('command', function(command, params, options){
|
||||
}).on('command', function(command, params, options, reply){
|
||||
|
||||
switch(command){
|
||||
case 'blocknotify':
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]});
|
||||
});
|
||||
reply('Pool workers notified');
|
||||
break;
|
||||
case 'coinswitch':
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send({type: 'coinswitch', switchName: params[0], coin: params[1] });
|
||||
});
|
||||
processCoinSwitchCommand(params, options, reply);
|
||||
break;
|
||||
case 'restartpool':
|
||||
case 'reloadpool':
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send({type: 'restartpool', coin: params[0] });
|
||||
cluster.workers[id].send({type: 'reloadpool', coin: params[0] });
|
||||
});
|
||||
reply('reloaded pool ' + params[0]);
|
||||
break;
|
||||
default:
|
||||
reply('unrecognized command "' + command + '"');
|
||||
break;
|
||||
}
|
||||
|
||||
console.log('command: ' + JSON.stringify([command, params, options]));
|
||||
}).start();
|
||||
};
|
||||
|
||||
/*
|
||||
//
|
||||
// Receives authenticated events from coin switch listener and triggers proxy
|
||||
// to swtich to a new coin.
|
||||
//
|
||||
var startCoinswitchListener = function(portalConfig){
|
||||
var listener = new CoinswitchListener(portalConfig.coinSwitchListener);
|
||||
listener.on('log', function(text){
|
||||
logger.debug('Master', 'Coinswitch', text);
|
||||
});
|
||||
listener.on('switchcoin', function(message){
|
||||
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash};
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send(ipcMessage);
|
||||
});
|
||||
var ipcMessage = {
|
||||
type:'coinswitch',
|
||||
coin: message.coin
|
||||
};
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send(ipcMessage);
|
||||
});
|
||||
});
|
||||
listener.start();
|
||||
};
|
||||
*/
|
||||
|
||||
var startRedisBlockListener = function(portalConfig){
|
||||
var processCoinSwitchCommand = function(params, options, reply){
|
||||
|
||||
var logSystem = 'CLI';
|
||||
var logComponent = 'coinswitch';
|
||||
|
||||
var replyError = function(msg){
|
||||
reply(msg);
|
||||
logger.error(logSystem, logComponent, msg);
|
||||
};
|
||||
|
||||
if (!params[0]) {
|
||||
replyError('Coin name required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params[1] && !options.algorithm){
|
||||
replyError('If switch key is not provided then algorithm options must be specified');
|
||||
return;
|
||||
}
|
||||
else if (params[1] && !portalConfig.switching[params[1]]){
|
||||
replyError('Switch key not recognized: ' + params[1]);
|
||||
return;
|
||||
}
|
||||
else if (options.algorithm && !Object.keys(portalConfig.switching).filter(function(s){
|
||||
return portalConfig.switching[s].algorithm === options.algorithm;
|
||||
})[0]){
|
||||
replyError('No switching options contain the algorithm ' + options.algorithm);
|
||||
return;
|
||||
}
|
||||
|
||||
var messageCoin = params[0].toLowerCase();
|
||||
var newCoin = Object.keys(poolConfigs).filter(function(p){
|
||||
return p.toLowerCase() === messageCoin;
|
||||
})[0];
|
||||
|
||||
if (!newCoin){
|
||||
replyError('Switch message to coin that is not recognized: ' + messageCoin);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var switchNames = [];
|
||||
|
||||
if (params[1]) {
|
||||
switchNames.push(params[1]);
|
||||
}
|
||||
else{
|
||||
for (var name in portalConfig.switching){
|
||||
if (portalConfig.switching[name].enabled && portalConfig.switching[name].algorithm === options.algorithm)
|
||||
switchNames.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
switchNames.forEach(function(name){
|
||||
if (poolConfigs[newCoin].coin.algorithm !== portalConfig.switching[name].algorithm){
|
||||
replyError('Cannot switch a '
|
||||
+ portalConfig.switching[name].algorithm
|
||||
+ ' algo pool to coin ' + newCoin + ' with ' + poolConfigs[newCoin].coin.algorithm + ' algo');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(cluster.workers).forEach(function (id) {
|
||||
cluster.workers[id].send({type: 'coinswitch', coin: newCoin, switchName: name });
|
||||
});
|
||||
});
|
||||
|
||||
reply('Switch message sent to pool workers');
|
||||
|
||||
};
|
||||
|
||||
|
||||
var startRedisBlockListener = function(){
|
||||
//block notify options
|
||||
//setup block notify here and use IPC to tell appropriate pools
|
||||
|
||||
|
@ -311,7 +359,7 @@ var startRedisBlockListener = function(portalConfig){
|
|||
};
|
||||
|
||||
|
||||
var startPaymentProcessor = function(poolConfigs){
|
||||
var startPaymentProcessor = function(){
|
||||
|
||||
var enabledForAny = false;
|
||||
for (var pool in poolConfigs){
|
||||
|
@ -339,7 +387,7 @@ var startPaymentProcessor = function(poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startWebsite = function(portalConfig, poolConfigs){
|
||||
var startWebsite = function(){
|
||||
|
||||
if (!portalConfig.website.enabled) return;
|
||||
|
||||
|
@ -357,7 +405,7 @@ var startWebsite = function(portalConfig, poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startProfitSwitch = function(portalConfig, poolConfigs){
|
||||
var startProfitSwitch = function(){
|
||||
|
||||
if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){
|
||||
//logger.error('Master', 'Profit', 'Profit auto switching disabled');
|
||||
|
@ -381,18 +429,18 @@ var startProfitSwitch = function(portalConfig, poolConfigs){
|
|||
|
||||
(function init(){
|
||||
|
||||
var poolConfigs = buildPoolConfigs();
|
||||
poolConfigs = buildPoolConfigs();
|
||||
|
||||
spawnPoolWorkers(portalConfig, poolConfigs);
|
||||
spawnPoolWorkers();
|
||||
|
||||
startPaymentProcessor(poolConfigs);
|
||||
startPaymentProcessor();
|
||||
|
||||
startRedisBlockListener(portalConfig);
|
||||
startRedisBlockListener();
|
||||
|
||||
startWebsite(portalConfig, poolConfigs);
|
||||
startWebsite();
|
||||
|
||||
startProfitSwitch(portalConfig, poolConfigs);
|
||||
startProfitSwitch();
|
||||
|
||||
startCliListener(portalConfig.cliPort);
|
||||
startCliListener();
|
||||
|
||||
})();
|
||||
|
|
|
@ -18,12 +18,14 @@ var listener = module.exports = function listener(port){
|
|||
c.on('data', function (d) {
|
||||
data += d;
|
||||
if (data.slice(-1) === '\n') {
|
||||
c.end();
|
||||
var message = JSON.parse(data);
|
||||
_this.emit('command', message.command, message.params, message.options, function(message){
|
||||
c.end(message);
|
||||
});
|
||||
}
|
||||
});
|
||||
c.on('end', function () {
|
||||
var message = JSON.parse(data);
|
||||
_this.emit('command', message.command, message.params, message.options);
|
||||
|
||||
});
|
||||
}
|
||||
catch(e){
|
||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function(logger, poolConfig){
|
|||
var mposConfig = poolConfig.shareProcessing.mpos;
|
||||
var coin = poolConfig.coin.name;
|
||||
|
||||
//var connection;
|
||||
var connection;
|
||||
|
||||
|
||||
var logIdentify = 'MySQL';
|
||||
|
@ -21,30 +21,6 @@ module.exports = function(logger, poolConfig){
|
|||
database: mposConfig.database
|
||||
});
|
||||
|
||||
/*connection = mysql.createConnection({
|
||||
host: mposConfig.host,
|
||||
port: mposConfig.port,
|
||||
user: mposConfig.user,
|
||||
password: mposConfig.password,
|
||||
database: mposConfig.database
|
||||
});
|
||||
connection.connect(function(err){
|
||||
if (err)
|
||||
logger.error(logIdentify, logComponent, 'Could not connect to mysql database: ' + JSON.stringify(err))
|
||||
else{
|
||||
logger.debug(logIdentify, logComponent, 'Successful connection to MySQL database');
|
||||
}
|
||||
});
|
||||
connection.on('error', function(err){
|
||||
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
|
||||
logger.warning(logIdentify, logComponent, 'Lost connection to MySQL database, attempting reconnection...');
|
||||
connect();
|
||||
}
|
||||
else{
|
||||
logger.error(logIdentify, logComponent, 'Database error: ' + JSON.stringify(err))
|
||||
}
|
||||
});*/
|
||||
|
||||
|
||||
}
|
||||
connect();
|
||||
|
|
|
@ -50,29 +50,11 @@ module.exports = function(logger){
|
|||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||
|
||||
var switchName = message.switchName;
|
||||
if (!portalConfig.switching[switchName]) {
|
||||
logger.error(logSystem, logComponent, logSubCat, 'Switching key not recognized: ' + switchName);
|
||||
}
|
||||
|
||||
var messageCoin = message.coin.toLowerCase();
|
||||
var newCoin = Object.keys(pools).filter(function(p){
|
||||
return p.toLowerCase() === messageCoin;
|
||||
})[0];
|
||||
|
||||
if (!newCoin){
|
||||
logger.error(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin);
|
||||
break;
|
||||
}
|
||||
var newCoin = message.coin;
|
||||
|
||||
var algo = poolConfigs[newCoin].coin.algorithm;
|
||||
|
||||
if (algo !== proxySwitch[switchName].algorithm){
|
||||
logger.error(logSystem, logComponent, logSubCat, 'Cannot switch a '
|
||||
+ proxySwitch[switchName].algorithm
|
||||
+ ' algo pool to coin ' + newCoin + ' with ' + algo + ' algo');
|
||||
break;
|
||||
}
|
||||
|
||||
var newPool = pools[newCoin];
|
||||
var oldCoin = proxySwitch[switchName].currentPool;
|
||||
var oldPool = pools[oldCoin];
|
||||
|
|
|
@ -420,7 +420,11 @@ module.exports = function(logger){
|
|||
};
|
||||
this.getDaemonInfoForCoin = function(symbol, cfg, callback){
|
||||
var daemon = new Stratum.daemon.interface([cfg]);
|
||||
daemon.once('online', function(){
|
||||
daemon.on('error', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).init();
|
||||
|
||||
daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) {
|
||||
if (result[0].error != null) {
|
||||
logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0]));
|
||||
|
@ -435,16 +439,9 @@ module.exports = function(logger){
|
|||
coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9));
|
||||
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
|
||||
|
||||
coinStatus.reward = new Number(response.coinbasevalue / 100000000);
|
||||
coinStatus.reward = response.coinbasevalue / 100000000;
|
||||
callback(null);
|
||||
});
|
||||
}).once('connectionFailed', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).on('error', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).init();
|
||||
};
|
||||
|
||||
|
||||
|
@ -453,8 +450,8 @@ module.exports = function(logger){
|
|||
Object.keys(profitStatus).forEach(function(algo){
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol){
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000)));
|
||||
coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour);
|
||||
coinStatus.blocksPerMhPerHour = 86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000));
|
||||
coinStatus.coinsPerMhPerHour = coinStatus.reward * coinStatus.blocksPerMhPerHour;
|
||||
});
|
||||
});
|
||||
callback(null);
|
||||
|
@ -467,7 +464,7 @@ module.exports = function(logger){
|
|||
|
||||
var bestExchange;
|
||||
var bestCoin;
|
||||
var bestBtcPerMhPerHour = new Number(0);
|
||||
var bestBtcPerMhPerHour = 0;
|
||||
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol) {
|
||||
var coinStatus = profitStatus[algo][symbol];
|
||||
|
@ -475,7 +472,7 @@ module.exports = function(logger){
|
|||
Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){
|
||||
var exchangeData = coinStatus.exchangeInfo[exchange];
|
||||
if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour);
|
||||
var btcPerMhPerHour = exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour;
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
|
@ -485,7 +482,7 @@ module.exports = function(logger){
|
|||
logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
}
|
||||
if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc);
|
||||
var btcPerMhPerHour = (exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc;
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
|
@ -497,14 +494,21 @@ module.exports = function(logger){
|
|||
});
|
||||
});
|
||||
logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
if (portalConfig.coinSwitchListener.enabled){
|
||||
var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () {
|
||||
|
||||
|
||||
var client = net.connect(portalConfig.cliPort, function () {
|
||||
client.write(JSON.stringify({
|
||||
password: portalConfig.coinSwitchListener.password,
|
||||
coin: bestCoin
|
||||
command: 'coinswitch',
|
||||
params: [bestCoin],
|
||||
options: {algorithm: algo}
|
||||
}) + '\n');
|
||||
}).on('error', function(error){
|
||||
if (error.code === 'ECONNREFUSED')
|
||||
logger.error(logSystem, 'CLI', 'Could not connect to NOMP instance on port ' + portalConfig.cliPort);
|
||||
else
|
||||
logger.error(logSystem, 'CLI', 'Socket error ' + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -34,5 +34,4 @@ var client = net.connect(options.port || defaultPort, options.host || defaultHos
|
|||
}).on('data', function(data) {
|
||||
console.log(data.toString());
|
||||
}).on('close', function () {
|
||||
|
||||
});
|
Loading…
Reference in New Issue