diff --git a/README.md b/README.md index 6720632..eeb7214 100644 --- a/README.md +++ b/README.md @@ -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 ===== diff --git a/init.js b/init.js index 4d086ce..bf5c0c9 100644 --- a/init.js +++ b/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); + +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 }); }); }); - listener.start(); + + reply('Switch message sent to pool workers'); + }; -*/ -var startRedisBlockListener = function(portalConfig){ + +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(); })(); diff --git a/libs/cliListener.js b/libs/cliListener.js index 96744b5..efb18cf 100644 --- a/libs/cliListener.js +++ b/libs/cliListener.js @@ -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){ diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 21c378d..0a3b1c7 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -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(); diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 5c66279..2fc114d 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -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]; diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 83491a0..6e0e6c6 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -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)); + }); + }); }; diff --git a/scripts/cli.js b/scripts/cli.js index 013678c..5e1cfd6 100644 --- a/scripts/cli.js +++ b/scripts/cli.js @@ -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 () { - }); \ No newline at end of file