From 4a651dec89017515add2b7712bc295079b3395a7 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 27 Mar 2014 16:29:43 -0600 Subject: [PATCH] Payment processing seems to be working well --- README.md | 2 +- libs/paymentProcessor.js | 213 ++++++++++++++++++++++++------------- libs/shareProcessor.js | 2 +- website/index.html | 2 + website/static/favicon.png | Bin 0 -> 413 bytes 5 files changed, 142 insertions(+), 77 deletions(-) create mode 100644 website/static/favicon.png diff --git a/README.md b/README.md index 898b602..6660c1b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index ead7e79..d805771 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -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); }; \ No newline at end of file diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index a085a56..9534189 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -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){ diff --git a/website/index.html b/website/index.html index 1371b95..35ac208 100644 --- a/website/index.html +++ b/website/index.html @@ -4,6 +4,8 @@ + + diff --git a/website/static/favicon.png b/website/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..738dbba65b9f7213a43c81781056b3d7119f89a4 GIT binary patch literal 413 zcmV;O0b>4%P)%~E8%pO%)-N3!TX5cdOd z0GfjWXk~dn)PnceEP+Oc0=46}0V#pOe0UxzRsd2%O&=;i4aFC~xbS*t+7KYu;`wA) ziVeSqhK>NSmIpvJ{iiYpg`xo=%N=N7F?why;B&xz8an`YnSjmre;%Cu4+8%_Jp2D` zb}Z4Bd|y@e|KIn|V7G2mF0;3q>y$H4eBc51bD8 zb#euL9RR{0OF;k>6<>Q?h;{%dxIzRzeqhvoFET2!PU1$Jp9Xa4>8P7*j z1H-tQ?gdC{Nb98FNf!+ygdX`F6=-Pe$ASZJI2M~)yuJnimf-2lJ1