backoff time only active after backoffOffset
This commit is contained in:
parent
895c52bada
commit
4569f1d3c5
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue