From 7fe215679a6d7f774c73919776ca0985a3b32a9d Mon Sep 17 00:00:00 2001 From: "Eugene@ubuntu" Date: Wed, 2 Apr 2014 14:56:13 +0400 Subject: [PATCH 1/3] fix payment redis final cleanout issue - possible double spending - optimized --- libs/paymentProcessor.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index a9edd14..af67f6a 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -493,6 +493,33 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); }, + /* Call redis to check if previous sendmany and/or redis cleanout commands completed successfully. + If sendmany worked fine but redis commands failed you HAVE TO run redis commands again + (manually) to prevent double payments. If sendmany failed too you can safely delete + coin + '_finalRedisCommands' string from redis to let pool calculate payments again. */ + function(magnitude, workerPayments, finalRedisCommands, callback) { + redisClient.get(coin + '_finalRedisCommands', function(error, reply) { + if (error){ + callback('Check finished - error with redis getting finalRedisCommands' + JSON.stringify(error)); + return; + } + if (reply) { + callback('Check finished - previous sendmany and/or redis cleanout commands failed - ' + reply); + return; + } else { + /* There was no error in previous sendmany and/or redis cleanout commands + so we can safely continue */ + redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { + if (error){ + callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); + return; + } + callback(null, magnitude, workerPayments, finalRedisCommands); + }); + } + }); + }, + function(magnitude, workerPayments, finalRedisCommands, callback){ //This does the final all-or-nothing atom transaction if block deamon sent payments From a9132319cc1c573882c819c31e5749d71ae7b0c4 Mon Sep 17 00:00:00 2001 From: "Eugene@ubuntu" Date: Thu, 3 Apr 2014 00:43:58 +0400 Subject: [PATCH 2/3] fix payment redis final cleanout issue - possible double spending - optimized - fixed --- libs/paymentProcessor.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 3f1f2cb..91bfeb4 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -470,27 +470,24 @@ function SetupForPool(logger, poolOptions, setupFinished){ if (orphanMergeCommands.length > 0) finalRedisCommands = finalRedisCommands.concat(orphanMergeCommands); - if (balanceUpdateCommands.length > 0) finalRedisCommands = finalRedisCommands.concat(balanceUpdateCommands); - if (workerPayoutsCommand.length > 0) finalRedisCommands = finalRedisCommands.concat(workerPayoutsCommand); - if (roundsToDelete.length > 0) finalRedisCommands.push(['del'].concat(roundsToDelete)); - if (toBePaid !== 0) finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', (toBePaid / magnitude).toFixed(coinPrecision)]); + finalRedisCommands.push(['del', coin + '_finalRedisCommands']); + finalRedisCommands.push(['bgsave']); callback(null, magnitude, workerPayments, finalRedisCommands); - }); }, From 086ace75d738d037779f2c5d4eed69cfef76aabf Mon Sep 17 00:00:00 2001 From: "Eugene@ubuntu" Date: Sat, 5 Apr 2014 05:02:52 +0400 Subject: [PATCH 3/3] fix payment redis final cleanout issue - possible double spending - optimized.v2 --- libs/paymentProcessor.js | 68 ++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 91bfeb4..3d3482a 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -52,6 +52,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var logSystem = 'Payments'; var logComponent = coin; + var processingPayments = true; var daemon; var redisClient; @@ -119,8 +120,28 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - - + /* Call redis to check if previous sendmany and/or redis cleanout commands completed successfully. + If sendmany worked fine but redis commands failed you HAVE TO run redis commands again + (manually) to prevent double payments. If sendmany failed too you can safely delete + coin + '_finalRedisCommands' string from redis to let pool calculate payments again. */ + function checkPreviousPaymentsStatus(callback) { + redisClient.get(coin + '_finalRedisCommands', function(error, reply) { + if (error){ + callback('Could not get finalRedisCommands - ' + JSON.stringify(error)); + return; + } + if (reply) { + callback('Payments stopped because of the critical error - failed commands saved in ' + + coin + '_finalRedisCommands redis set:\n' + reply); + return; + } else { + /* There was no error in previous sendmany and/or redis cleanout commands + so we can safely continue */ + processingPayments = false; + callback(); + } + }); + } /* Number.toFixed gives us the decimal places we want, but as a string. parseFloat turns it back into number @@ -140,6 +161,21 @@ function SetupForPool(logger, poolOptions, setupFinished){ async.waterfall([ + function(callback) { + if (processingPayments) { + checkPreviousPaymentsStatus(function(error){ + if (error) { + logger.error(logSystem, logComponent, error); + callback('Check finished - previous payments processing error'); + return; + } + callback(); + }); + return; + } + callback(); + }, + /* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted blocks. */ function(callback){ @@ -147,7 +183,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ redisClient.smembers(coin + '_blocksPending', function(error, results){ if (error){ - logger.error(logSystem, logComponent, 'Could get blocks from redis ' + JSON.stringify(error)); + logger.error(logSystem, logComponent, 'Could not get blocks from redis ' + JSON.stringify(error)); callback('Check finished - redis error for getting blocks'); return; } @@ -491,30 +527,14 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); }, - /* Call redis to check if previous sendmany and/or redis cleanout commands completed successfully. - If sendmany worked fine but redis commands failed you HAVE TO run redis commands again - (manually) to prevent double payments. If sendmany failed too you can safely delete - coin + '_finalRedisCommands' string from redis to let pool calculate payments again. */ function(magnitude, workerPayments, finalRedisCommands, callback) { - redisClient.get(coin + '_finalRedisCommands', function(error, reply) { + /* Save final redis cleanout commands in case something goes wrong during payments */ + redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { if (error){ - callback('Check finished - error with redis getting finalRedisCommands' + JSON.stringify(error)); + callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); return; } - if (reply) { - callback('Check finished - previous sendmany and/or redis cleanout commands failed - ' + reply); - return; - } else { - /* There was no error in previous sendmany and/or redis cleanout commands - so we can safely continue */ - redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { - if (error){ - callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); - return; - } - callback(null, magnitude, workerPayments, finalRedisCommands); - }); - } + callback(null, magnitude, workerPayments, finalRedisCommands); }); }, @@ -527,6 +547,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback('Error with final redis commands for cleaning up ' + JSON.stringify(error)); return; } + processingPayments = false; logger.debug(logSystem, logComponent, 'Payments processing performed an interval'); }); }; @@ -548,6 +569,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); + processingPayments = true; daemon.cmd('sendmany', ['', addressAmounts], function(results){ if (results[0].error){