Payment processing seems to be working well

This commit is contained in:
Matt 2014-03-27 16:29:43 -06:00
parent fc56d94092
commit 4a651dec89
5 changed files with 142 additions and 77 deletions

View File

@ -1,4 +1,4 @@
# NOMP
# NOMP ![NOMP Logo](http://zone117x.github.io/node-open-mining-portal/logo.svg "NOMP Logo")
#### Node Open Mining Portal
This portal is an extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool written

View File

@ -62,9 +62,9 @@ function SetupForPool(logger, poolOptions){
redisClient.on('ready', function(){
clearTimeout(reconnectTimeout);
logger.debug(logSystem, logComponent, 'Successfully connected to redis database');
}).on('error', function(err){
})/*).on('error', function(err){
logger.error(logSystem, logComponent, 'Redis client had an error: ' + JSON.stringify(err))
}).on('end', function(){
})*/.on('end', function(){
logger.error(logSystem, logComponent, 'Connection to redis database as been ended');
logger.warning(logSystem, logComponent, 'Trying reconnection to redis in 3 seconds...');
reconnectTimeout = setTimeout(function(){
@ -86,6 +86,10 @@ function SetupForPool(logger, poolOptions){
when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */
var processPayments = function(){
var startPaymentProcess = Date.now();
async.waterfall([
/* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted
@ -108,10 +112,10 @@ function SetupForPool(logger, poolOptions){
var details = r.split(':');
return {
category: details[0].category,
txHash: details[0],
height: details[1],
reward: details[2],
solution: details[3],
solution: details[0],
txHash: details[1],
height: details[2],
reward: details[3],
serialized: r
};
});
@ -120,6 +124,29 @@ function SetupForPool(logger, poolOptions){
});
},
/* First get data from all pending blocks via batch RPC call, we need the coinbase txHash. */
/*(function(rounds, callback){
var batchRPCcommand = rounds.map(function(r){
return ['getblock', [r.solution]];
});
daemon.batchCmd(batchRPCcommand, function(error, blocks) {
if (error || !blocks) {
callback('Check finished - daemon rpc error with batch gettransactions ' +
JSON.stringify(error));
return;
}
blocks.forEach(function (b, i) {
if (b.error || b.result.hash !== rounds[i].solution){
logger.error(logSystem, logComponent, "Did daemon drop a block? " + rounds[i].solution);
return;
}
rounds[i].txHash = b.result.tx[0];
});
callback(null, rounds);
});
},*/
/* 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. */
@ -138,23 +165,39 @@ function SetupForPool(logger, poolOptions){
}
txDetails.forEach(function(tx, i){
if (tx.error && tx.error.code === -5){
/* Block was dropped from coin daemon even after it happily accepted it earlier.
Must be a bug in the daemon code or maybe only something that happens in testnet.
We handle this by treating it like an orphaned block. */
logger.error(logSystem, logComponent,
'Daemon dropped a previously valid block - we are treating it as an orphaned block');
var round = rounds[i];
//These changes to the tx will convert it from dropped to orphan
tx.result = {
txid: rounds[i].txHash,
details: [{category: 'orphan'}]
};
if (tx.error && tx.error.code === -5){
/* Block was dropped from coin daemon even after it happily accepted it earlier. */
//If we find another block at the same height then this block was drop-kicked orphaned
var dropKicked = !!rounds.filter(function(r){
return r.height === round.height && r.solution !== round.solution && r.category !== 'dropkicked';
}).length;
if (dropKicked){
logger.warning(logSystem, logComponent,
'A block was drop-kicked orphaned'
+ ' - we found a better block at the same height, solution '
+ round.solution + " round " + round.height);
round.category = 'dropkicked';
}
else{
/* We have no other blocks that match this height so convert to orphan in order for
shares from the round to be rewarded. */
round.category = 'orphan';
}
}
else if (tx.error || !tx.result){
logger.error(logSystem, logComponent,
'error with requesting transaction from block daemon: ' + JSON.stringify(tx));
}
else{
round.category = tx.result.details[0].category;
if (round.category === 'generate')
round.amount = tx.result.amount;
}
});
@ -162,44 +205,37 @@ function SetupForPool(logger, poolOptions){
//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];
switch (r.category) {
if (!tx){
logger.error(logSystem, logComponent,
'daemon did not give us back a transaction that we asked for: ' + r.txHash);
return;
}
case 'generate':
/* 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;
r.category = tx.result.details[0].category;
if (!magnitude) {
magnitude = roundMagnitude;
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)
if (roundMagnitude % 10 !== 0)
logger.error(logSystem, logComponent,
'Satosihis in coin is not divisible by 10 which is very odd');
}
else if (magnitude != roundMagnitude) {
/* 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,
'Satosihis in coin is not divisible by 10 which is very odd');
}
else if (magnitude != roundMagnitude){
/* 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;
}
else if (r.category === 'orphan')
return true;
'Magnitude in a round was different than in another round. HUGE PROBLEM.');
}
return true;
case 'dropkicked':
case 'orphan':
return true;
default:
return false;
}
});
@ -236,32 +272,42 @@ function SetupForPool(logger, poolOptions){
rounds.forEach(function(round, i){
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]]);
});
if (!workerShares){
logger.error(logSystem, logComponent, 'No worker shares for round: '
+ round.height + ' solution: ' + round.solution);
return;
}
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);
switch (round.category){
case '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]]);
});
break;
var totalShares = Object.keys(workerShares).reduce(function(p, c){
return p + parseInt(workerShares[c])
}, 0);
case '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);
for (var worker in workerShares){
var percent = parseInt(workerShares[worker]) / totalShares;
var workerRewardTotal = Math.floor(reward * percent);
if (!(worker in workerRewards)) workerRewards[worker] = 0;
workerRewards[worker] += workerRewardTotal;
}
var totalShares = Object.keys(workerShares).reduce(function(p, c){
return p + parseInt(workerShares[c])
}, 0);
for (var worker in workerShares){
var percent = parseInt(workerShares[worker]) / totalShares;
var workerRewardTotal = Math.floor(reward * percent);
if (!(worker in workerRewards)) workerRewards[worker] = 0;
workerRewards[worker] += workerRewardTotal;
}
break;
}
});
callback(null, rounds, magnitude, workerRewards, orphanMergeCommands);
@ -380,7 +426,14 @@ function SetupForPool(logger, poolOptions){
var movePendingCommands = [];
var roundsToDelete = [];
rounds.forEach(function(r){
var destinationSet = r.category === 'orphan' ? '_blocksOrphaned' : '_blocksConfirmed';
var destinationSet = (function(){
switch(r.category){
case 'orphan': return '_blocksOrphaned';
case 'generate': return '_blocksConfirmed';
case 'dropkicked': return '_blocksDropKicked';
}
})();
movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]);
roundsToDelete.push(coin + '_shares:round' + r.height)
});
@ -477,11 +530,15 @@ function SetupForPool(logger, poolOptions){
}
}
], function(error, result){
var paymentProcessTime = Date.now() - startPaymentProcess;
if (error)
logger.debug(logSystem, logComponent, error);
logger.debug(logSystem, logComponent, '[' + paymentProcessTime + 'ms] ' + error);
else{
logger.debug(logSystem, logComponent, result);
logger.debug(logSystem, logComponent, '[' + paymentProcessTime + 'ms] ' + result);
// not sure if we need some time to let daemon update the wallet balance
setTimeout(withdrawalProfit, 1000);
}
@ -523,7 +580,13 @@ function SetupForPool(logger, poolOptions){
};
setInterval(processPayments, processingConfig.paymentInterval * 1000);
setInterval(function(){
try {
processPayments();
} catch(e){
throw e;
}
}, processingConfig.paymentInterval * 1000);
setTimeout(processPayments, 100);
};

View File

@ -69,7 +69,7 @@ module.exports = function(logger, poolConfig){
if (isValidBlock){
redisCommands.push(['rename', coin + '_shares:roundCurrent', coin + '_shares:round' + shareData.height]);
redisCommands.push(['sadd', coin + '_blocksPending', shareData.tx + ':' + shareData.height + ':' + shareData.reward + ':' + shareData.solution]);
redisCommands.push(['sadd', coin + '_blocksPending', [shareData.solution, shareData.tx, shareData.height, shareData.reward].join(':')]);
redisCommands.push(['hincrby', coin + '_stats', 'validBlocks', 1]);
}
else if (shareData.solution){

View File

@ -4,6 +4,8 @@
<head>
<meta charset="utf-8">
<link rel="icon" type="image/png" href="/static/favicon.png"/>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.0.3/css/font-awesome.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/pure/0.4.2/pure-min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/Chart.js/0.2.0/Chart.min.js"></script>

BIN
website/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B