Added lots more comments and tried to squeeze all comments and code into github's max page width
This commit is contained in:
parent
024cc7492f
commit
634f6c842f
@ -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,30 +422,38 @@ 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 +
|
|
||||||
' was sent to ' + totalWorkers + ' miners');
|
logger.debug(logSystem, logComponent, 'Payments sent, a total of ' + totalAmountUnits
|
||||||
|
+ ' ' + poolOptions.coin.symbol + ' was sent to ' + totalWorkers + ' miners');
|
||||||
|
|
||||||
setTimeout(function() { // not sure if we need some time to let daemon update the wallet balance
|
setTimeout(function() { // not sure if we need some time to let daemon update the wallet balance
|
||||||
daemon.cmd('getbalance', [''], function(results){
|
daemon.cmd('getbalance', [''], function(results){
|
||||||
var balanceDiff = balanceBefore - results[0].response;
|
var balanceDiff = balanceBefore - results[0].response;
|
||||||
var txFee = balanceDiff - totalAmountUnits;
|
var txFee = balanceDiff - totalAmountUnits;
|
||||||
var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision));
|
var leftOver = totalAmountUnits / (1 - processingConfig.feePercent);
|
||||||
|
var feeAmountUnits = toPrecision(leftOver * processingConfig.feePercent, coinPrecision);
|
||||||
var poolFees = feeAmountUnits - txFee;
|
var poolFees = feeAmountUnits - txFee;
|
||||||
daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){
|
daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){
|
||||||
if (results[0].error){
|
if (results[0].error){
|
||||||
@ -426,15 +487,17 @@ function SetupForPool(logger, poolOptions){
|
|||||||
logger.debug(logSystem, logComponent, 'Profit withdrawal started');
|
logger.debug(logSystem, logComponent, 'Profit withdrawal started');
|
||||||
daemon.cmd('getbalance', [processingConfig.feeCollectAccount], function(results){
|
daemon.cmd('getbalance', [processingConfig.feeCollectAccount], function(results){
|
||||||
|
|
||||||
// We have to pay some tx fee here too but maybe we shoudn't really care about it too much as long as fee is less
|
/* We have to pay some tx fee here too but maybe we shoudn't really care about it too much as long as fee
|
||||||
// then minimumReserve value. Because in this case even if feeCollectAccount account will have negative balance
|
is less then minimumReserve value. Because in this case even if feeCollectAccount account will have
|
||||||
// total wallet balance will be positive and feeCollectAccount account will be refilled during next payment processing.
|
negative balance total wallet balance will be positive and feeCollectAccount account will be refilled
|
||||||
// But to be as much accurate as we can we use getinfo command to retrieve minimum tx fee (paytxfee).
|
during next payment processing. But to be as much accurate as we can we use getinfo command to retrieve
|
||||||
|
minimum tx fee (paytxfee). */
|
||||||
daemon.cmd('getinfo', [], function(result){
|
daemon.cmd('getinfo', [], function(result){
|
||||||
|
|
||||||
var paytxfee;
|
var paytxfee;
|
||||||
if (!result[0].response || !result[0].response.paytxfee){
|
if (!result[0].response || !result[0].response.paytxfee){
|
||||||
logger.error(logSystem, logComponent, 'Daemon does not have paytxfee property on getinfo method results - withdrawal processing could be broken with this daemon');
|
logger.error(logSystem, logComponent,
|
||||||
|
'Daemon does not have paytxfee - withdrawal processing could be broken with this daemon');
|
||||||
paytxfee = 0;
|
paytxfee = 0;
|
||||||
} else {
|
} else {
|
||||||
paytxfee = result[0].response.paytxfee;
|
paytxfee = result[0].response.paytxfee;
|
||||||
@ -452,10 +515,12 @@ function SetupForPool(logger, poolOptions){
|
|||||||
|
|
||||||
daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){
|
daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){
|
||||||
if (results[0].error){
|
if (results[0].error){
|
||||||
logger.debug(logSystem, logComponent, 'Profit withdrawal finished - error with sendmany ' + JSON.stringify(results[0].error));
|
logger.debug(logSystem, logComponent,
|
||||||
|
'Profit withdrawal finished - error with sendmany ' + JSON.stringify(results[0].error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.debug(logSystem, logComponent, 'Profit sent, a total of ' + withdrawalAmount + ' ' + poolOptions.coin.symbol +
|
logger.debug(logSystem, logComponent,
|
||||||
|
'Profit sent, a total of ' + withdrawalAmount + ' ' + poolOptions.coin.symbol +
|
||||||
' was sent to ' + processingConfig.feeReceiveAddress);
|
' was sent to ' + processingConfig.feeReceiveAddress);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user