Added lots more comments and tried to squeeze all comments and code into github's max page width

This commit is contained in:
Matt 2014-03-27 00:56:36 -06:00
parent 024cc7492f
commit 634f6c842f

View File

@ -40,11 +40,13 @@ function SetupForPool(logger, poolOptions){
daemon.cmd('validateaddress', [poolOptions.address], function(result){
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){
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){
logger.error(logSystem, logComponent);
}).init();
@ -73,6 +75,15 @@ function SetupForPool(logger, poolOptions){
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(){
async.waterfall([
@ -120,13 +131,15 @@ function SetupForPool(logger, poolOptions){
daemon.batchCmd(batchRPCcommand, function(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;
}
txDetails = txDetails.filter(function(tx){
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 true;
@ -135,11 +148,13 @@ function SetupForPool(logger, poolOptions){
var magnitude;
//Filter out all rounds that are immature (not confirmed or orphaned yet)
rounds = rounds.filter(function(r){
var tx = txDetails.filter(function(tx){return tx.result.txid === r.txHash})[0];
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;
}
@ -148,15 +163,25 @@ function SetupForPool(logger, poolOptions){
if (r.category === 'generate'){
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;
if (!magnitude){
magnitude = roundMagnitude;
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){
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;
}
@ -200,12 +225,18 @@ function SetupForPool(logger, poolOptions){
var workerShares = allWorkerShares[i];
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){
orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', worker, workerShares[worker]]);
orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent',
worker, workerShares[worker]]);
});
}
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 totalShares = Object.keys(workerShares).reduce(function(p, c){
@ -272,6 +303,8 @@ function SetupForPool(logger, poolOptions){
var balanceUpdateCommands = [];
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){
workerPayments[worker] = ((workerPayments[worker] || 0) + workerRewards[worker]);
}
@ -279,16 +312,32 @@ function SetupForPool(logger, poolOptions){
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){
var coinPrecision = magnitude.toString().length - 1;
for (var worker in workerPayments){
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];
}
else{
//If worker had a balance that is about to be paid out, subtract it from the database
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);
workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', worker, rewardInPrecision]);
@ -299,19 +348,23 @@ function SetupForPool(logger, poolOptions){
}
// 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 minReserveSatoshis = processingConfig.minimumReserve * magnitude;
if (balanceLeftOver < minReserveSatoshis){
callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + toBePaid +
' and collect ' + feeAmountToBeCollected + ' as fees' +
/* TODO: Need to convert all these variables into whole coin units before displaying because
humans aren't good at reading satoshi units. */
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 +
', needs to be at least ' + minReserveSatoshis);
return;
}
/* Move pending blocks into either orphan for confirmed sets, and delete their no longer
required round/shares data. */
var movePendingCommands = [];
var roundsToDelete = [];
rounds.forEach(function(r){
@ -357,7 +410,7 @@ function SetupForPool(logger, poolOptions){
var finalizeRedisTx = function(){
redisClient.multi(finalRedisCommands).exec(function(error, results){
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;
}
logger.debug(logSystem, logComponent, 'Payments processing performed an interval');
@ -369,30 +422,38 @@ function SetupForPool(logger, poolOptions){
}
else{
//This is how many decimal places to round a coin down to
var coinPrecision = magnitude.toString().length - 1;
var addressAmounts = {};
var totalAmountUnits = 0;
for (var address in workerPayments){
var coinUnits = parseFloat((workerPayments[address] / magnitude).toFixed(coinPrecision));
var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision);
addressAmounts[address] = 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){
if (results[0].error){
callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error));
return;
}
finalizeRedisTx();
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
daemon.cmd('getbalance', [''], function(results){
var balanceDiff = balanceBefore - results[0].response;
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;
daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){
if (results[0].error){
@ -426,15 +487,17 @@ function SetupForPool(logger, poolOptions){
logger.debug(logSystem, logComponent, 'Profit withdrawal started');
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
// then minimumReserve value. Because in this case even if feeCollectAccount account will have negative balance
// total wallet balance will be positive and feeCollectAccount account will be refilled during next payment processing.
// But to be as much accurate as we can we use getinfo command to retrieve minimum tx fee (paytxfee).
/* 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 then minimumReserve value. Because in this case even if feeCollectAccount account will have
negative balance total wallet balance will be positive and feeCollectAccount account will be refilled
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){
var 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;
} else {
paytxfee = result[0].response.paytxfee;
@ -452,10 +515,12 @@ function SetupForPool(logger, poolOptions){
daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){
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;
}
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);
});
}