NOMP CLI gives replies now. Profit switching transitioned to using CLI port rather than old profitListener port.

This commit is contained in:
Matt 2014-04-28 15:04:12 -06:00
parent 52108e3b7a
commit 66d7640c9b
7 changed files with 149 additions and 135 deletions

View File

@ -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
View File

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

View File

@ -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){

View File

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

View File

@ -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];

View File

@ -420,31 +420,28 @@ module.exports = function(logger){
};
this.getDaemonInfoForCoin = function(symbol, cfg, callback){
var daemon = new Stratum.daemon.interface([cfg]);
daemon.once('online', function(){
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]));
callback(null); // fail gracefully for each coin
return;
}
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
var response = result[0].response;
// some shitcoins dont provide target, only bits, so we need to deal with both
var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits);
coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9));
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
coinStatus.reward = new Number(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){
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]));
callback(null); // fail gracefully for each coin
return;
}
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
var response = result[0].response;
// some shitcoins dont provide target, only bits, so we need to deal with both
var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits);
coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9));
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
coinStatus.reward = response.coinbasevalue / 100000000;
callback(null);
});
};
@ -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 () {
client.write(JSON.stringify({
password: portalConfig.coinSwitchListener.password,
coin: bestCoin
}) + '\n');
});
}
var client = net.connect(portalConfig.cliPort, function () {
client.write(JSON.stringify({
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));
});
});
};

View File

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