mirror of https://github.com/BTCPrivate/z-nomp.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
fe72b6e533
8
init.js
8
init.js
|
@ -265,6 +265,7 @@ var spawnPoolWorkers = function(){
|
||||||
if (!_lastShareTimes[workerAddress] || !_lastStartTimes[workerAddress]) {
|
if (!_lastShareTimes[workerAddress] || !_lastStartTimes[workerAddress]) {
|
||||||
_lastShareTimes[workerAddress] = now;
|
_lastShareTimes[workerAddress] = now;
|
||||||
_lastStartTimes[workerAddress] = now;
|
_lastStartTimes[workerAddress] = now;
|
||||||
|
logger.debug('PPLNT', msg.coin, 'Thread '+msg.thread, workerAddress+' joined current round.');
|
||||||
}
|
}
|
||||||
if (_lastShareTimes[workerAddress] != null && _lastShareTimes[workerAddress] > 0) {
|
if (_lastShareTimes[workerAddress] != null && _lastShareTimes[workerAddress] > 0) {
|
||||||
lastShareTime = _lastShareTimes[workerAddress];
|
lastShareTime = _lastShareTimes[workerAddress];
|
||||||
|
@ -273,13 +274,13 @@ var spawnPoolWorkers = function(){
|
||||||
|
|
||||||
var redisCommands = [];
|
var redisCommands = [];
|
||||||
|
|
||||||
// if its been less than 10 minutes since last share was submitted
|
// if its been less than 15 minutes since last share was submitted
|
||||||
var timeChangeSec = roundTo(Math.max(now - lastShareTime, 0) / 1000, 4);
|
var timeChangeSec = roundTo(Math.max(now - lastShareTime, 0) / 1000, 4);
|
||||||
var timeChangeTotal = roundTo(Math.max(now - lastStartTime, 0) / 1000, 4);
|
var timeChangeTotal = roundTo(Math.max(now - lastStartTime, 0) / 1000, 4);
|
||||||
if (timeChangeSec < 600) {
|
if (timeChangeSec < 900) {
|
||||||
// loyal miner keeps mining :)
|
// loyal miner keeps mining :)
|
||||||
redisCommands.push(['hincrbyfloat', msg.coin + ':shares:timesCurrent', workerAddress, timeChangeSec]);
|
redisCommands.push(['hincrbyfloat', msg.coin + ':shares:timesCurrent', workerAddress, timeChangeSec]);
|
||||||
logger.debug('PPLNT', msg.coin, 'Thread '+msg.thread, workerAddress+':{totalTimeSec:'+timeChangeTotal+', timeChangeSec:'+timeChangeSec+'}');
|
//logger.debug('PPLNT', msg.coin, 'Thread '+msg.thread, workerAddress+':{totalTimeSec:'+timeChangeTotal+', timeChangeSec:'+timeChangeSec+'}');
|
||||||
connection.multi(redisCommands).exec(function(err, replies){
|
connection.multi(redisCommands).exec(function(err, replies){
|
||||||
if (err)
|
if (err)
|
||||||
logger.error('PPLNT', msg.coin, 'Thread '+msg.thread, 'Error with time share processor call to redis ' + JSON.stringify(err));
|
logger.error('PPLNT', msg.coin, 'Thread '+msg.thread, 'Error with time share processor call to redis ' + JSON.stringify(err));
|
||||||
|
@ -287,6 +288,7 @@ var spawnPoolWorkers = function(){
|
||||||
} else {
|
} else {
|
||||||
// they just re-joined the pool
|
// they just re-joined the pool
|
||||||
_lastStartTimes[workerAddress] = now;
|
_lastStartTimes[workerAddress] = now;
|
||||||
|
logger.debug('PPLNT', msg.coin, 'Thread '+msg.thread, workerAddress+' re-joined current round.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// track last time share
|
// track last time share
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var request = require('request');
|
||||||
|
|
||||||
var redis = require('redis');
|
var redis = require('redis');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -50,17 +51,24 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
var logComponent = coin;
|
var logComponent = coin;
|
||||||
var opidCount = 0;
|
var opidCount = 0;
|
||||||
|
|
||||||
var minConfShield = 3;
|
// zcash team recommends 10 confirmations for safety from orphaned blocks
|
||||||
var minConfPayout = 3;
|
var minConfShield = Math.max((processingConfig.minConf || 10), 3);
|
||||||
|
var minConfPayout = Math.max((processingConfig.minConf || 10), 3);
|
||||||
|
|
||||||
var maxBlocksPerPayment = processingConfig.maxBlocksPerPayment || 3;
|
var maxBlocksPerPayment = processingConfig.maxBlocksPerPayment || 3;
|
||||||
|
|
||||||
|
// pplnt - pay per last N time shares
|
||||||
|
var pplntEnabled = processingConfig.paymentMode === "pplnt" || false;
|
||||||
|
var pplntTimeQualify = processingConfig.pplnt || 0.51; // 51%
|
||||||
|
|
||||||
|
var getMarketStats = poolOptions.coin.getMarketStats === true;
|
||||||
var requireShielding = poolOptions.coin.requireShielding === true;
|
var requireShielding = poolOptions.coin.requireShielding === true;
|
||||||
var fee = parseFloat(poolOptions.coin.txfee) || parseFloat(0.0004);
|
var fee = parseFloat(poolOptions.coin.txfee) || parseFloat(0.0004);
|
||||||
|
|
||||||
logger.debug(logSystem, logComponent, logComponent + ' requireShielding: ' + requireShielding);
|
logger.debug(logSystem, logComponent, logComponent + ' requireShielding: ' + requireShielding);
|
||||||
logger.debug(logSystem, logComponent, logComponent + ' payments txfee reserve: ' + fee);
|
logger.debug(logSystem, logComponent, logComponent + ' payments txfee reserve: ' + fee);
|
||||||
logger.debug(logSystem, logComponent, logComponent + ' maxBlocksPerPayment: ' + maxBlocksPerPayment);
|
logger.debug(logSystem, logComponent, logComponent + ' maxBlocksPerPayment: ' + maxBlocksPerPayment);
|
||||||
|
logger.debug(logSystem, logComponent, logComponent + ' PPLNT: ' + pplntEnabled + ', time period: '+pplntTimeQualify);
|
||||||
|
|
||||||
var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){
|
var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){
|
||||||
logger[severity](logSystem, logComponent, message);
|
logger[severity](logSystem, logComponent, message);
|
||||||
|
@ -236,7 +244,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var amount = balanceRound((tBalance - 10000) / magnitude);
|
var amount = satoshisToCoins(tBalance - 10000);
|
||||||
var params = [poolOptions.address, [{'address': poolOptions.zAddress, 'amount': amount}]];
|
var params = [poolOptions.address, [{'address': poolOptions.zAddress, 'amount': amount}]];
|
||||||
daemon.cmd('z_sendmany', params,
|
daemon.cmd('z_sendmany', params,
|
||||||
function (result) {
|
function (result) {
|
||||||
|
@ -269,8 +277,8 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var amount = balanceRound((zBalance - 10000) / magnitude);
|
var amount = satoshisToCoins(zBalance - 10000);
|
||||||
// no more than 100 ZEC at a time
|
// unshield no more than 100 ZEC at a time
|
||||||
if (amount > 100.0)
|
if (amount > 100.0)
|
||||||
amount = 100.0;
|
amount = 100.0;
|
||||||
|
|
||||||
|
@ -294,6 +302,36 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO, this needs to be moved out of payments processor
|
||||||
|
function cacheMarketStats() {
|
||||||
|
var marketStatsUpdate = [];
|
||||||
|
var coin = logComponent.replace('_testnet', '');
|
||||||
|
request('https://api.coinmarketcap.com/v1/ticker/'+coin+'/', function (error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
logger.error(logSystem, logComponent, 'Error getting coin market stats from CoinMarketCap ' + JSON.stringify(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response && response.statusCode) {
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
if (body) {
|
||||||
|
var data = JSON.parse(body);
|
||||||
|
if (data.length > 0) {
|
||||||
|
marketStatsUpdate.push(['hset', coin + ':stats', 'coinmarketcap', JSON.stringify(data)]);
|
||||||
|
redisClient.multi(marketStatsUpdate).exec(function(err, results){
|
||||||
|
if (err){
|
||||||
|
logger.error(logSystem, logComponent, 'Error update coin market stats to redis ' + JSON.stringify(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(logSystem, logComponent, 'Error returned from coinmarketcap ' + JSON.stringify(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// TODO, this needs to be moved out of payments processor
|
// TODO, this needs to be moved out of payments processor
|
||||||
function cacheNetworkStats () {
|
function cacheNetworkStats () {
|
||||||
var params = null;
|
var params = null;
|
||||||
|
@ -345,39 +383,45 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
}
|
}
|
||||||
|
|
||||||
// run coinbase coin transfers every x minutes
|
// run coinbase coin transfers every x minutes
|
||||||
var intervalState = 0; // do not send ZtoT and TtoZ and same time, this results in operation failed!
|
var shieldIntervalState = 0; // do not send ZtoT and TtoZ and same time, this results in operation failed!
|
||||||
var interval = poolOptions.walletInterval * 60 * 1000; // run every x minutes
|
var shielding_interval = poolOptions.walletInterval * 60 * 1000; // run every x minutes
|
||||||
setInterval(function() {
|
|
||||||
// shielding not required for some equihash coins
|
// shielding not required for some equihash coins
|
||||||
if (requireShielding === true) {
|
if (requireShielding === true) {
|
||||||
intervalState++;
|
var shieldInterval = setInterval(function() {
|
||||||
switch (intervalState) {
|
shieldIntervalState++;
|
||||||
|
switch (shieldIntervalState) {
|
||||||
case 1:
|
case 1:
|
||||||
listUnspent(poolOptions.address, null, minConfShield, false, sendTToZ);
|
listUnspent(poolOptions.address, null, minConfShield, false, sendTToZ);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
listUnspentZ(poolOptions.zAddress, minConfShield, false, sendZToT);
|
listUnspentZ(poolOptions.zAddress, minConfShield, false, sendZToT);
|
||||||
intervalState = 0;
|
shieldIntervalState = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}, shielding_interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stats caching every 58 seconds
|
||||||
|
var stats_interval = 58 * 1000;
|
||||||
|
var statsInterval = setInterval(function() {
|
||||||
// update network stats using coin daemon
|
// update network stats using coin daemon
|
||||||
cacheNetworkStats();
|
cacheNetworkStats();
|
||||||
}, interval);
|
// update market stats using coinmarketcap
|
||||||
|
if (getMarketStats === true) {
|
||||||
|
cacheMarketStats();
|
||||||
|
}
|
||||||
|
}, stats_interval);
|
||||||
|
|
||||||
// check operation statuses every x seconds
|
// check operation statuses every 57 seconds
|
||||||
var opid_interval = poolOptions.walletInterval * 1000;
|
var opid_interval = 57 * 1000;
|
||||||
// shielding not required for some equihash coins
|
// shielding not required for some equihash coins
|
||||||
if (requireShielding === true) {
|
if (requireShielding === true) {
|
||||||
setInterval(function(){
|
var checkOpids = function() {
|
||||||
var checkOpIdSuccessAndGetResult = function(ops) {
|
var checkOpIdSuccessAndGetResult = function(ops) {
|
||||||
|
var batchRPC = [];
|
||||||
ops.forEach(function(op, i){
|
ops.forEach(function(op, i){
|
||||||
if (op.status == "success" || op.status == "failed") {
|
if (op.status == "success" || op.status == "failed") {
|
||||||
daemon.cmd('z_getoperationresult', [[op.id]], function (result) {
|
batchRPC.push(['z_getoperationresult', [[op.id]]]);
|
||||||
if (result.error) {
|
|
||||||
logger.warning(logSystem, logComponent, 'Unable to get payment operation id result ' + JSON.stringify(result));
|
|
||||||
}
|
|
||||||
if (result.response) {
|
|
||||||
if (opidCount > 0) {
|
if (opidCount > 0) {
|
||||||
opidCount = 0;
|
opidCount = 0;
|
||||||
}
|
}
|
||||||
|
@ -390,8 +434,6 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
} else {
|
} else {
|
||||||
logger.special(logSystem, logComponent, 'Shielding operation success ' + op.id + ' txid: ' + op.result.txid);
|
logger.special(logSystem, logComponent, 'Shielding operation success ' + op.id + ' txid: ' + op.result.txid);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, true, true);
|
|
||||||
} else if (op.status == "executing") {
|
} else if (op.status == "executing") {
|
||||||
if (opidCount == 0) {
|
if (opidCount == 0) {
|
||||||
opidCount++;
|
opidCount++;
|
||||||
|
@ -399,28 +441,58 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (batchRPC.length <= 0) {
|
||||||
|
opidInterval = setInterval(checkOpids, opid_interval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
daemon.batchCmd(batchRPC, function(error, results){
|
||||||
|
if (error || !results) {
|
||||||
|
logger.error(logSystem, logComponent, 'Error with z_getoperationresult ' + JSON.stringify(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results.forEach(function(result, i) {
|
||||||
|
if (parseFloat(result.result[i].execution_secs || 0) > parseFloat(poolOptions.walletInterval))
|
||||||
|
logger.warning(logSystem, logComponent, 'Increase walletInterval in pool_config. opid execution took '+result.result[i].execution_secs+' secs.');
|
||||||
|
});
|
||||||
|
opidInterval = setInterval(checkOpids, opid_interval);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
clearInterval(opidInterval);
|
||||||
daemon.cmd('z_getoperationstatus', null, function (result) {
|
daemon.cmd('z_getoperationstatus', null, function (result) {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
logger.warning(logSystem, logComponent, 'Unable to get operation ids for clearing.');
|
logger.warning(logSystem, logComponent, 'Unable to get operation ids for clearing.');
|
||||||
}
|
opidInterval = setInterval(checkOpids, opid_interval);
|
||||||
if (result.response) {
|
} else if (result.response) {
|
||||||
checkOpIdSuccessAndGetResult(result.response);
|
checkOpIdSuccessAndGetResult(result.response);
|
||||||
|
} else {
|
||||||
|
opidInterval = setInterval(checkOpids, opid_interval);
|
||||||
}
|
}
|
||||||
}, true, true);
|
}, true, true);
|
||||||
}, opid_interval);
|
}
|
||||||
|
|
||||||
|
var opidInterval = setInterval(checkOpids, opid_interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
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){
|
var satoshisToCoins = function(satoshis){
|
||||||
return parseFloat((satoshis / magnitude).toFixed(coinPrecision));
|
return roundTo((satoshis / magnitude), coinPrecision);
|
||||||
};
|
};
|
||||||
|
|
||||||
var coinsToSatoshies = function(coins){
|
var coinsToSatoshies = function(coins){
|
||||||
return Math.round(coins * magnitude);
|
return Math.round(coins * magnitude);
|
||||||
};
|
};
|
||||||
|
|
||||||
function balanceRound(number) {
|
function coinsRound(number) {
|
||||||
return parseFloat((Math.round(number * 100000000) / 100000000).toFixed(8));
|
return roundTo(number, coinPrecision);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForDuplicateBlockHeight(rounds, height) {
|
function checkForDuplicateBlockHeight(rounds, height) {
|
||||||
|
@ -452,8 +524,10 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
var endRPCTimer = function(){ timeSpentRPC += Date.now() - startTimeRedis };
|
var endRPCTimer = function(){ timeSpentRPC += Date.now() - startTimeRedis };
|
||||||
|
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
/*
|
||||||
/* Call redis to get an array of rounds and balances - which are coinbase transactions and block heights from submitted blocks. */
|
Step 1 - build workers and rounds objects from redis
|
||||||
|
* removes duplicate block submissions from redis
|
||||||
|
*/
|
||||||
function(callback){
|
function(callback){
|
||||||
startRedisTimer();
|
startRedisTimer();
|
||||||
redisClient.multi([
|
redisClient.multi([
|
||||||
|
@ -466,12 +540,12 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// build worker balances
|
// build workers object from :balances
|
||||||
var workers = {};
|
var workers = {};
|
||||||
for (var w in results[0]){
|
for (var w in results[0]){
|
||||||
workers[w] = {balance: coinsToSatoshies(parseFloat(results[0][w]))};
|
workers[w] = {balance: coinsToSatoshies(parseFloat(results[0][w]))};
|
||||||
}
|
}
|
||||||
// build initial rounds data from blocksPending
|
// build rounds object from :blocksPending
|
||||||
var rounds = results[1].map(function(r){
|
var rounds = results[1].map(function(r){
|
||||||
var details = r.split(':');
|
var details = r.split(':');
|
||||||
return {
|
return {
|
||||||
|
@ -561,86 +635,42 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet.
|
/*
|
||||||
It also adds the block reward amount to the round object - which the daemon gives also gives us. */
|
Step 2 - check if mined block coinbase tx are ready for payment
|
||||||
|
* adds block reward to rounds object
|
||||||
|
* adds block confirmations count to rounds object
|
||||||
|
* updates confirmation counts in redis
|
||||||
|
*/
|
||||||
function(workers, rounds, callback){
|
function(workers, rounds, callback){
|
||||||
|
// get pending block tx details
|
||||||
// first verify block confirmations by block hash
|
|
||||||
var batchRPCcommand2 = rounds.map(function(r){
|
|
||||||
return ['getblock', [r.blockHash]];
|
|
||||||
});
|
|
||||||
// guarantee a response for batchRPCcommand2
|
|
||||||
batchRPCcommand2.push(['getblockcount']);
|
|
||||||
|
|
||||||
startRPCTimer();
|
|
||||||
daemon.batchCmd(batchRPCcommand2, function(error, blockDetails){
|
|
||||||
endRPCTimer();
|
|
||||||
|
|
||||||
// error getting block info by hash?
|
|
||||||
if (error || !blockDetails){
|
|
||||||
logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch getblock '
|
|
||||||
+ JSON.stringify(error));
|
|
||||||
callback(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update confirmations in redis for pending blocks
|
|
||||||
var confirmsUpdate = blockDetails.map(function(b) {
|
|
||||||
if (b.result != null && b.result.confirmations > 0) {
|
|
||||||
if (b.result.confirmations > 100) {
|
|
||||||
return ['hdel', logComponent + ':blocksPendingConfirms', b.result.hash];
|
|
||||||
}
|
|
||||||
return ['hset', logComponent + ':blocksPendingConfirms', b.result.hash, b.result.confirmations];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// filter nulls, last item is always null...
|
|
||||||
confirmsUpdate = confirmsUpdate.filter(function(val) { return val !== null; });
|
|
||||||
// guarantee at least one redis update
|
|
||||||
if (confirmsUpdate.length < 1)
|
|
||||||
confirmsUpdate.push(['hset', logComponent + ':blocksPendingConfirms', 0, 0]);
|
|
||||||
|
|
||||||
startRedisTimer();
|
|
||||||
redisClient.multi(confirmsUpdate).exec(function(error, updated){
|
|
||||||
endRedisTimer();
|
|
||||||
|
|
||||||
if (error){
|
|
||||||
logger.error(logSystem, logComponent, 'failed to update pending block confirmations'
|
|
||||||
+ JSON.stringify(error));
|
|
||||||
callback(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get pending block transaction details from coin daemon
|
|
||||||
var batchRPCcommand = rounds.map(function(r){
|
var batchRPCcommand = rounds.map(function(r){
|
||||||
return ['gettransaction', [r.txHash]];
|
return ['gettransaction', [r.txHash]];
|
||||||
});
|
});
|
||||||
// get account address (not implemented in zcash at this time..)
|
// get account address (not implemented at this time)
|
||||||
batchRPCcommand.push(['getaccount', [poolOptions.address]]);
|
batchRPCcommand.push(['getaccount', [poolOptions.address]]);
|
||||||
|
|
||||||
startRPCTimer();
|
startRPCTimer();
|
||||||
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
|
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
|
||||||
endRPCTimer();
|
endRPCTimer();
|
||||||
|
|
||||||
if (error || !txDetails){
|
if (error || !txDetails){
|
||||||
logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch gettransactions '
|
logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch gettransactions ' + JSON.stringify(error));
|
||||||
+ JSON.stringify(error));
|
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var confirmsUpdate = [];
|
||||||
var addressAccount = "";
|
var addressAccount = "";
|
||||||
|
|
||||||
// check for transaction errors and generated coins
|
// check for transaction errors and generated coins
|
||||||
txDetails.forEach(function(tx, i){
|
txDetails.forEach(function(tx, i){
|
||||||
|
|
||||||
if (i === txDetails.length - 1){
|
if (i === txDetails.length - 1){
|
||||||
addressAccount = tx.result;
|
if (tx.result && tx.result.toString().length > 0) {
|
||||||
|
addressAccount = tx.result.toString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var round = rounds[i];
|
var round = rounds[i];
|
||||||
|
// look for transaction errors
|
||||||
if (tx.error && tx.error.code === -5){
|
if (tx.error && tx.error.code === -5){
|
||||||
logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash);
|
logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash);
|
||||||
round.category = 'kicked';
|
round.category = 'kicked';
|
||||||
|
@ -652,29 +682,29 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (tx.error || !tx.result){
|
else if (tx.error || !tx.result){
|
||||||
logger.error(logSystem, logComponent, 'Odd error with gettransaction ' + round.txHash + ' '
|
logger.error(logSystem, logComponent, 'Odd error with gettransaction ' + round.txHash + ' ' + JSON.stringify(tx));
|
||||||
+ JSON.stringify(tx));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// get the coin base generation tx
|
||||||
var generationTx = tx.result.details.filter(function(tx){
|
var generationTx = tx.result.details.filter(function(tx){
|
||||||
return tx.address === poolOptions.address;
|
return tx.address === poolOptions.address;
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
if (!generationTx && tx.result.details.length === 1){
|
if (!generationTx && tx.result.details.length === 1){
|
||||||
generationTx = tx.result.details[0];
|
generationTx = tx.result.details[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!generationTx){
|
if (!generationTx){
|
||||||
logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + round.txHash);
|
logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + round.txHash);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// get transaction category for round
|
||||||
round.category = generationTx.category;
|
round.category = generationTx.category;
|
||||||
|
round.confirmations = parseInt((tx.result.confirmations || 0));
|
||||||
|
// get reward for newly generated blocks
|
||||||
if (round.category === 'generate') {
|
if (round.category === 'generate') {
|
||||||
round.reward = balanceRound(generationTx.amount - fee) || balanceRound(generationTx.value - fee); // TODO: Adjust fees to be dynamic
|
round.reward = coinsRound(parseFloat(generationTx.amount || generationTx.value));
|
||||||
}
|
}
|
||||||
|
// update confirmations in redis
|
||||||
|
confirmsUpdate.push(['hset', coin + ':blocksPendingConfirms', round.blockHash, round.confirmations]);
|
||||||
});
|
});
|
||||||
|
|
||||||
var canDeleteShares = function(r){
|
var canDeleteShares = function(r){
|
||||||
|
@ -692,10 +722,8 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
|
|
||||||
// limit blocks paid per payment round
|
// limit blocks paid per payment round
|
||||||
var payingBlocks = 0;
|
var payingBlocks = 0;
|
||||||
|
|
||||||
//filter out all rounds that are immature (not confirmed or orphaned yet)
|
//filter out all rounds that are immature (not confirmed or orphaned yet)
|
||||||
rounds = rounds.filter(function(r){
|
rounds = rounds.filter(function(r){
|
||||||
|
|
||||||
// only pay max blocks at a time
|
// only pay max blocks at a time
|
||||||
if (payingBlocks >= maxBlocksPerPayment)
|
if (payingBlocks >= maxBlocksPerPayment)
|
||||||
return false;
|
return false;
|
||||||
|
@ -715,14 +743,13 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: make tx fees dynamic
|
// TODO: make tx fees dynamic
|
||||||
var feeSatoshi = fee * magnitude;
|
var feeSatoshi = coinsToSatoshies(fee);
|
||||||
|
|
||||||
// calculate what the pool owes its miners
|
// calculate what the pool owes its miners
|
||||||
var totalOwed = parseInt(0);
|
var totalOwed = parseInt(0);
|
||||||
for (var i = 0; i < rounds.length; i++) {
|
for (var i = 0; i < rounds.length; i++) {
|
||||||
// only pay generated blocks, not orphaned or kicked
|
// only pay generated blocks, not orphaned or kicked
|
||||||
if (rounds[i].category == 'generate') {
|
if (rounds[i].category == 'generate') {
|
||||||
totalOwed = totalOwed + Math.round(rounds[i].reward * magnitude) - feeSatoshi;
|
totalOwed = totalOwed + coinsToSatoshies(rounds[i].reward) - feeSatoshi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,93 +758,180 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
notAddr = poolOptions.address;
|
notAddr = poolOptions.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we have enough tAddress funds to brgin payment processing
|
// update confirmations for pending blocks in redis
|
||||||
|
if (confirmsUpdate.length > 0) {
|
||||||
|
startRedisTimer();
|
||||||
|
redisClient.multi(confirmsUpdate).exec(function(error, result){
|
||||||
|
endRedisTimer();
|
||||||
|
if (error) {
|
||||||
|
logger.error(logSystem, logComponent, 'Error could not update confirmations for pending blocks in redis ' + JSON.stringify(error));
|
||||||
|
return callback(true);
|
||||||
|
}
|
||||||
|
// check if we have enough tAddress funds to begin payment processing
|
||||||
listUnspent(null, notAddr, minConfPayout, false, function (error, tBalance){
|
listUnspent(null, notAddr, minConfPayout, false, function (error, tBalance){
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(logSystem, logComponent, 'Error checking pool balance before processing payments.');
|
logger.error(logSystem, logComponent, 'Error checking pool balance before processing payments.');
|
||||||
return callback(true);
|
return callback(true);
|
||||||
} else if (tBalance < totalOwed) {
|
} else if (tBalance < totalOwed) {
|
||||||
logger.error(logSystem, logComponent, 'Insufficient funds to process payments for ' + payingBlocks + ' blocks ('+(tBalance / magnitude).toFixed(8) + ' < ' + (totalOwed / magnitude).toFixed(8)+'). Possibly waiting for shielding process.');
|
logger.error(logSystem, logComponent, 'Insufficient funds ('+satoshisToCoins(tBalance) + ') to process payments (' + satoshisToCoins(totalOwed)+') for ' + payingBlocks + ' blocks; possibly waiting for txs.');
|
||||||
return callback(true);
|
return callback(true);
|
||||||
} else {
|
}
|
||||||
// zcash daemon does not support account feature
|
// account feature not implemented at this time
|
||||||
addressAccount = "";
|
addressAccount = "";
|
||||||
|
// begin payments for generated coins
|
||||||
callback(null, workers, rounds, addressAccount);
|
callback(null, workers, rounds, addressAccount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// no pending blocks, need to find a block!
|
||||||
|
return callback(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/* Does a batch redis call to get shares contributed to each round. Then calculates the reward
|
/*
|
||||||
amount owned to each miner for each round. */
|
Step 3 - lookup shares in redis and calculate rewards
|
||||||
|
*/
|
||||||
function(workers, rounds, addressAccount, callback){
|
function(workers, rounds, addressAccount, callback){
|
||||||
|
// pplnt times lookup
|
||||||
var shareLookups = rounds.map(function(r){
|
var timeLookups = rounds.map(function(r){
|
||||||
return ['hgetall', coin + ':shares:round' + r.height]
|
return ['hgetall', coin + ':shares:times' + r.height]
|
||||||
|
});
|
||||||
|
startRedisTimer();
|
||||||
|
redisClient.multi(timeLookups).exec(function(error, allWorkerTimes){
|
||||||
|
endRedisTimer();
|
||||||
|
if (error){
|
||||||
|
callback('Check finished - redis error with multi get rounds time');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var shareLookups = rounds.map(function(r){
|
||||||
|
return ['hgetall', coin + ':shares:round' + r.height];
|
||||||
});
|
});
|
||||||
|
|
||||||
startRedisTimer();
|
startRedisTimer();
|
||||||
redisClient.multi(shareLookups).exec(function(error, allWorkerShares){
|
redisClient.multi(shareLookups).exec(function(error, allWorkerShares){
|
||||||
endRedisTimer();
|
endRedisTimer();
|
||||||
|
|
||||||
if (error){
|
if (error){
|
||||||
callback('Check finished - redis error with multi get rounds share');
|
callback('Check finished - redis error with multi get rounds share');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// error detection
|
||||||
|
var err = null;
|
||||||
|
|
||||||
|
// total shares
|
||||||
rounds.forEach(function(round, i){
|
rounds.forEach(function(round, i){
|
||||||
var workerShares = allWorkerShares[i];
|
var workerShares = allWorkerShares[i];
|
||||||
|
|
||||||
if (!workerShares){
|
if (!workerShares){
|
||||||
logger.error(logSystem, logComponent, 'No worker shares for round: '
|
err = true;
|
||||||
+ round.height + ' blockHash: ' + round.blockHash);
|
logger.error(logSystem, logComponent, 'No worker shares for round: ' + round.height + ' blockHash: ' + round.blockHash);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var workerTimes = allWorkerTimes[i];
|
||||||
switch (round.category){
|
switch (round.category){
|
||||||
case 'kicked':
|
case 'kicked':
|
||||||
case 'orphan':
|
case 'orphan':
|
||||||
round.workerShares = workerShares;
|
round.workerShares = workerShares;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'generate':
|
case 'generate':
|
||||||
/* We found a confirmed block! Now get the reward for it and calculate how much
|
// TODO: make tx fees dynamic
|
||||||
we owe each miner based on the shares they submitted during that block round. */
|
var feeSatoshi = coinsToSatoshies(fee);
|
||||||
var reward = parseInt(round.reward * magnitude);
|
var reward = coinsToSatoshies(round.reward) - feeSatoshi;
|
||||||
|
var totalShares = parseFloat(0);
|
||||||
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
var sharesLost = parseFloat(0);
|
||||||
return p + parseFloat(workerShares[c])
|
// find most time spent in this round by single worker
|
||||||
}, 0);
|
maxTime = 0;
|
||||||
|
for (var workerAddress in workerTimes){
|
||||||
for (var workerAddress in workerShares){
|
if (maxTime < parseFloat(workerTimes[workerAddress]))
|
||||||
var percent = parseFloat(workerShares[workerAddress]) / totalShares;
|
maxTime = parseFloat(workerTimes[workerAddress]);
|
||||||
var workerRewardTotal = Math.floor(reward * percent);
|
|
||||||
var worker = workers[workerAddress] = (workers[workerAddress] || {});
|
|
||||||
worker.totalShares = (worker.totalShares || 0) + parseFloat(workerShares[workerAddress]);
|
|
||||||
worker.reward = (worker.reward || 0) + workerRewardTotal;
|
|
||||||
}
|
}
|
||||||
|
// total up shares for round
|
||||||
|
for (var workerAddress in workerShares){
|
||||||
|
var worker = workers[workerAddress] = (workers[workerAddress] || {});
|
||||||
|
var shares = parseFloat((workerShares[workerAddress] || 0));
|
||||||
|
// if pplnt mode
|
||||||
|
if (pplntEnabled === true && maxTime > 0) {
|
||||||
|
var tshares = shares;
|
||||||
|
var lost = parseFloat(0);
|
||||||
|
var address = workerAddress.split('.')[0];
|
||||||
|
if (workerTimes[address] != null && parseFloat(workerTimes[address]) > 0) {
|
||||||
|
var timePeriod = roundTo(parseFloat(workerTimes[address] || 1) / maxTime , 2);
|
||||||
|
if (timePeriod > 0 && timePeriod < pplntTimeQualify) {
|
||||||
|
var lost = shares - (shares * timePeriod);
|
||||||
|
sharesLost += lost;
|
||||||
|
shares = Math.max(shares - lost, 0);
|
||||||
|
logger.warning(logSystem, logComponent, 'PPLNT: Reduced shares for '+workerAddress+' round:' + round.height + ' maxTime:'+maxTime+'sec timePeriod:'+roundTo(timePeriod,6)+' shares:'+tshares+' lost:'+lost+' new:'+shares);
|
||||||
|
}
|
||||||
|
if (timePeriod > 1.0) {
|
||||||
|
err = true;
|
||||||
|
logger.error(logSystem, logComponent, 'Time share period is greater than 1.0 for '+workerAddress+' round:' + round.height + ' blockHash:' + round.blockHash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warning(logSystem, logComponent, 'PPLNT: Missing time share period for '+workerAddress+', miner shares qualified in round ' + round.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
worker.roundShares = shares;
|
||||||
|
worker.totalShares = parseFloat(worker.totalShares || 0) + shares;
|
||||||
|
totalShares += shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('--REWARD DEBUG--------------');
|
||||||
|
// calculate rewards for round
|
||||||
|
var totalAmount = 0;
|
||||||
|
for (var workerAddress in workerShares){
|
||||||
|
var worker = workers[workerAddress] = (workers[workerAddress] || {});
|
||||||
|
var percent = parseFloat(worker.roundShares) / totalShares;
|
||||||
|
if (percent > 1.0) {
|
||||||
|
err = true;
|
||||||
|
logger.error(logSystem, logComponent, 'Share percent is greater than 1.0 for '+workerAddress+' round:' + round.height + ' blockHash:' + round.blockHash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// calculate workers reward for this round
|
||||||
|
var workerRewardTotal = Math.round(reward * percent);
|
||||||
|
// add to total reward for worker
|
||||||
|
worker.reward = (worker.reward || 0) + workerRewardTotal;
|
||||||
|
// add to total amount sent to all workers
|
||||||
|
totalAmount += worker.reward;
|
||||||
|
//console.log('rewardAmount: '+workerAddress+' '+workerRewardTotal);
|
||||||
|
//console.log('totalAmount: '+workerAddress+' '+worker.reward);
|
||||||
|
}
|
||||||
|
//console.log('totalAmount: '+totalAmount);
|
||||||
|
//console.log('blockHeight: '+round.height);
|
||||||
|
//console.log('blockReward: '+reward);
|
||||||
|
//console.log('totalShares: '+totalShares);
|
||||||
|
//console.log('sharesLost: '+sharesLost);
|
||||||
|
//console.log('----------------------------');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// if there was no errors
|
||||||
|
if (err === null) {
|
||||||
|
// continue payments
|
||||||
callback(null, workers, rounds, addressAccount);
|
callback(null, workers, rounds, addressAccount);
|
||||||
|
} else {
|
||||||
|
// stop waterfall flow, do not process payments
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Calculate if any payments are ready to be sent and trigger them sending
|
|
||||||
Get balance different for each address and pass it along as object of latest balances such as
|
/*
|
||||||
{worker1: balance1, worker2, balance2}
|
Step 4 - Generate RPC commands to send payments
|
||||||
when deciding the sent balance, it the difference should be -1*amount they had in db,
|
When deciding the sent balance, it the difference should be -1*amount they had in db,
|
||||||
if not sending the balance, the differnce should be +(the amount they earned this round)
|
If not sending the balance, the differnce should be +(the amount they earned this round)
|
||||||
*/
|
*/
|
||||||
function(workers, rounds, addressAccount, callback) {
|
function(workers, rounds, addressAccount, callback) {
|
||||||
|
|
||||||
var trySend = function (withholdPercent) {
|
var trySend = function (withholdPercent) {
|
||||||
var addressAmounts = {};
|
var addressAmounts = {};
|
||||||
|
var balanceAmounts = {};
|
||||||
|
var shareAmounts = {};
|
||||||
var minerTotals = {};
|
var minerTotals = {};
|
||||||
var totalSent = 0;
|
var totalSent = 0;
|
||||||
var totalShares = 0;
|
var totalShares = 0;
|
||||||
|
@ -827,12 +941,13 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
totalShares += (worker.totalShares || 0)
|
totalShares += (worker.totalShares || 0)
|
||||||
worker.balance = worker.balance || 0;
|
worker.balance = worker.balance || 0;
|
||||||
worker.reward = worker.reward || 0;
|
worker.reward = worker.reward || 0;
|
||||||
var toSend = balanceRound(satoshisToCoins(Math.floor((worker.balance + worker.reward) * (1 - withholdPercent))));
|
// get miner payout totals
|
||||||
|
var toSendSatoshis = Math.round((worker.balance + worker.reward) * (1 - withholdPercent));
|
||||||
var address = worker.address = (worker.address || getProperAddress(w.split('.')[0]));
|
var address = worker.address = (worker.address || getProperAddress(w.split('.')[0]));
|
||||||
if (minerTotals[address] != null && minerTotals[address] > 0) {
|
if (minerTotals[address] != null && minerTotals[address] > 0) {
|
||||||
minerTotals[address] = balanceRound(minerTotals[address] + toSend);
|
minerTotals[address] += toSendSatoshis;
|
||||||
} else {
|
} else {
|
||||||
minerTotals[address] = toSend;
|
minerTotals[address] = toSendSatoshis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// now process each workers balance, and pay the miner
|
// now process each workers balance, and pay the miner
|
||||||
|
@ -840,23 +955,40 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
var worker = workers[w];
|
var worker = workers[w];
|
||||||
worker.balance = worker.balance || 0;
|
worker.balance = worker.balance || 0;
|
||||||
worker.reward = worker.reward || 0;
|
worker.reward = worker.reward || 0;
|
||||||
var toSend = Math.floor((worker.balance + worker.reward) * (1 - withholdPercent));
|
var toSendSatoshis = Math.round((worker.balance + worker.reward) * (1 - withholdPercent));
|
||||||
var address = worker.address = (worker.address || getProperAddress(w.split('.')[0]));
|
var address = worker.address = (worker.address || getProperAddress(w.split('.')[0]));
|
||||||
// if miners total is enough, go ahead and add this worker balance
|
// if miners total is enough, go ahead and add this worker balance
|
||||||
if (minerTotals[address] >= satoshisToCoins(minPaymentSatoshis)) {
|
if (minerTotals[address] >= minPaymentSatoshis) {
|
||||||
totalSent += toSend;
|
totalSent += toSendSatoshis;
|
||||||
worker.sent = balanceRound(satoshisToCoins(toSend));
|
// send funds
|
||||||
worker.balanceChange = Math.min(worker.balance, toSend) * -1;
|
worker.sent = satoshisToCoins(toSendSatoshis);
|
||||||
|
worker.balanceChange = Math.min(worker.balance, toSendSatoshis) * -1;
|
||||||
// multiple workers may have same address, add them up
|
// multiple workers may have same address, add them up
|
||||||
if (addressAmounts[address] != null && addressAmounts[address] > 0) {
|
if (addressAmounts[address] != null && addressAmounts[address] > 0) {
|
||||||
addressAmounts[address] = balanceRound(addressAmounts[address] + worker.sent);
|
addressAmounts[address] = coinsRound(addressAmounts[address] + worker.sent);
|
||||||
} else {
|
} else {
|
||||||
addressAmounts[address] = worker.sent;
|
addressAmounts[address] = worker.sent;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
// add to balance, not enough minerals
|
||||||
worker.balanceChange = Math.max(toSend - worker.balance, 0);
|
|
||||||
worker.sent = 0;
|
worker.sent = 0;
|
||||||
|
worker.balanceChange = Math.max(toSendSatoshis - worker.balance, 0);
|
||||||
|
// track balance changes
|
||||||
|
if (worker.balanceChange > 0) {
|
||||||
|
if (balanceAmounts[address] != null && balanceAmounts[address] > 0) {
|
||||||
|
balanceAmounts[address] = coinsRound(balanceAmounts[address] + satoshisToCoins(worker.balanceChange));
|
||||||
|
} else {
|
||||||
|
balanceAmounts[address] = satoshisToCoins(worker.balanceChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// track share work
|
||||||
|
if (worker.totalShares > 0) {
|
||||||
|
if (shareAmounts[address] != null && shareAmounts[address] > 0) {
|
||||||
|
shareAmounts[address] += worker.totalShares;
|
||||||
|
} else {
|
||||||
|
shareAmounts[address] = worker.totalShares;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,49 +997,40 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
callback(null, workers, rounds);
|
callback(null, workers, rounds);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
var undoPaymentsOnError = function(workers) {
|
// POINT OF NO RETURN! GOOD LUCK!
|
||||||
totalSent = 0;
|
// WE ARE SENDING PAYMENT CMD TO DAEMON
|
||||||
// TODO, set round.category to immature, to attempt to pay again
|
|
||||||
// we did not send anything to any workers
|
// perform the sendmany operation .. addressAccount
|
||||||
for (var w in workers) {
|
|
||||||
var worker = workers[w];
|
|
||||||
if (worker.sent > 0) {
|
|
||||||
worker.balanceChange = 0;
|
|
||||||
worker.sent = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
// perform the sendmany operation
|
|
||||||
daemon.cmd('sendmany', ["", addressAmounts], function (result) {
|
daemon.cmd('sendmany', ["", addressAmounts], function (result) {
|
||||||
// check for failed payments, there are many reasons
|
// check for failed payments, there are many reasons
|
||||||
if (result.error && result.error.code === -6) {
|
if (result.error && result.error.code === -6) {
|
||||||
// not enough minerals...
|
// we thought we had enough funds to send payments, but apparently not...
|
||||||
var higherPercent = withholdPercent + 0.01;
|
// try decreasing payments by a small percent to cover unexpected tx fees?
|
||||||
logger.warning(logSystem, logComponent, 'Not enough funds to cover the tx fees for sending out payments, decreasing rewards by '
|
var higherPercent = withholdPercent + 0.001;
|
||||||
+ (higherPercent * 100) + '% and retrying');
|
logger.warning(logSystem, logComponent, 'Not enough funds to cover the tx fees for sending out payments, decreasing rewards by ' + (higherPercent * 100) + '% and retrying');
|
||||||
|
|
||||||
trySend(higherPercent);
|
trySend(higherPercent);
|
||||||
|
//callback(true); not a complete failure...
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (result.error && result.error.code === -5) {
|
else if (result.error && result.error.code === -5) {
|
||||||
// invalid address specified in addressAmounts array
|
// invalid address specified in addressAmounts array
|
||||||
logger.error(logSystem, logComponent, 'Error sending payments ' + result.error.message);
|
logger.error(logSystem, logComponent, 'Error sending payments ' + result.error.message);
|
||||||
//undoPaymentsOnError(workers);
|
// payment failed, prevent updates to redis
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (result.error && result.error.message != null) {
|
else if (result.error && result.error.message != null) {
|
||||||
// unknown error from daemon
|
// error from daemon
|
||||||
logger.error(logSystem, logComponent, 'Error sending payments ' + result.error.message);
|
logger.error(logSystem, logComponent, 'Error sending payments ' + result.error.message);
|
||||||
//undoPaymentsOnError(workers);
|
// payment failed, prevent updates to redis
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (result.error) {
|
else if (result.error) {
|
||||||
// some other unknown error
|
// unknown error
|
||||||
logger.error(logSystem, logComponent, 'Error sending payments ' + JSON.stringify(result.error));
|
logger.error(logSystem, logComponent, 'Error sending payments ' + JSON.stringify(result.error));
|
||||||
//undoPaymentsOnError(workers);
|
// payment failed, prevent updates to redis
|
||||||
callback(true);
|
callback(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -921,7 +1044,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
if (txid != null) {
|
if (txid != null) {
|
||||||
|
|
||||||
// it worked, congrats on your pools payout ;)
|
// it worked, congrats on your pools payout ;)
|
||||||
logger.special(logSystem, logComponent, 'Sent ' + (totalSent / magnitude).toFixed(8)
|
logger.special(logSystem, logComponent, 'Sent ' + satoshisToCoins(totalSent)
|
||||||
+ ' to ' + Object.keys(addressAmounts).length + ' miners; txid: '+txid);
|
+ ' to ' + Object.keys(addressAmounts).length + ' miners; txid: '+txid);
|
||||||
|
|
||||||
if (withholdPercent > 0) {
|
if (withholdPercent > 0) {
|
||||||
|
@ -931,11 +1054,12 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
}
|
}
|
||||||
|
|
||||||
// save payments data to redis
|
// save payments data to redis
|
||||||
var paymentBlocks = rounds.map(function(r){
|
var paymentBlocks = rounds.filter(function(r){ return r.category == 'generate'; }).map(function(r){
|
||||||
return parseInt(r.height);
|
return parseInt(r.height);
|
||||||
});
|
});
|
||||||
|
|
||||||
var paymentsUpdate = [];
|
var paymentsUpdate = [];
|
||||||
var paymentsData = {time:Date.now(), txid:txid, shares:totalShares, paid:balanceRound(totalSent / magnitude), miners:Object.keys(addressAmounts).length, blocks: paymentBlocks, amounts: addressAmounts};
|
var paymentsData = {time:Date.now(), txid:txid, shares:totalShares, paid:satoshisToCoins(totalSent), miners:Object.keys(addressAmounts).length, blocks: paymentBlocks, amounts: addressAmounts, balances: balanceAmounts, work:shareAmounts};
|
||||||
paymentsUpdate.push(['zadd', logComponent + ':payments', Date.now(), JSON.stringify(paymentsData)]);
|
paymentsUpdate.push(['zadd', logComponent + ':payments', Date.now(), JSON.stringify(paymentsData)]);
|
||||||
startRedisTimer();
|
startRedisTimer();
|
||||||
redisClient.multi(paymentsUpdate).exec(function(error, payments){
|
redisClient.multi(paymentsUpdate).exec(function(error, payments){
|
||||||
|
@ -943,6 +1067,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
if (error){
|
if (error){
|
||||||
logger.error(logSystem, logComponent, 'Error redis save payments data ' + JSON.stringify(payments));
|
logger.error(logSystem, logComponent, 'Error redis save payments data ' + JSON.stringify(payments));
|
||||||
}
|
}
|
||||||
|
// perform final redis updates
|
||||||
callback(null, workers, rounds);
|
callback(null, workers, rounds);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -958,12 +1083,16 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, true, true);
|
}, true, true);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
trySend(0);
|
trySend(0);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Step 5 - Final redis commands
|
||||||
|
*/
|
||||||
function(workers, rounds, callback){
|
function(workers, rounds, callback){
|
||||||
|
|
||||||
var totalPaid = parseFloat(0);
|
var totalPaid = parseFloat(0);
|
||||||
|
@ -979,18 +1108,19 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
'hincrbyfloat',
|
'hincrbyfloat',
|
||||||
coin + ':balances',
|
coin + ':balances',
|
||||||
w,
|
w,
|
||||||
balanceRound(satoshisToCoins(worker.balanceChange))
|
satoshisToCoins(worker.balanceChange)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (worker.sent !== 0){
|
if (worker.sent !== 0){
|
||||||
workerPayoutsCommand.push(['hincrbyfloat', coin + ':payouts', w, balanceRound(worker.sent)]);
|
workerPayoutsCommand.push(['hincrbyfloat', coin + ':payouts', w, coinsRound(worker.sent)]);
|
||||||
totalPaid = balanceRound(totalPaid + worker.sent);
|
totalPaid = coinsRound(totalPaid + worker.sent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var movePendingCommands = [];
|
var movePendingCommands = [];
|
||||||
var roundsToDelete = [];
|
var roundsToDelete = [];
|
||||||
var orphanMergeCommands = [];
|
var orphanMergeCommands = [];
|
||||||
|
var confirmsToDelete = [];
|
||||||
|
|
||||||
var moveSharesToCurrent = function(r){
|
var moveSharesToCurrent = function(r){
|
||||||
var workerShares = r.workerShares;
|
var workerShares = r.workerShares;
|
||||||
|
@ -1005,17 +1135,22 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
rounds.forEach(function(r){
|
rounds.forEach(function(r){
|
||||||
switch(r.category){
|
switch(r.category){
|
||||||
case 'kicked':
|
case 'kicked':
|
||||||
|
confirmsToDelete.push(['hdel', coin + ':blocksPendingConfirms', r.blockHash]);
|
||||||
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksKicked', r.serialized]);
|
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksKicked', r.serialized]);
|
||||||
case 'orphan':
|
case 'orphan':
|
||||||
|
confirmsToDelete.push(['hdel', coin + ':blocksPendingConfirms', r.blockHash]);
|
||||||
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksOrphaned', r.serialized]);
|
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksOrphaned', r.serialized]);
|
||||||
if (r.canDeleteShares){
|
if (r.canDeleteShares){
|
||||||
moveSharesToCurrent(r);
|
moveSharesToCurrent(r);
|
||||||
roundsToDelete.push(coin + ':shares:round' + r.height);
|
roundsToDelete.push(coin + ':shares:round' + r.height);
|
||||||
|
roundsToDelete.push(coin + ':shares:times' + r.height);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case 'generate':
|
case 'generate':
|
||||||
|
confirmsToDelete.push(['hdel', coin + ':blocksPendingConfirms', r.blockHash]);
|
||||||
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksConfirmed', r.serialized]);
|
movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksConfirmed', r.serialized]);
|
||||||
roundsToDelete.push(coin + ':shares:round' + r.height);
|
roundsToDelete.push(coin + ':shares:round' + r.height);
|
||||||
|
roundsToDelete.push(coin + ':shares:times' + r.height);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1037,8 +1172,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
if (roundsToDelete.length > 0)
|
if (roundsToDelete.length > 0)
|
||||||
finalRedisCommands.push(['del'].concat(roundsToDelete));
|
finalRedisCommands.push(['del'].concat(roundsToDelete));
|
||||||
|
|
||||||
|
if (confirmsToDelete.length > 0)
|
||||||
|
finalRedisCommands = finalRedisCommands.concat(confirmsToDelete);
|
||||||
|
|
||||||
if (totalPaid !== 0)
|
if (totalPaid !== 0)
|
||||||
finalRedisCommands.push(['hincrbyfloat', coin + ':stats', 'totalPaid', balanceRound(totalPaid)]);
|
finalRedisCommands.push(['hincrbyfloat', coin + ':stats', 'totalPaid', totalPaid]);
|
||||||
|
|
||||||
if (finalRedisCommands.length === 0){
|
if (finalRedisCommands.length === 0){
|
||||||
callback();
|
callback();
|
||||||
|
@ -1048,12 +1186,14 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
||||||
startRedisTimer();
|
startRedisTimer();
|
||||||
redisClient.multi(finalRedisCommands).exec(function(error, results){
|
redisClient.multi(finalRedisCommands).exec(function(error, results){
|
||||||
endRedisTimer();
|
endRedisTimer();
|
||||||
if (error){
|
if (error) {
|
||||||
clearInterval(paymentInterval);
|
clearInterval(paymentInterval);
|
||||||
|
|
||||||
logger.error(logSystem, logComponent,
|
logger.error(logSystem, logComponent,
|
||||||
'Payments sent but could not update redis. ' + JSON.stringify(error)
|
'Payments sent but could not update redis. ' + JSON.stringify(error)
|
||||||
+ ' Disabling payment processing to prevent possible double-payouts. The redis commands in '
|
+ ' Disabling payment processing to prevent possible double-payouts. The redis commands in '
|
||||||
+ coin + '_finalRedisCommands.txt must be ran manually');
|
+ coin + '_finalRedisCommands.txt must be ran manually');
|
||||||
|
|
||||||
fs.writeFile(coin + '_finalRedisCommands.txt', JSON.stringify(finalRedisCommands), function(err){
|
fs.writeFile(coin + '_finalRedisCommands.txt', JSON.stringify(finalRedisCommands), function(err){
|
||||||
logger.error('Could not write finalRedisCommands.txt, you are fucked.');
|
logger.error('Could not write finalRedisCommands.txt, you are fucked.');
|
||||||
});
|
});
|
||||||
|
|
|
@ -187,18 +187,21 @@ module.exports = function(logger){
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Block found: ' + data.blockHash + ' by ' + data.worker);
|
logger.debug(logSystem, logComponent, logSubCat, 'Block found: ' + data.blockHash + ' by ' + data.worker);
|
||||||
|
|
||||||
if (isValidShare) {
|
if (isValidShare) {
|
||||||
if(data.shareDiff > 1000000000)
|
if(data.shareDiff > 1000000000) {
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000.000!');
|
logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000.000!');
|
||||||
else if(data.shareDiff > 1000000)
|
} else if(data.shareDiff > 1000000) {
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000!');
|
logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000!');
|
||||||
|
}
|
||||||
//logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + '/' + data.shareDiff + ' by ' + data.worker + ' [' + data.ip + ']' );
|
//logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + '/' + data.shareDiff + ' by ' + data.worker + ' [' + data.ip + ']' );
|
||||||
|
} else if (!isValidShare) {
|
||||||
} else if (!isValidShare)
|
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Share rejected: ' + shareData);
|
logger.debug(logSystem, logComponent, logSubCat, 'Share rejected: ' + shareData);
|
||||||
|
}
|
||||||
|
|
||||||
handlers.share(isValidShare, isValidBlock, data)
|
// handle the share
|
||||||
|
handlers.share(isValidShare, isValidBlock, data);
|
||||||
|
|
||||||
|
// send to master for pplnt time tracking
|
||||||
|
process.send({type: 'shareTrack', thread:(parseInt(forkId)+1), coin:poolOptions.coin.name, isValidShare:isValidShare, isValidBlock:isValidBlock, data:data});
|
||||||
|
|
||||||
}).on('difficultyUpdate', function(workerName, diff){
|
}).on('difficultyUpdate', function(workerName, diff){
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Difficulty update to diff ' + diff + ' workerName=' + JSON.stringify(workerName));
|
logger.debug(logSystem, logComponent, logSubCat, 'Difficulty update to diff ' + diff + ' workerName=' + JSON.stringify(workerName));
|
||||||
|
|
|
@ -27,7 +27,6 @@ module.exports = function(logger, poolConfig){
|
||||||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||||
|
|
||||||
var connection = redis.createClient(redisConfig.port, redisConfig.host);
|
var connection = redis.createClient(redisConfig.port, redisConfig.host);
|
||||||
|
|
||||||
connection.on('ready', function(){
|
connection.on('ready', function(){
|
||||||
logger.debug(logSystem, logComponent, logSubCat, 'Share processing setup with redis (' + redisConfig.host +
|
logger.debug(logSystem, logComponent, logSubCat, 'Share processing setup with redis (' + redisConfig.host +
|
||||||
':' + redisConfig.port + ')');
|
':' + redisConfig.port + ')');
|
||||||
|
@ -38,7 +37,6 @@ module.exports = function(logger, poolConfig){
|
||||||
connection.on('end', function(){
|
connection.on('end', function(){
|
||||||
logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database has been ended');
|
logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database has been ended');
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.info(function(error, response){
|
connection.info(function(error, response){
|
||||||
if (error){
|
if (error){
|
||||||
logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed');
|
logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed');
|
||||||
|
@ -65,18 +63,17 @@ module.exports = function(logger, poolConfig){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.handleShare = function(isValidShare, isValidBlock, shareData) {
|
||||||
this.handleShare = function(isValidShare, isValidBlock, shareData){
|
|
||||||
|
|
||||||
var redisCommands = [];
|
var redisCommands = [];
|
||||||
|
|
||||||
if (isValidShare){
|
if (isValidShare) {
|
||||||
redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]);
|
redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]);
|
||||||
redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]);
|
redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]);
|
redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it
|
/* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it
|
||||||
doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to
|
doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to
|
||||||
generate hashrate for each worker and pool. */
|
generate hashrate for each worker and pool. */
|
||||||
|
@ -86,6 +83,7 @@ module.exports = function(logger, poolConfig){
|
||||||
|
|
||||||
if (isValidBlock){
|
if (isValidBlock){
|
||||||
redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]);
|
redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]);
|
||||||
|
redisCommands.push(['rename', coin + ':shares:timesCurrent', coin + ':shares:times' + shareData.height]);
|
||||||
redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height, shareData.worker, dateNow].join(':')]);
|
redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height, shareData.worker, dateNow].join(':')]);
|
||||||
redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]);
|
redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]);
|
||||||
}
|
}
|
||||||
|
@ -97,8 +95,6 @@ module.exports = function(logger, poolConfig){
|
||||||
if (err)
|
if (err)
|
||||||
logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err));
|
logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -153,6 +153,20 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
_this.statPoolHistory.push(data);
|
_this.statPoolHistory.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.getCoins = function(cback){
|
||||||
_this.stats.coins = redisClients[0].coins;
|
_this.stats.coins = redisClients[0].coins;
|
||||||
cback();
|
cback();
|
||||||
|
@ -296,7 +310,8 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
['smembers', ':blocksConfirmed'],
|
['smembers', ':blocksConfirmed'],
|
||||||
['hgetall', ':shares:roundCurrent'],
|
['hgetall', ':shares:roundCurrent'],
|
||||||
['hgetall', ':blocksPendingConfirms'],
|
['hgetall', ':blocksPendingConfirms'],
|
||||||
['zrange', ':payments', -100, -1]
|
['zrange', ':payments', -100, -1],
|
||||||
|
['hgetall', ':shares:timesCurrent']
|
||||||
];
|
];
|
||||||
|
|
||||||
var commandsPerCoin = redisCommandTemplates.length;
|
var commandsPerCoin = redisCommandTemplates.length;
|
||||||
|
@ -317,6 +332,12 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
else{
|
else{
|
||||||
for(var i = 0; i < replies.length; i += commandsPerCoin){
|
for(var i = 0; i < replies.length; i += commandsPerCoin){
|
||||||
var coinName = client.coins[i / commandsPerCoin | 0];
|
var coinName = client.coins[i / commandsPerCoin | 0];
|
||||||
|
var marketStats = {};
|
||||||
|
if (replies[i + 2]) {
|
||||||
|
if (replies[i + 2].coinmarketcap) {
|
||||||
|
marketStats = replies[i + 2] ? (JSON.parse(replies[i + 2].coinmarketcap)[0] || 0) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
var coinStats = {
|
var coinStats = {
|
||||||
name: coinName,
|
name: coinName,
|
||||||
symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
|
symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
|
||||||
|
@ -335,6 +356,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
networkVersion: replies[i + 2] ? (replies[i + 2].networkSubVersion || 0) : 0,
|
networkVersion: replies[i + 2] ? (replies[i + 2].networkSubVersion || 0) : 0,
|
||||||
networkProtocolVersion: replies[i + 2] ? (replies[i + 2].networkProtocolVersion || 0) : 0
|
networkProtocolVersion: replies[i + 2] ? (replies[i + 2].networkProtocolVersion || 0) : 0
|
||||||
},
|
},
|
||||||
|
marketStats: marketStats,
|
||||||
/* block stat counts */
|
/* block stat counts */
|
||||||
blocks: {
|
blocks: {
|
||||||
pending: replies[i + 3],
|
pending: replies[i + 3],
|
||||||
|
@ -344,14 +366,17 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
/* show all pending blocks */
|
/* show all pending blocks */
|
||||||
pending: {
|
pending: {
|
||||||
blocks: replies[i + 6].sort(sortBlocks),
|
blocks: replies[i + 6].sort(sortBlocks),
|
||||||
confirms: replies[i + 9]
|
confirms: (replies[i + 9] || {})
|
||||||
},
|
},
|
||||||
/* show last 5 found blocks */
|
/* show last 5 found blocks */
|
||||||
confirmed: {
|
confirmed: {
|
||||||
blocks: replies[i + 7].sort(sortBlocks).slice(0,5)
|
blocks: replies[i + 7].sort(sortBlocks).slice(0,5)
|
||||||
},
|
},
|
||||||
payments: [],
|
payments: [],
|
||||||
currentRoundShares: replies[i + 8]
|
currentRoundShares: (replies[i + 8] || {}),
|
||||||
|
currentRoundTimes: (replies[i + 11] || {}),
|
||||||
|
maxRoundTime: 0,
|
||||||
|
shareCount: 0
|
||||||
};
|
};
|
||||||
for(var j = replies[i + 10].length; j > 0; j--){
|
for(var j = replies[i + 10].length; j > 0; j--){
|
||||||
var jsonObj;
|
var jsonObj;
|
||||||
|
@ -364,17 +389,10 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
coinStats.payments.push(jsonObj);
|
coinStats.payments.push(jsonObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
for (var b in coinStats.confirmed.blocks) {
|
|
||||||
var parms = coinStats.confirmed.blocks[b].split(':');
|
|
||||||
if (parms[4] != null && parms[4] > 0) {
|
|
||||||
console.log(fancyTimestamp(parseInt(parms[4]), true));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
allCoinStats[coinStats.name] = (coinStats);
|
allCoinStats[coinStats.name] = (coinStats);
|
||||||
}
|
}
|
||||||
|
// sort pools alphabetically
|
||||||
|
allCoinStats = sortPoolsByName(allCoinStats);
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -419,6 +437,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
shares: workerShares,
|
shares: workerShares,
|
||||||
invalidshares: 0,
|
invalidshares: 0,
|
||||||
currRoundShares: 0,
|
currRoundShares: 0,
|
||||||
|
currRoundTime: 0,
|
||||||
hashrate: null,
|
hashrate: null,
|
||||||
hashrateString: null,
|
hashrateString: null,
|
||||||
luckDays: null,
|
luckDays: null,
|
||||||
|
@ -436,6 +455,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
shares: workerShares,
|
shares: workerShares,
|
||||||
invalidshares: 0,
|
invalidshares: 0,
|
||||||
currRoundShares: 0,
|
currRoundShares: 0,
|
||||||
|
currRoundTime: 0,
|
||||||
hashrate: null,
|
hashrate: null,
|
||||||
hashrateString: null,
|
hashrateString: null,
|
||||||
luckDays: null,
|
luckDays: null,
|
||||||
|
@ -455,6 +475,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
shares: 0,
|
shares: 0,
|
||||||
invalidshares: -workerShares,
|
invalidshares: -workerShares,
|
||||||
currRoundShares: 0,
|
currRoundShares: 0,
|
||||||
|
currRoundTime: 0,
|
||||||
hashrate: null,
|
hashrate: null,
|
||||||
hashrateString: null,
|
hashrateString: null,
|
||||||
luckDays: null,
|
luckDays: null,
|
||||||
|
@ -472,6 +493,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
shares: 0,
|
shares: 0,
|
||||||
invalidshares: -workerShares,
|
invalidshares: -workerShares,
|
||||||
currRoundShares: 0,
|
currRoundShares: 0,
|
||||||
|
currRoundTime: 0,
|
||||||
hashrate: null,
|
hashrate: null,
|
||||||
hashrateString: null,
|
hashrateString: null,
|
||||||
luckDays: null,
|
luckDays: null,
|
||||||
|
@ -510,6 +532,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
portalStats.algos[algo].workers += Object.keys(coinStats.workers).length;
|
portalStats.algos[algo].workers += Object.keys(coinStats.workers).length;
|
||||||
|
|
||||||
var _shareTotal = parseFloat(0);
|
var _shareTotal = parseFloat(0);
|
||||||
|
var _maxTimeShare = parseFloat(0);
|
||||||
for (var worker in coinStats.currentRoundShares) {
|
for (var worker in coinStats.currentRoundShares) {
|
||||||
var miner = worker.split(".")[0];
|
var miner = worker.split(".")[0];
|
||||||
if (miner in coinStats.miners) {
|
if (miner in coinStats.miners) {
|
||||||
|
@ -520,7 +543,20 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
}
|
}
|
||||||
_shareTotal += parseFloat(coinStats.currentRoundShares[worker]);
|
_shareTotal += parseFloat(coinStats.currentRoundShares[worker]);
|
||||||
}
|
}
|
||||||
|
for (var worker in coinStats.currentRoundTimes) {
|
||||||
|
var time = parseFloat(coinStats.currentRoundTimes[worker]);
|
||||||
|
if (_maxTimeShare < time)
|
||||||
|
_maxTimeShare = time;
|
||||||
|
|
||||||
|
var miner = worker.split(".")[0];
|
||||||
|
if (miner in coinStats.miners) {
|
||||||
|
coinStats.miners[miner].currRoundTime += parseFloat(coinStats.currentRoundTimes[worker]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
coinStats.shareCount = _shareTotal;
|
coinStats.shareCount = _shareTotal;
|
||||||
|
coinStats.maxRoundTime = _maxTimeShare;
|
||||||
|
coinStats.maxRoundTimeString = readableSeconds(_maxTimeShare);
|
||||||
|
|
||||||
for (var worker in coinStats.workers) {
|
for (var worker in coinStats.workers) {
|
||||||
var _workerRate = shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow;
|
var _workerRate = shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow;
|
||||||
|
@ -559,6 +595,7 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
delete saveStats.pools[pool].pending;
|
delete saveStats.pools[pool].pending;
|
||||||
delete saveStats.pools[pool].confirmed;
|
delete saveStats.pools[pool].confirmed;
|
||||||
delete saveStats.pools[pool].currentRoundShares;
|
delete saveStats.pools[pool].currentRoundShares;
|
||||||
|
delete saveStats.pools[pool].currentRoundTimes;
|
||||||
delete saveStats.pools[pool].payments;
|
delete saveStats.pools[pool].payments;
|
||||||
delete saveStats.pools[pool].miners;
|
delete saveStats.pools[pool].miners;
|
||||||
});
|
});
|
||||||
|
@ -591,11 +628,22 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) {
|
function sortBlocks(a, b) {
|
||||||
var as = a.split(":");
|
var as = parseInt(a.split(":")[2]);
|
||||||
var bs = b.split(":");
|
var bs = parseInt(b.split(":")[2]);
|
||||||
if (as[2] > bs[2]) return -1;
|
if (as > bs) return -1;
|
||||||
if (as[2] < bs[2]) return 1;
|
if (as < bs) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,13 @@ module.exports = function(logger){
|
||||||
// if an html file was changed reload it
|
// if an html file was changed reload it
|
||||||
/* requires node-watch 0.5.0 or newer */
|
/* requires node-watch 0.5.0 or newer */
|
||||||
watch(['./website', './website/pages'], function(evt, filename){
|
watch(['./website', './website/pages'], function(evt, filename){
|
||||||
var basename = path.basename(filename);
|
var basename;
|
||||||
|
// support older versions of node-watch automatically
|
||||||
|
if (!filename && evt)
|
||||||
|
basename = path.basename(evt);
|
||||||
|
else
|
||||||
|
basename = path.basename(filename);
|
||||||
|
|
||||||
if (basename in pageFiles){
|
if (basename in pageFiles){
|
||||||
readPageFiles([basename]);
|
readPageFiles([basename]);
|
||||||
logger.special(logSystem, 'Server', 'Reloaded file ' + basename);
|
logger.special(logSystem, 'Server', 'Reloaded file ' + basename);
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
"paymentProcessing": {
|
"paymentProcessing": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"paymentMode": "prop",
|
||||||
|
"_comment_paymentMode":"prop, pplnt",
|
||||||
"paymentInterval": 57,
|
"paymentInterval": 57,
|
||||||
"_comment_paymentInterval": "Interval in seconds to check and perform payments.",
|
"_comment_paymentInterval": "Interval in seconds to check and perform payments.",
|
||||||
"minimumPayment": 0.1,
|
"minimumPayment": 0.1,
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
"paymentProcessing": {
|
"paymentProcessing": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"paymentMode": "prop",
|
||||||
|
"_comment_paymentMode":"prop, pplnt",
|
||||||
"paymentInterval": 20,
|
"paymentInterval": 20,
|
||||||
"minimumPayment": 0.1,
|
"minimumPayment": 0.1,
|
||||||
"maxBlocksPerPayment": 3,
|
"maxBlocksPerPayment": 3,
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
"paymentProcessing": {
|
"paymentProcessing": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"paymentMode": "prop",
|
||||||
|
"_comment_paymentMode":"prop, pplnt",
|
||||||
"paymentInterval": 20,
|
"paymentInterval": 20,
|
||||||
"minimumPayment": 0.1,
|
"minimumPayment": 0.1,
|
||||||
"maxBlocksPerPayment": 1,
|
"maxBlocksPerPayment": 1,
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
"paymentProcessing": {
|
"paymentProcessing": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"paymentMode": "prop",
|
||||||
|
"_comment_paymentMode":"prop, pplnt",
|
||||||
"paymentInterval": 20,
|
"paymentInterval": 20,
|
||||||
"minimumPayment": 0.1,
|
"minimumPayment": 0.1,
|
||||||
"maxBlocksPerPayment": 3,
|
"maxBlocksPerPayment": 3,
|
||||||
|
|
|
@ -153,11 +153,15 @@
|
||||||
{{if (block[4] != null) { }}
|
{{if (block[4] != null) { }}
|
||||||
<span style="padding-left: 18px;"><small>{{=readableDate(block[4])}}</small></span>
|
<span style="padding-left: 18px;"><small>{{=readableDate(block[4])}}</small></span>
|
||||||
{{ } }}
|
{{ } }}
|
||||||
|
{{if (it.stats.pools[pool].pending.confirms) { }}
|
||||||
{{if (it.stats.pools[pool].pending.confirms[block[0]]) { }}
|
{{if (it.stats.pools[pool].pending.confirms[block[0]]) { }}
|
||||||
<span style="float:right; color: red;"><small>{{=it.stats.pools[pool].pending.confirms[block[0]]}} of 100</small></span>
|
<span style="float:right; color: red;"><small>{{=it.stats.pools[pool].pending.confirms[block[0]]}} of 100</small></span>
|
||||||
{{ } else { }}
|
{{ } else { }}
|
||||||
<span style="float:right; color: red;"><small>*PENDING*</small></span>
|
<span style="float:right; color: red;"><small>*PENDING*</small></span>
|
||||||
{{ } }}
|
{{ } }}
|
||||||
|
{{ } else { }}
|
||||||
|
<span style="float:right; color: red;"><small>*PENDING*</small></span>
|
||||||
|
{{ } }}
|
||||||
<div><i class="fa fa-gavel"></i><small>Mined By:</small> <a href="/workers/{{=block[3].split('.')[0]}}">{{=block[3]}}</a></div>
|
<div><i class="fa fa-gavel"></i><small>Mined By:</small> <a href="/workers/{{=block[3].split('.')[0]}}">{{=block[3]}}</a></div>
|
||||||
</div>
|
</div>
|
||||||
{{ blockscomb.push(block);}}
|
{{ blockscomb.push(block);}}
|
||||||
|
|
Loading…
Reference in New Issue