From 8ef05c884195371cb3d8d13323d1c43c0f17efd4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 26 Nov 2015 12:28:02 -0300 Subject: [PATCH] check available utxos when sending temporary txp --- lib/errors/errordefinitions.js | 1 + lib/server.js | 23 ++++++++-- test/integration/server.js | 78 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/lib/errors/errordefinitions.js b/lib/errors/errordefinitions.js index f5e713f..c0fa8ab 100644 --- a/lib/errors/errordefinitions.js +++ b/lib/errors/errordefinitions.js @@ -26,6 +26,7 @@ var errors = { TX_NOT_ACCEPTED: 'The transaction proposal is not accepted', TX_NOT_FOUND: 'Transaction proposal not found', TX_NOT_PENDING: 'The transaction proposal is not pending', + UNAVAILABLE_UTXOS: 'Unavailable unspent outputs', UPGRADE_NEEDED: 'Client app needs to be upgraded', WALLET_ALREADY_EXISTS: 'Wallet already exists', WALLET_FULL: 'Wallet full', diff --git a/lib/server.js b/lib/server.js index 9c24d22..2adc162 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1436,6 +1436,10 @@ WalletService.prototype.createTx2 = function(opts, cb) { WalletService.prototype.sendTx = function(opts, cb) { var self = this; + function utxoKey(utxo) { + return utxo.txid + '|' + utxo.vout + }; + if (!Utils.checkRequired(opts, ['txProposalId', 'proposalSignature', 'proposalSignaturePubKey', 'proposalSignaturePubKeySig'])) return cb(new ClientError('Required argument missing')); @@ -1445,10 +1449,23 @@ WalletService.prototype.sendTx = function(opts, cb) { if (!txp) return cb(Errors.TX_NOT_FOUND); if (!txp.isTemporary()) return cb(); - txp.status = 'pending'; - self.storage.storeTx(self.walletId, txp, function(err) { + // Verify UTXOs are still available + self.getUtxos({}, function(err, utxos) { if (err) return cb(err); - return cb(); + + var txpInputs = _.map(txp.inputs, utxoKey); + var lockedUtxoIndex = _.indexBy(_.filter(utxos, 'locked'), utxoKey); + var unavailable = _.any(txpInputs, function(i) { + return lockedUtxoIndex[i]; + }); + + if (unavailable) return cb(Errors.UNAVAILABLE_UTXOS); + + txp.status = 'pending'; + self.storage.storeTx(self.walletId, txp, function(err) { + if (err) return cb(err); + return cb(); + }); }); }); }); diff --git a/test/integration/server.js b/test/integration/server.js index ed3b129..bc5909e 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -2409,6 +2409,84 @@ describe('Wallet service', function() { }); }); }); + it('should fail to send a temporary tx proposal if utxos are unavailable', function(done) { + var txp1, txp2; + var txOpts = helpers.createProposalOpts2([{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 0.8 + }], { + message: 'some message', + }); + + async.waterfall([ + + function(next) { + helpers.stubUtxos(server, wallet, [1, 2], function() { + next(); + }); + }, + function(next) { + server.createTx2(txOpts, next); + }, + function(txp, next) { + txp1 = txp; + server.createTx2(txOpts, next); + }, + function(txp, next) { + txp2 = txp; + should.exist(txp1); + should.exist(txp2); + server.sendTx({ + txProposalId: txp1.id, + proposalSignature: 'dummy', + proposalSignaturePubKey: 'dummy', + proposalSignaturePubKeySig: 'dummy', + }, next); + }, + function(next) { + server.sendTx({ + txProposalId: txp2.id, + proposalSignature: 'dummy', + proposalSignaturePubKey: 'dummy', + proposalSignaturePubKeySig: 'dummy', + }, function(err) { + should.exist(err); + err.code.should.equal('UNAVAILABLE_UTXOS'); + next(); + }); + }, + function(next) { + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(1); + next(); + }); + }, + function(next) { + // A new tx proposal should use the next available UTXO + server.createTx2(txOpts, next); + }, + function(txp3, next) { + should.exist(txp3); + server.sendTx({ + txProposalId: txp3.id, + proposalSignature: 'dummy', + proposalSignaturePubKey: 'dummy', + proposalSignaturePubKeySig: 'dummy', + }, next); + }, + function(next) { + server.getPendingTxs({}, function(err, txs) { + should.not.exist(err); + txs.length.should.equal(2); + next(); + }); + }, + ], function(err) { + should.not.exist(err); + done(); + }); + }); }); describe('#createTx backoff time', function(done) { var server, wallet, txid;