UNPKG

cryptocurrency-pool-server

Version:

An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency pool server

826 lines (727 loc) 38.1 kB
var zlib = require('zlib'); var redis = require('redis'); var async = require('async'); var os = require('os'); var algos = require('cryptocurrency-stratum-pool/lib/algoProperties.js'); var CreateRedisClient = require('./createRedisClient.js'); // redis callback Ready check failed bypass trick function rediscreateClient(redisConfig) { var client = CreateRedisClient(redisConfig); if (redisConfig.password) { client.auth(redisConfig.password); } return client; } /** * Sort object properties (only own properties will be sorted). * @param {object} obj object to sort properties * @param {string|int} sortedBy 1 - sort object properties by specific value. * @param {bool} isNumericSort true - sort object properties as numeric value, false - sort as string value. * @param {bool} reverse false - reverse sorting. * @returns {Array} array of items in [[key,value],[key,value],...] format. */ function sortProperties(obj, sortedBy, isNumericSort, reverse) { sortedBy = sortedBy || 1; // by default first key isNumericSort = isNumericSort || false; // by default text sort reverse = reverse || false; // by default no reverse var reversed = (reverse) ? -1 : 1; var sortable = []; for (var key in obj) { if (obj.hasOwnProperty(key)) { sortable.push([key, obj[key]]); } } if (isNumericSort) sortable.sort(function (a, b) { return reversed * (a[1][sortedBy] - b[1][sortedBy]); }); else sortable.sort(function (a, b) { var x = a[1][sortedBy].toLowerCase(), y = b[1][sortedBy].toLowerCase(); return x < y ? reversed * -1 : x > y ? reversed : 0; }); return sortable; // array in format [ [ key1, val1 ], [ key2, val2 ], ... ] } module.exports = function(logger, portalConfig, poolConfigs){ var _this = this; var logSystem = 'Stats'; var redisClients = []; var redisStats; this.statHistory = []; this.statPoolHistory = []; this.stats = {}; this.statsString = ''; setupStatsRedis(); gatherStatHistory(); var canDoStats = true; Object.keys(poolConfigs).forEach(function(coin){ if (!canDoStats) return; var poolConfig = poolConfigs[coin]; var redisConfig = poolConfig.redis; for (var i = 0; i < redisClients.length; i++){ var client = redisClients[i]; if ((client.client.port === redisConfig.port && client.client.host === redisConfig.host) || (client.client.path !== null && client.client.path === redisConfig.socket)){ client.coins.push(coin); return; } } redisClients.push({ coins: [coin], client: rediscreateClient(redisConfig) }); }); function setupStatsRedis(){ redisStats = CreateRedisClient(portalConfig.redis); redisStats.on('error', function(err){ logger.error(logSystem, 'Historics', 'Redis for stats had an error ' + JSON.stringify(err)); redisStats.auth(portalConfig.redis.password); }); } this.getBlocks = function (cback) { var allBlocks = {}; async.each(_this.stats.pools, function(pool, pcb) { if (_this.stats.pools[pool.name].pending && _this.stats.pools[pool.name].pending.blocks) for (var i=0; i<_this.stats.pools[pool.name].pending.blocks.length; i++) allBlocks[pool.name+"-"+_this.stats.pools[pool.name].pending.blocks[i].split(':')[2]] = _this.stats.pools[pool.name].pending.blocks[i]; if (_this.stats.pools[pool.name].confirmed && _this.stats.pools[pool.name].confirmed.blocks) for (var i=0; i<_this.stats.pools[pool.name].confirmed.blocks.length; i++) allBlocks[pool.name+"-"+_this.stats.pools[pool.name].confirmed.blocks[i].split(':')[2]] = _this.stats.pools[pool.name].confirmed.blocks[i]; pcb(); }, function(err) { cback(allBlocks); }); } function gatherStatHistory(){ var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0).toString(); redisStats.zrangebyscore(['statHistory', retentionTime, '+inf'], function(err, replies){ if (err) { logger.error(logSystem, 'Historics', 'Error when trying to grab historical stats ' + JSON.stringify(err)); return; } for (var i = 0; i < replies.length; i++){ _this.statHistory.push(JSON.parse(replies[i])); } _this.statHistory = _this.statHistory.sort(function(a, b){ return a.time - b.time; }); _this.statHistory.forEach(function(stats){ addStatPoolHistory(stats); }); }); } function getWorkerStats(address) { address = address.split(".")[0]; if (address.length === 34) { for (var h in statHistory) { for(var pool in statHistory[h].pools) { statHistory[h].pools[pool].workers.sort(sortWorkersByHashrate); for(var w in statHistory[h].pools[pool].workers){ if (w.startsWith(address)) { if (history[w] == null) { history[w] = []; } if (workers[w] == null && stats.pools[pool].workers[w] != null) { workers[w] = stats.pools[pool].workers[w]; } if (statHistory[h].pools[pool].workers[w].hashrate) { history[w].push({time: statHistory[h].time, hashrate:statHistory[h].pools[pool].workers[w].hashrate}); } } } } } return JSON.stringify({"workers": workers, "history": history}); } return null; } function addStatPoolHistory(stats){ var data = { time: stats.time, pools: {} }; for (var pool in stats.pools){ data.pools[pool] = { hashrate: stats.pools[pool].hashrate, workerCount: stats.pools[pool].workerCount, blocks: stats.pools[pool].blocks } } _this.statPoolHistory.push(data); } var magnitude = 100000000; var coinPrecision = magnitude.toString().length - 1; function roundTo(n, digits) { if (digits === undefined) { digits = 0; } var multiplicator = Math.pow(10, digits); n = parseFloat((n * multiplicator).toFixed(11)); var test =(Math.round(n) / multiplicator); return +(test.toFixed(digits)); } var satoshisToCoins = function(satoshis){ return roundTo((satoshis / magnitude), coinPrecision); }; var coinsToSatoshies = function(coins){ return Math.round(coins * magnitude); }; function coinsRound(number) { return roundTo(number, coinPrecision); } function readableSeconds(t) { var seconds = Math.round(t); var minutes = Math.floor(seconds/60); var hours = Math.floor(minutes/60); var days = Math.floor(hours/24); hours = hours-(days*24); minutes = minutes-(days*24*60)-(hours*60); seconds = seconds-(days*24*60*60)-(hours*60*60)-(minutes*60); if (days > 0) { return (days + "d " + hours + "h " + minutes + "m " + seconds + "s"); } if (hours > 0) { return (hours + "h " + minutes + "m " + seconds + "s"); } if (minutes > 0) {return (minutes + "m " + seconds + "s"); } return (seconds + "s"); } this.getCoins = function(cback){ _this.stats.coins = redisClients[0].coins; cback(); }; this.getPayout = function(address, cback){ async.waterfall([ function(callback){ _this.getBalanceByAddress(address, function(){ callback(null, 'test'); }); } ], function(err, total){ cback(coinsRound(total).toFixed(8)); }); }; this.getTotalSharesByAddress = function(address, cback) { var a = address.split(".")[0]; var client = redisClients[0].client, coins = redisClients[0].coins, shares = []; var pindex = parseInt(0); var totalShares = parseFloat(0); async.each(_this.stats.pools, function(pool, pcb) { pindex++; var coin = String(_this.stats.pools[pool.name].name); client.hscan(coin + ':shares:roundCurrent', 0, "match", a+"*", "count", 1000, function(error, result) { if (error) { pcb(error); return; } var workerName=""; var shares = 0; for (var i in result[1]) { if (Math.abs(i % 2) != 1) { workerName = String(result[1][i]); } else { shares += parseFloat(result[1][i]); } } if (shares>0) { totalShares = shares; } pcb(); }); }, function(err) { if (err) { cback(0); return; } if (totalShares > 0 || (pindex >= Object.keys(_this.stats.pools).length)) { cback(totalShares); return; } }); }; this.getBalanceByAddress = function(address, cback){ var a = address.split(".")[0]; var client = redisClients[0].client, coins = redisClients[0].coins, balances = []; var totalHeld = parseFloat(0); var totalPaid = parseFloat(0); var totalImmature = parseFloat(0); async.each(_this.stats.pools, function(pool, pcb) { var coin = String(_this.stats.pools[pool.name].name); // get all immature balances from address client.hscan(coin + ':immature', 0, "match", a+"*", "count", 10000, function(error, pends) { // get all balances from address client.hscan(coin + ':balances', 0, "match", a+"*", "count", 10000, function(error, bals) { // get all payouts from address client.hscan(coin + ':payouts', 0, "match", a+"*", "count", 10000, function(error, pays) { var workerName = ""; var balAmount = 0; var paidAmount = 0; var pendingAmount = 0; var workers = {}; for (var i in pays[1]) { if (Math.abs(i % 2) != 1) { workerName = String(pays[1][i]); workers[workerName] = (workers[workerName] || {}); } else { paidAmount = parseFloat(pays[1][i]); workers[workerName].paid = coinsRound(paidAmount); totalPaid += paidAmount; } } for (var b in bals[1]) { if (Math.abs(b % 2) != 1) { workerName = String(bals[1][b]); workers[workerName] = (workers[workerName] || {}); } else { balAmount = parseFloat(bals[1][b]); workers[workerName].balance = coinsRound(balAmount); totalHeld += balAmount; } } for (var b in pends[1]) { if (Math.abs(b % 2) != 1) { workerName = String(pends[1][b]); workers[workerName] = (workers[workerName] || {}); } else { pendingAmount = parseFloat(pends[1][b]); workers[workerName].immature = coinsRound(pendingAmount); totalImmature += pendingAmount; } } for (var w in workers) { balances.push({ worker:String(w), balance:workers[w].balance, paid:workers[w].paid, immature:workers[w].immature }); } pcb(); }); }); }); }, function(err) { if (err) { callback("There was an error getting balances"); return; } _this.stats.balances = balances; _this.stats.address = address; cback({totalHeld:coinsRound(totalHeld), totalPaid:coinsRound(totalPaid), totalImmature:satoshisToCoins(totalImmature), balances}); }); }; this.getGlobalStats = function(callback){ var statGatherTime = Date.now() / 1000 | 0; var allCoinStats = {}; async.each(redisClients, function(client, callback){ var windowTime = (((Date.now() / 1000) - portalConfig.website.stats.hashrateWindow) | 0).toString(); var redisCommands = []; var redisCommandTemplates = [ ['zremrangebyscore', ':hashrate', '-inf', '(' + windowTime], ['zrangebyscore', ':hashrate', windowTime, '+inf'], ['hgetall', ':stats'], ['scard', ':blocksPending'], ['scard', ':blocksConfirmed'], ['scard', ':blocksKicked'], ['smembers', ':blocksPending'], ['smembers', ':blocksConfirmed'], ['hgetall', ':shares:roundCurrent'], ['hgetall', ':blocksPendingConfirms'], ['zrange', ':payments', -100, -1] ]; var commandsPerCoin = redisCommandTemplates.length; client.coins.map(function(coin){ redisCommandTemplates.map(function(t){ var clonedTemplates = t.slice(0); clonedTemplates[1] = coin + clonedTemplates[1]; redisCommands.push(clonedTemplates); }); }); client.client.multi(redisCommands).exec(function(err, replies){ if (err){ logger.error(logSystem, 'Global', 'error with getting global stats ' + JSON.stringify(err)); callback(err); } else{ for(var i = 0; i < replies.length; i += commandsPerCoin){ var coinName = client.coins[i / commandsPerCoin | 0]; var coinStats = { name: coinName, symbol: poolConfigs[coinName].coin.symbol.toUpperCase(), algorithm: poolConfigs[coinName].coin.algorithm, poolFees: poolConfigs[coinName].rewardRecipients, minimumPayment: poolConfigs[coinName].paymentProcessing.minimumPayment, hashrates: replies[i + 1], poolStats: { validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0, validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0, invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0, totalPaid: replies[i + 2] ? (replies[i + 2].totalPaid || 0) : 0, networkBlocks: replies[i + 2] ? (replies[i + 2].networkBlocks || 0) : 0, networkSols: replies[i + 2] ? (replies[i + 2].networkSols || 0) : 0, networkSolsString: !poolConfigs[coinName].coin.isZCashProtocol ? _this.getReadableHashRateString(replies[i + 2] ? (replies[i + 2].networkSols || 0) : 0) : getZCashReadableNetworkHashRateString(replies[i + 2] ? (replies[i + 2].networkSols || 0) : 0), networkDiff: replies[i + 2] ? (replies[i + 2].networkDiff || 0) : 0, networkConnections: replies[i + 2] ? (replies[i + 2].networkConnections || 0) : 0, networkVersion: replies[i + 2] ? (replies[i + 2].networkSubVersion || 0) : 0, networkProtocolVersion: replies[i + 2] ? (replies[i + 2].networkProtocolVersion || 0) : 0 }, /* block stat counts */ blocks: { pending: replies[i + 3], confirmed: replies[i + 4], orphaned: replies[i + 5] }, /* show all pending blocks */ pending: { blocks: replies[i + 6].sort(sortBlocks), confirms: (replies[i + 9] || {}) }, /* show last 50 found blocks */ confirmed: { blocks: replies[i + 7].sort(sortBlocks).slice(0,50) }, payments: [], currentRoundShares: (replies[i + 8] || {}), maxRoundTime: 0, shareCount: 0 }; for(var j = replies[i + 10].length; j > 0; j--){ var jsonObj; try { jsonObj = JSON.parse(replies[i + 10][j-1]); } catch(e) { jsonObj = null; } if (jsonObj !== null) { coinStats.payments.push(jsonObj); } } allCoinStats[coinStats.name] = (coinStats); } allCoinStats = sortPoolsByName(allCoinStats); callback(); } }); }, function(err){ if (err){ logger.error(logSystem, 'Global', 'error getting all stats' + JSON.stringify(err)); callback(); return; } var portalStats = { time: statGatherTime, global:{ workers: 0, hashrate: 0 }, algos: {}, pools: allCoinStats }; Object.keys(allCoinStats).forEach(function(coin){ var coinStats = allCoinStats[coin]; coinStats.workers = {}; coinStats.miners = {}; coinStats.shares = 0; coinStats.soloshares = 0; coinStats.hashrates.forEach(function(ins){ var parts = ins.split(':'); var workerShares = parseFloat(parts[0]); var miner = parts[1].split('.')[0]; var worker = parts[1]; var diff = Math.round(parts[0] * 8192); var lastShare = parseInt(parts[2]); var mode = parts[3] || 'PPS'; if (workerShares > 0) { coinStats[mode !== 'SOLO' ? 'shares' : 'soloshares'] += workerShares; if (worker in coinStats.workers) { coinStats.workers[worker].mode = mode; coinStats.workers[worker][mode !== 'SOLO' ? 'shares' : 'soloshares'] += workerShares; coinStats.workers[worker].diff = diff; if (lastShare > coinStats.workers[worker].lastShare) { coinStats.workers[worker].lastShare = lastShare; } } else { coinStats.workers[worker] = { lastShare: lastShare, name: worker, diff: diff, mode: mode, shares: mode !== 'SOLO' ? workerShares : 0, soloshares: mode === 'SOLO' ? workerShares : 0, invalidshares: 0, invalidsoloshares: 0, currRoundShares: 0, hashrate: null, hashrateString: null, solohashrate: null, solohashrateString: null, luckDays: null, luckHours: null, paid: 0, balance: 0 }; } if (miner in coinStats.miners) { coinStats.miners[miner][mode !== 'SOLO' ? 'shares' : 'soloshares'] += workerShares; if (lastShare > coinStats.miners[miner].lastShare) { coinStats.miners[miner].lastShare = lastShare; } } else { coinStats.miners[miner] = { lastShare: 0, name: miner, shares: mode !== 'SOLO' ? workerShares : 0, soloshares: mode === 'SOLO' ? workerShares : 0, invalidshares: 0, invalidsoloshares: 0, currRoundShares: 0, hashrate: null, hashrateString: null, solohashrate: null, solohashrateString: null, luckDays: null, luckHours: null }; } } else { if (worker in coinStats.workers) { coinStats.workers[worker].mode = mode; coinStats.workers[worker][mode !== 'SOLO' ? 'invalidshares' : 'invalidsoloshares'] -= workerShares; // workerShares is negative number! coinStats.workers[worker].diff = diff; } else { coinStats.workers[worker] = { lastShare: 0, name: worker, diff: diff, mode: mode, shares: 0, soloshares: 0, invalidshares: mode !== 'SOLO' ? -workerShares : 0, invalidsoloshares: mode === 'SOLO' ? -workerShares : 0, currRoundShares: 0, hashrate: null, hashrateString: null, solohashrate: null, solohashrateString: null, luckDays: null, luckHours: null, paid: 0, balance: 0 }; } if (miner in coinStats.miners) { coinStats.miners[miner][mode !== 'SOLO' ? 'invalidshares' : 'invalidsoloshares'] -= workerShares; // workerShares is negative number! } else { coinStats.miners[miner] = { lastShare: 0, name: miner, shares: 0, soloshares: 0, invalidshares: mode !== 'SOLO' ? -workerShares : 0, invalidsoloshares: mode === 'SOLO' ? -workerShares : 0, currRoundShares: 0, hashrate: null, hashrateString: null, solohashrate: null, solohashrateString: null, luckDays: null, luckHours: null }; } } }); var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; coinStats.hashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(coinStats.hashrate) : _this.getZCashReadableHashRateString(coinStats.hashrate); coinStats.solohashrate = shareMultiplier * coinStats.soloshares / portalConfig.website.stats.hashrateWindow; coinStats.solohashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(coinStats.solohashrate) : _this.getZCashReadableHashRateString(coinStats.solohashrate); var _blocktime = 160; var _networkHashRate = parseFloat(coinStats.poolStats.networkSols) * (!poolConfigs[coin].coin.isZCashProtocol ? 1.0 : 1.2); var _myHashRate = !poolConfigs[coin].coin.isZCashProtocol ? coinStats.hashrate : (coinStats.hashrate / 1000000) * 2; coinStats.luckDays = ((_networkHashRate / _myHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.luckHours = ((_networkHashRate / _myHashRate * _blocktime) / (60 * 60)).toFixed(3); var _mySoloHashRate = !poolConfigs[coin].coin.isZCashProtocol ? coinStats.solohashrate : (coinStats.solohashrate / 1000000) * 2; coinStats.soloLuckDays = ((_networkHashRate / _mySoloHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.soloLuckHours = ((_networkHashRate / _mySoloHashRate * _blocktime) / (60 * 60)).toFixed(3); coinStats.minerCount = Object.keys(coinStats.miners).length; coinStats.workerCount = Object.keys(coinStats.workers).length; portalStats.global.workers += coinStats.workerCount; /* algorithm specific global stats */ var algo = coinStats.algorithm; if (!portalStats.algos.hasOwnProperty(algo)){ portalStats.algos[algo] = { workers: 0, hashrate: 0, hashrateString: null, solohashrate: 0, solohashrateString: null }; } portalStats.algos[algo].hashrate += coinStats.hashrate; portalStats.algos[algo].solohashrate += coinStats.solohashrate; portalStats.algos[algo].workers += Object.keys(coinStats.workers).length; var _shareTotal = parseFloat(0); for (var worker in coinStats.currentRoundShares) { var miner = worker.split(".")[0]; if (miner in coinStats.miners) { coinStats.miners[miner].currRoundShares += parseFloat(coinStats.currentRoundShares[worker]); } if (worker in coinStats.workers) { coinStats.workers[worker].currRoundShares += parseFloat(coinStats.currentRoundShares[worker]); } _shareTotal += parseFloat(coinStats.currentRoundShares[worker]); } coinStats.shareCount = _shareTotal; for (var worker in coinStats.workers) { var _workerRate = shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow; var _workerSoloRate = shareMultiplier * coinStats.workers[worker].soloshares / portalConfig.website.stats.hashrateWindow; var _wHashRate = !poolConfigs[coin].coin.isZCashProtocol ? _workerRate : (_workerRate / 1000000) * 2; var _wSoloHashRate = !poolConfigs[coin].coin.isZCashProtocol ? _workerSoloRate : (_workerSoloRate / 1000000) * 2; coinStats.workers[worker].luckDays = ((_networkHashRate / _wHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.workers[worker].luckHours = ((_networkHashRate / _wHashRate * _blocktime) / (60 * 60)).toFixed(3); coinStats.workers[worker].hashrate = _workerRate; coinStats.workers[worker].hashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(_workerRate) : _this.getZCashReadableHashRateString(_workerRate); coinStats.workers[worker].soloLuckDays = ((_networkHashRate / _wSoloHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.workers[worker].soloLuckHours = ((_networkHashRate / _wSoloHashRate * _blocktime) / (60 * 60)).toFixed(3); coinStats.workers[worker].solohashrate = _workerSoloRate; coinStats.workers[worker].solohashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(_workerSoloRate) : _this.getZCashReadableHashRateString(_workerSoloRate); } for (var miner in coinStats.miners) { var _workerRate = shareMultiplier * coinStats.miners[miner].shares / portalConfig.website.stats.hashrateWindow; var _workerSoloRate = shareMultiplier * coinStats.miners[miner].shares / portalConfig.website.stats.hashrateWindow; var _wHashRate = !poolConfigs[coin].coin.isZCashProtocol ? _workerRate : (_workerRate / 1000000) * 2; var _wSoloHashRate = !poolConfigs[coin].coin.isZCashProtocol ? _workerSoloRate : (_workerSoloRate / 1000000) * 2; coinStats.miners[miner].luckDays = ((_networkHashRate / _wHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.miners[miner].luckHours = ((_networkHashRate / _wHashRate * _blocktime) / (60 * 60)).toFixed(3); coinStats.miners[miner].hashrate = _workerRate; coinStats.miners[miner].hashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(_workerRate) : _this.getZCashReadableHashRateString(_workerRate); coinStats.miners[miner].soloLuckDays = ((_networkHashRate / _wSoloHashRate * _blocktime) / (24 * 60 * 60)).toFixed(3); coinStats.miners[miner].soloLuckHours = ((_networkHashRate / _wSoloHashRate * _blocktime) / (60 * 60)).toFixed(3); coinStats.miners[miner].solohashrate = _workerSoloRate; coinStats.miners[miner].solohashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(_workerSoloRate) : _this.getZCashReadableHashRateString(_workerSoloRate); } coinStats.workers = sortWorkersByName(coinStats.workers); coinStats.miners = sortMinersByHashrate(coinStats.miners); delete coinStats.hashrates; delete coinStats.shares; coinStats.hashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(coinStats.hashrate) : _this.getZCashReadableHashRateString(coinStats.hashrate); coinStats.solohashrateString = !poolConfigs[coin].coin.isZCashProtocol ? _this.getReadableHashRateString(coinStats.solohashrate) : _this.getZCashReadableHashRateString(coinStats.solohashrate); }); Object.keys(portalStats.algos).forEach(function(algo){ var algoStats = portalStats.algos[algo]; if (algo === 'verushash' || algo === 'equihash') { algoStats.hashrateString = _this.getZCashReadableHashRateString(algoStats.hashrate); algoStats.solohashrateString = _this.getZCashReadableHashRateString(algoStats.solohashrate); } else { algoStats.hashrateString = _this.getReadableHashRateString(algoStats.hashrate); algoStats.solohashrateString = _this.getReadableHashRateString(algoStats.solohashrate); } }); _this.stats = portalStats; var saveStats = JSON.parse(JSON.stringify(portalStats)); Object.keys(saveStats.pools).forEach(function(pool){ delete saveStats.pools[pool].pending; delete saveStats.pools[pool].confirmed; delete saveStats.pools[pool].currentRoundShares; delete saveStats.pools[pool].currentRoundTimes; delete saveStats.pools[pool].payments; delete saveStats.pools[pool].miners; }); _this.statsString = JSON.stringify(saveStats); _this.statHistory.push(saveStats); addStatPoolHistory(portalStats); var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0); for (var i = 0; i < _this.statHistory.length; i++){ if (retentionTime < _this.statHistory[i].time){ if (i > 0) { _this.statHistory = _this.statHistory.slice(i); _this.statPoolHistory = _this.statPoolHistory.slice(i); } break; } } redisStats.multi([ ['zadd', 'statHistory', statGatherTime, _this.statsString], ['zremrangebyscore', 'statHistory', '-inf', '(' + retentionTime] ]).exec(function(err, replies){ if (err) logger.error(logSystem, 'Historics', 'Error adding stats to historics ' + JSON.stringify(err)); }); callback(); }); }; function sortPoolsByName(objects) { var newObject = {}; var sortedArray = sortProperties(objects, 'name', false, false); for (var i = 0; i < sortedArray.length; i++) { var key = sortedArray[i][0]; var value = sortedArray[i][1]; newObject[key] = value; } return newObject; } function sortBlocks(a, b) { var as = parseInt(a.split(":")[2]); var bs = parseInt(b.split(":")[2]); if (as > bs) return -1; if (as < bs) return 1; return 0; } function sortWorkersByName(objects) { var newObject = {}; var sortedArray = sortProperties(objects, 'name', false, false); for (var i = 0; i < sortedArray.length; i++) { var key = sortedArray[i][0]; var value = sortedArray[i][1]; newObject[key] = value; } return newObject; } function sortMinersByHashrate(objects) { var newObject = {}; var sortedArray = sortProperties(objects, 'shares', true, true); for (var i = 0; i < sortedArray.length; i++) { var key = sortedArray[i][0]; var value = sortedArray[i][1]; newObject[key] = value; } return newObject; } function sortWorkersByHashrate(a, b) { if (a.hashrate === b.hashrate) { return 0; } else { return (a.hashrate < b.hashrate) ? -1 : 1; } } this.getReadableHashRateString = function(hashrate){ var i = -1; var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH', ' EH', ' ZH', ' YH' ]; do { hashrate = hashrate / 1024; i++; } while (hashrate > 1024); return hashrate.toFixed(2) + byteUnits[i]; }; this.getZCashReadableHashRateString = function(hashrate){ hashrate = (hashrate * 2); if (hashrate < 1000000) { return (Math.round(hashrate / 1000) / 1000 ).toFixed(2)+' Sol/s'; } var byteUnits = [ ' Sol/s', ' KSol/s', ' MSol/s', ' GSol/s', ' TSol/s', ' PSol/s', ' ESol/s', ' ZSol/s', ' YSol/s' ]; var i = Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1); hashrate = (hashrate/1000) / Math.pow(1000, i + 1); return hashrate.toFixed(2) + byteUnits[i]; }; function getZCashReadableNetworkHashRateString(hashrate) { hashrate = (hashrate * 1000000); if (hashrate < 1000000) return '0 Sol'; var byteUnits = [ ' Sol/s', ' KSol/s', ' MSol/s', ' GSol/s', ' TSol/s', ' PSol/s', ' ESol/s', ' ZSol/s', ' YSol/s' ]; var i = Math.floor((Math.log(hashrate/1000) / Math.log(1000)) - 1); hashrate = (hashrate/1000) / Math.pow(1000, i + 1); return hashrate.toFixed(2) + byteUnits[i]; } };