From 4569f1d3c56100732750a30e4458c3282eb1a43b Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 13 Jun 2015 12:03:04 -0300 Subject: [PATCH] backoff time only active after backoffOffset --- lib/server.js | 23 +++-- test/integration/server.js | 203 ++++++++++++++++++++----------------- 2 files changed, 127 insertions(+), 99 deletions(-) diff --git a/lib/server.js b/lib/server.js index 4879f32..60856c1 100644 --- a/lib/server.js +++ b/lib/server.js @@ -53,8 +53,11 @@ function WalletService() { // Time after which a Tx proposal can be erased by any copayer. in seconds WalletService.deleteLockTime = 24 * 3600; -// Time a copayer need to wait to create a new TX after her tx previous proposal we rejected. (incremental). in seconds. -WalletService.backoffTime = 2 * 60; +// Allowed consecutive txp rejections before backoff is applied. +WalletService.backoffOffset = 3; + +// Time a copayer need to wait to create a new TX after her tx previous proposal we rejected. (incremental). in Minutes. +WalletService.backoffTimeMinutes = 2; // Fund scanning parameters WalletService.scanConfig = { @@ -750,18 +753,22 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) { self.storage.fetchLastTxs(self.walletId, copayerId, 5, function(err, txs) { if (err) return cb(err); - if (!txs.length) + if (!txs.length) return cb(null, true); - var lastRejections = _.takeWhile(txs, {status: 'rejected'}); + var lastRejections = _.takeWhile(txs, { + status: 'rejected' + }); - if (!lastRejections.length) + var exceededRejections = lastRejections.length - WalletService.backoffOffset; + if (exceededRejections <= 0) return cb(null, true); + var lastTxTs = txs[0].createdOn; var now = Math.floor(Date.now() / 1000); var timeSinceLastRejection = now - lastTxTs; - var backoffTime = Math.pow(WalletService.backoffTime,lastRejections.length); + var backoffTime = 60 * Math.pow(WalletService.backoffTimeMinutes, exceededRejections); if (timeSinceLastRejection <= backoffTime) log.debug('Not allowing to create TX: timeSinceLastRejection/backoffTime', timeSinceLastRejection, backoffTime); @@ -795,9 +802,9 @@ WalletService.prototype.createTx = function(opts, cb) { self._canCreateTx(self.copayerId, function(err, canCreate) { if (err) return cb(err); - if (!canCreate) + if (!canCreate) return cb(new ClientError('NOTALLOWEDTOCREATETX', 'Cannot create TX proposal during backoff time')); - + var copayer = wallet.getCopayer(self.copayerId); var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message, opts.payProUrl); if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) diff --git a/test/integration/server.js b/test/integration/server.js index 68808b0..038f725 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1675,43 +1675,67 @@ describe('Wallet service', function() { }); describe('#createTx backoff time', function() { - var server, wallet, txid; + var server, wallet; beforeEach(function(done) { helpers.createAndJoinWallet(2, 2, function(s, w) { server = s; wallet = w; - helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); - server.createTx(txOpts, function(err, tx) { - - should.not.exist(err); - should.exist(tx); - txid = tx.id; - done(); - }); - }); + done(); }); }); - it('should fail to create inmediatly after a rejection', function(done) { + it('should allow to create inmediatly after a 3 rejections', function(done) { async.series([ function(next) { - server.getPendingTxs({}, function(err, txs) { - var tx = txs[0]; - tx.id.should.equal(txid); - next(); - }); + async.each([0, 1, 2], function(i, a_next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + server.rejectTx({ + txProposalId: tx.id, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + a_next(); + }); + }); + }); + }, next); }, function(next) { - server.rejectTx({ - txProposalId: txid, - reason: 'some reason', - }, function(err) { - should.not.exist(err); - next(); + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + next(); + }); }); + } + ], done); + }); + + it('should NOT allow to create inmediatly after a 4 rejections', function(done) { + async.series([ + + function(next) { + async.each([0, 1, 2, 3], function(i, a_next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + server.rejectTx({ + txProposalId: tx.id, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + a_next(); + }); + }); + }); + }, next); }, function(next) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() { @@ -1725,87 +1749,42 @@ describe('Wallet service', function() { ], done); }); - it('should allow to create after backoffTime', function(done) { + it('should allow to create inmediatly after a 4 rejections after backofftime', function(done) { async.series([ function(next) { - server.getPendingTxs({}, function(err, txs) { - var tx = txs[0]; - tx.id.should.equal(txid); - next(); - }); - }, - function(next) { - server.rejectTx({ - txProposalId: txid, - reason: 'some reason', - }, function(err) { - should.not.exist(err); - next(); - }); - }, - function(next) { - helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); - server.createTx(txOpts, function(err, tx) { - err.code.should.equal('NOTALLOWEDTOCREATETX'); - next(); - }); - }); - }, - function(next) { - helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); - - var clock = sinon.useFakeTimers(Date.now() + 2000 + WalletService.backoffTime * 1000); - server.createTx(txOpts, function(err, tx) { - should.not.exist(err); - clock.restore(); - next(); - }); - }); - }, - ], done); - }); - it('should not allow to create after backoffTime and 2 rejections', function(done) { - async.series([ - - function(next) { - helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); - server.createTx(txOpts, function(err, tx) { - should.not.exist(err); - next(); - }); - }); - }, - function(next) { - server.getPendingTxs({}, function(err, tx) { - should.not.exist(err); - server.rejectTx({ - txProposalId: tx[0].id, - reason: 'some reason', - }, function(err) { - should.not.exist(err); - server.rejectTx({ - txProposalId: tx[1].id, - reason: 'some other reason', - }, function(err) { + async.each([0, 1, 2, 3], function(i, a_next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { should.not.exist(err); - - next(); + server.rejectTx({ + txProposalId: tx.id, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + a_next(); + }); }); }); + }, next); + }, + function(next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + err.code.should.equal('NOTALLOWEDTOCREATETX'); + next(); + }); }); }, function(next) { helpers.stubUtxos(server, wallet, _.range(1, 9), function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); - - var clock = sinon.useFakeTimers(Date.now() + 2000 + WalletService.backoffTime * 1000); + var clock = sinon.useFakeTimers(Date.now() + WalletService.backoffTimeMinutes * 60 * 1000 + 2000); server.createTx(txOpts, function(err, tx) { - err.code.should.equal('NOTALLOWEDTOCREATETX'); clock.restore(); + should.not.exist(err); next(); }); }); @@ -1814,6 +1793,47 @@ describe('Wallet service', function() { }); + it('should NOT allow to create after a 5 rejections after backofftime', function(done) { + async.series([ + + function(next) { + async.each([0, 1, 2, 3, 4], function(i, a_next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + server.rejectTx({ + txProposalId: tx.id, + reason: 'some reason', + }, function(err) { + should.not.exist(err); + a_next(); + }); + }); + }); + }, next); + }, + function(next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + next(); + }); + }); + }, + function(next) { + helpers.stubUtxos(server, wallet, _.range(1, 9), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + var clock = sinon.useFakeTimers(Date.now() + WalletService.backoffTimeMinutes * 60 * 1000 + 2000); + server.createTx(txOpts, function(err, tx) { + clock.restore(); + err.code.should.equal('NOTALLOWEDTOCREATETX'); + next(); + }); + }); + }, + ], done); + }); }); @@ -2437,6 +2457,7 @@ describe('Wallet service', function() { next(); }); }, function(err) { + clock.restore(); return done(err); }); });