mirror of https://github.com/BTCPrivate/z-nomp.git
merge latest fixes and fee withdrawal functionality
This commit is contained in:
commit
d3699b3d08
|
@ -20,7 +20,7 @@
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"siteTitle": "Cryppit",
|
"siteTitle": "Cryppit",
|
||||||
"port": 80,
|
"port": 80,
|
||||||
"statUpdateInterval": 5,
|
"statUpdateInterval": 1.5,
|
||||||
"hashrateWindow": 600
|
"hashrateWindow": 600
|
||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
|
|
3
init.js
3
init.js
|
@ -2,6 +2,7 @@ var fs = require('fs');
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
var cluster = require('cluster');
|
var cluster = require('cluster');
|
||||||
|
|
||||||
|
require('./libs/algoProperties.js');
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var posix = require('posix');
|
var posix = require('posix');
|
||||||
|
@ -183,7 +184,7 @@ var startPaymentProcessor = function(poolConfigs){
|
||||||
var enabledForAny = false;
|
var enabledForAny = false;
|
||||||
for (var pool in poolConfigs){
|
for (var pool in poolConfigs){
|
||||||
var p = poolConfigs[pool];
|
var p = poolConfigs[pool];
|
||||||
var enabled = p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled;
|
var enabled = !p.disabled && p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled;
|
||||||
if (enabled){
|
if (enabled){
|
||||||
enabledForAny = true;
|
enabledForAny = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
global.algos = {
|
||||||
|
|
||||||
|
};
|
||||||
|
//lets put all algo related properties in here as a global object. also put this in stratum module then borrow it for the portal.
|
|
@ -4,6 +4,8 @@ var colors = require('colors');
|
||||||
|
|
||||||
var severityToColor = function(severity, text) {
|
var severityToColor = function(severity, text) {
|
||||||
switch(severity) {
|
switch(severity) {
|
||||||
|
case 'special':
|
||||||
|
return text.cyan.underline;
|
||||||
case 'debug':
|
case 'debug':
|
||||||
return text.green;
|
return text.green;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
|
@ -19,7 +21,8 @@ var severityToColor = function(severity, text) {
|
||||||
var severityValues = {
|
var severityValues = {
|
||||||
'debug': 1,
|
'debug': 1,
|
||||||
'warning': 2,
|
'warning': 2,
|
||||||
'error': 3
|
'error': 3,
|
||||||
|
'special': 4
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,11 +40,13 @@ function SetupForPool(logger, poolOptions){
|
||||||
|
|
||||||
daemon.cmd('validateaddress', [poolOptions.address], function(result){
|
daemon.cmd('validateaddress', [poolOptions.address], function(result){
|
||||||
if (!result[0].response || !result[0].response.ismine){
|
if (!result[0].response || !result[0].response.ismine){
|
||||||
logger.error(logSystem, logComponent, 'Daemon does not own pool address - payment processing can not be done with this daemon');
|
logger.error(logSystem, logComponent,
|
||||||
|
'Daemon does not own pool address - payment processing can not be done with this daemon');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).once('connectionFailed', function(error){
|
}).once('connectionFailed', function(error){
|
||||||
logger.error(logSystem, logComponent, 'Failed to connect to daemon for payment processing: ' + JSON.stringify(error));
|
logger.error(logSystem, logComponent, 'Failed to connect to daemon for payment processing: ' +
|
||||||
|
JSON.stringify(error));
|
||||||
}).on('error', function(error){
|
}).on('error', function(error){
|
||||||
logger.error(logSystem, logComponent);
|
logger.error(logSystem, logComponent);
|
||||||
}).init();
|
}).init();
|
||||||
|
@ -73,6 +75,15 @@ function SetupForPool(logger, poolOptions){
|
||||||
connectToRedis();
|
connectToRedis();
|
||||||
|
|
||||||
|
|
||||||
|
/* Number.toFixed gives us the decimal places we want, but as a string. parseFloat turns it back into number
|
||||||
|
we don't care about trailing zeros in this case. */
|
||||||
|
var toPrecision = function(value, precision){
|
||||||
|
return parseFloat(value.toFixed(precision));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Deal with numbers in smallest possible units (satoshis) as much as possible. This greatly helps with accuracy
|
||||||
|
when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */
|
||||||
|
|
||||||
var processPayments = function(){
|
var processPayments = function(){
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
@ -120,13 +131,15 @@ function SetupForPool(logger, poolOptions){
|
||||||
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
|
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
|
||||||
|
|
||||||
if (error || !txDetails){
|
if (error || !txDetails){
|
||||||
callback('Check finished - daemon rpc error with batch gettransactions ' + JSON.stringify(error));
|
callback('Check finished - daemon rpc error with batch gettransactions ' +
|
||||||
|
JSON.stringify(error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
txDetails = txDetails.filter(function(tx){
|
txDetails = txDetails.filter(function(tx){
|
||||||
if (tx.error || !tx.result){
|
if (tx.error || !tx.result){
|
||||||
logger.error(logSystem, logComponent, 'error with requesting transaction from block daemon: ' + JSON.stringify(tx));
|
logger.error(logSystem, logComponent,
|
||||||
|
'error with requesting transaction from block daemon: ' + JSON.stringify(tx));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -135,11 +148,13 @@ function SetupForPool(logger, poolOptions){
|
||||||
|
|
||||||
var magnitude;
|
var magnitude;
|
||||||
|
|
||||||
|
//Filter out all rounds that are immature (not confirmed or orphaned yet)
|
||||||
rounds = rounds.filter(function(r){
|
rounds = rounds.filter(function(r){
|
||||||
var tx = txDetails.filter(function(tx){return tx.result.txid === r.txHash})[0];
|
var tx = txDetails.filter(function(tx){return tx.result.txid === r.txHash})[0];
|
||||||
|
|
||||||
if (!tx){
|
if (!tx){
|
||||||
logger.error(logSystem, logComponent, 'daemon did not give us back a transaction that we asked for: ' + r.txHash);
|
logger.error(logSystem, logComponent,
|
||||||
|
'daemon did not give us back a transaction that we asked for: ' + r.txHash);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,15 +163,25 @@ function SetupForPool(logger, poolOptions){
|
||||||
if (r.category === 'generate'){
|
if (r.category === 'generate'){
|
||||||
r.amount = tx.result.amount;
|
r.amount = tx.result.amount;
|
||||||
|
|
||||||
|
/* Here we calculate the smallest unit in this coin's currency; the 'satoshi'.
|
||||||
|
The rpc.getblocktemplate.amount tells us how much we get in satoshis, while the
|
||||||
|
rpc.gettransaction.amount tells us how much we get in whole coin units. Therefore,
|
||||||
|
we simply divide the two to get the magnitude. I don't know math, there is probably
|
||||||
|
a better term than 'magnitude'. Sue me or do a pull request to fix it. */
|
||||||
var roundMagnitude = r.reward / r.amount;
|
var roundMagnitude = r.reward / r.amount;
|
||||||
|
|
||||||
if (!magnitude){
|
if (!magnitude){
|
||||||
magnitude = roundMagnitude;
|
magnitude = roundMagnitude;
|
||||||
|
|
||||||
if (roundMagnitude % 10 !== 0)
|
if (roundMagnitude % 10 !== 0)
|
||||||
logger.error(logSystem, logComponent, 'Satosihis in coin is not divisible by 10 which is very odd');
|
logger.error(logSystem, logComponent,
|
||||||
|
'Satosihis in coin is not divisible by 10 which is very odd');
|
||||||
}
|
}
|
||||||
else if (magnitude != roundMagnitude){
|
else if (magnitude != roundMagnitude){
|
||||||
logger.error(logSystem, logComponent, 'Magnitude in a round was different than in another round. HUGE PROBLEM.');
|
/* Magnitude for a coin should ALWAYS be the same. For BTC and most coins there are
|
||||||
|
100,000,000 satoshis in one coin unit. */
|
||||||
|
logger.error(logSystem, logComponent,
|
||||||
|
'Magnitude in a round was different than in another round. HUGE PROBLEM.');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -200,12 +225,18 @@ function SetupForPool(logger, poolOptions){
|
||||||
var workerShares = allWorkerShares[i];
|
var workerShares = allWorkerShares[i];
|
||||||
|
|
||||||
if (round.category === 'orphan'){
|
if (round.category === 'orphan'){
|
||||||
|
/* Each block that gets orphaned, all the shares go into the current round so that miners
|
||||||
|
still get a reward for their work. This seems unfair to those that just started mining
|
||||||
|
during this current round, but over time it balances out and rewards loyal miners. */
|
||||||
Object.keys(workerShares).forEach(function(worker){
|
Object.keys(workerShares).forEach(function(worker){
|
||||||
orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', worker, workerShares[worker]]);
|
orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent',
|
||||||
|
worker, workerShares[worker]]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (round.category === 'generate'){
|
else if (round.category === 'generate'){
|
||||||
|
|
||||||
|
/* We found a confirmed block! Now get the reward for it and calculate how much
|
||||||
|
we owe each miner based on the shares they submitted during that block round. */
|
||||||
var reward = round.reward * (1 - processingConfig.feePercent);
|
var reward = round.reward * (1 - processingConfig.feePercent);
|
||||||
|
|
||||||
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||||
|
@ -272,6 +303,8 @@ function SetupForPool(logger, poolOptions){
|
||||||
var balanceUpdateCommands = [];
|
var balanceUpdateCommands = [];
|
||||||
var workerPayoutsCommand = [];
|
var workerPayoutsCommand = [];
|
||||||
|
|
||||||
|
/* Here we add up all workers' previous unpaid balances plus their current rewards as we are
|
||||||
|
about to check if they reach the payout threshold. */
|
||||||
for (var worker in workerRewards){
|
for (var worker in workerRewards){
|
||||||
workerPayments[worker] = ((workerPayments[worker] || 0) + workerRewards[worker]);
|
workerPayments[worker] = ((workerPayments[worker] || 0) + workerRewards[worker]);
|
||||||
}
|
}
|
||||||
|
@ -279,16 +312,32 @@ function SetupForPool(logger, poolOptions){
|
||||||
workerPayments[worker] = ((workerPayments[worker] || 0) + workerBalances[worker]);
|
workerPayments[worker] = ((workerPayments[worker] || 0) + workerBalances[worker]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Here we check if any of the workers reached their payout threshold, or delete them from the
|
||||||
|
pending payment ledger (the workerPayments object). */
|
||||||
if (Object.keys(workerPayments).length > 0){
|
if (Object.keys(workerPayments).length > 0){
|
||||||
var coinPrecision = magnitude.toString().length - 1;
|
var coinPrecision = magnitude.toString().length - 1;
|
||||||
for (var worker in workerPayments){
|
for (var worker in workerPayments){
|
||||||
if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){
|
if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){
|
||||||
balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, workerRewards[worker]]);
|
/* The workers total earnings (balance + current reward) was not enough to warrant
|
||||||
|
a transaction, so we will store their balance in the database. Next time they
|
||||||
|
are rewarded it might reach the payout threshold. */
|
||||||
|
balanceUpdateCommands.push([
|
||||||
|
'hincrby',
|
||||||
|
coin + '_balances',
|
||||||
|
worker,
|
||||||
|
workerRewards[worker]
|
||||||
|
]);
|
||||||
delete workerPayments[worker];
|
delete workerPayments[worker];
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
//If worker had a balance that is about to be paid out, subtract it from the database
|
||||||
if (workerBalances[worker] !== 0){
|
if (workerBalances[worker] !== 0){
|
||||||
balanceUpdateCommands.push(['hincrby', coin + '_balances', worker, -1 * workerBalances[worker]]);
|
balanceUpdateCommands.push([
|
||||||
|
'hincrby',
|
||||||
|
coin + '_balances',
|
||||||
|
worker,
|
||||||
|
-1 * workerBalances[worker]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
var rewardInPrecision = (workerRewards[worker] / magnitude).toFixed(coinPrecision);
|
var rewardInPrecision = (workerRewards[worker] / magnitude).toFixed(coinPrecision);
|
||||||
workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', worker, rewardInPrecision]);
|
workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', worker, rewardInPrecision]);
|
||||||
|
@ -299,19 +348,23 @@ function SetupForPool(logger, poolOptions){
|
||||||
}
|
}
|
||||||
|
|
||||||
// txfee included in feeAmountToBeCollected
|
// txfee included in feeAmountToBeCollected
|
||||||
var feeAmountToBeCollected = parseFloat((toBePaid / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision));
|
var leftOver = toBePaid / (1 - processingConfig.feePercent);
|
||||||
|
var feeAmountToBeCollected = toPrecision(leftOver * processingConfig.feePercent, coinPrecision);
|
||||||
var balanceLeftOver = totalBalance - toBePaid - feeAmountToBeCollected;
|
var balanceLeftOver = totalBalance - toBePaid - feeAmountToBeCollected;
|
||||||
var minReserveSatoshis = processingConfig.minimumReserve * magnitude;
|
var minReserveSatoshis = processingConfig.minimumReserve * magnitude;
|
||||||
if (balanceLeftOver < minReserveSatoshis){
|
if (balanceLeftOver < minReserveSatoshis){
|
||||||
|
/* TODO: Need to convert all these variables into whole coin units before displaying because
|
||||||
callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + toBePaid +
|
humans aren't good at reading satoshi units. */
|
||||||
' and collect ' + feeAmountToBeCollected + ' as fees' +
|
callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' +
|
||||||
|
toBePaid + ' and collect ' + feeAmountToBeCollected + ' as fees' +
|
||||||
' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver +
|
' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver +
|
||||||
', needs to be at least ' + minReserveSatoshis);
|
', needs to be at least ' + minReserveSatoshis);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Move pending blocks into either orphan for confirmed sets, and delete their no longer
|
||||||
|
required round/shares data. */
|
||||||
var movePendingCommands = [];
|
var movePendingCommands = [];
|
||||||
var roundsToDelete = [];
|
var roundsToDelete = [];
|
||||||
rounds.forEach(function(r){
|
rounds.forEach(function(r){
|
||||||
|
@ -357,7 +410,7 @@ function SetupForPool(logger, poolOptions){
|
||||||
var finalizeRedisTx = function(){
|
var finalizeRedisTx = function(){
|
||||||
redisClient.multi(finalRedisCommands).exec(function(error, results){
|
redisClient.multi(finalRedisCommands).exec(function(error, results){
|
||||||
if (error){
|
if (error){
|
||||||
callback('Check finished - error with final redis commands for cleaning up ' + JSON.stringify(error));
|
callback('Error with final redis commands for cleaning up ' + JSON.stringify(error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.debug(logSystem, logComponent, 'Payments processing performed an interval');
|
logger.debug(logSystem, logComponent, 'Payments processing performed an interval');
|
||||||
|
@ -369,22 +422,27 @@ function SetupForPool(logger, poolOptions){
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
||||||
|
//This is how many decimal places to round a coin down to
|
||||||
var coinPrecision = magnitude.toString().length - 1;
|
var coinPrecision = magnitude.toString().length - 1;
|
||||||
var addressAmounts = {};
|
var addressAmounts = {};
|
||||||
var totalAmountUnits = 0;
|
var totalAmountUnits = 0;
|
||||||
for (var address in workerPayments){
|
for (var address in workerPayments){
|
||||||
var coinUnits = parseFloat((workerPayments[address] / magnitude).toFixed(coinPrecision));
|
var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision);
|
||||||
addressAmounts[address] = coinUnits;
|
addressAmounts[address] = coinUnits;
|
||||||
totalAmountUnits += coinUnits;
|
totalAmountUnits += coinUnits;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(logSystem, logComponent, 'Payments about to be sent to: ' + JSON.stringify(addressAmounts));
|
logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts));
|
||||||
|
|
||||||
daemon.cmd('sendmany', ['', addressAmounts], function(results){
|
daemon.cmd('sendmany', ['', addressAmounts], function(results){
|
||||||
|
|
||||||
if (results[0].error){
|
if (results[0].error){
|
||||||
callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error));
|
callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizeRedisTx();
|
finalizeRedisTx();
|
||||||
|
|
||||||
var totalWorkers = Object.keys(workerPayments).length;
|
var totalWorkers = Object.keys(workerPayments).length;
|
||||||
logger.debug(logSystem, logComponent, 'Payments sent, a total of ' + totalAmountUnits + ' ' + poolOptions.coin.symbol +
|
logger.debug(logSystem, logComponent, 'Payments sent, a total of ' + totalAmountUnits + ' ' + poolOptions.coin.symbol +
|
||||||
' was sent to ' + totalWorkers + ' miners');
|
' was sent to ' + totalWorkers + ' miners');
|
||||||
|
|
Loading…
Reference in New Issue