diff --git a/lib/server.js b/lib/server.js index de91056..c11acea 100644 --- a/lib/server.js +++ b/lib/server.js @@ -2070,9 +2070,10 @@ WalletService.prototype.publishTx = function(opts, cb) { if (err) return cb(err); var txpInputs = _.map(txp.inputs, utxoKey); - var lockedUtxoIndex = _.indexBy(_.filter(utxos, 'locked'), utxoKey); + var utxosIndex = _.indexBy(utxos, utxoKey); var unavailable = _.any(txpInputs, function(i) { - return lockedUtxoIndex[i]; + var utxo = utxosIndex[i]; + return !utxo || utxo.locked; }); if (unavailable) return cb(Errors.UNAVAILABLE_UTXOS); diff --git a/test/integration/server.js b/test/integration/server.js index eb1b8cc..0d824bc 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1257,9 +1257,10 @@ describe('Wallet service', function() { it('should create many addresses on simultaneous requests', function(done) { var N = 5; - async.map(_.range(N), function(i, cb) { + async.mapSeries(_.range(N), function(i, cb) { server.createAddress({}, cb); }, function(err, addresses) { + addresses = _.sortBy(addresses, 'path'); addresses.length.should.equal(N); _.each(_.range(N), function(i) { addresses[i].path.should.equal('m/0/' + i); @@ -2386,7 +2387,7 @@ describe('Wallet service', function() { describe('Tx proposal creation & publishing', function() { var server, wallet; beforeEach(function(done) { - helpers.createAndJoinWallet(2, 3, function(s, w) { + helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; done(); @@ -2407,10 +2408,10 @@ describe('Wallet service', function() { server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); - tx.walletM.should.equal(2); - tx.walletN.should.equal(3); - tx.requiredRejections.should.equal(2); - tx.requiredSignatures.should.equal(2); + tx.walletM.should.equal(1); + tx.walletN.should.equal(1); + tx.requiredRejections.should.equal(1); + tx.requiredSignatures.should.equal(1); tx.isAccepted().should.equal.false; tx.isRejected().should.equal.false; tx.isPending().should.equal.true; @@ -2816,7 +2817,7 @@ describe('Wallet service', function() { }); }); }); - it('should fail to publish a temporary tx proposal if utxos are unavailable', function(done) { + it('should fail to publish a temporary tx proposal if utxos are locked by other pending proposals', function(done) { var txp1, txp2; var txOpts = { outputs: [{ @@ -2884,6 +2885,71 @@ describe('Wallet service', function() { done(); }); }); + it('should fail to publish a temporary tx proposal if utxos are already spent', function(done) { + var txp1, txp2; + var txOpts = { + outputs: [{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 0.8 * 1e8, + }], + message: 'some message', + feePerKb: 100e2, + }; + + async.waterfall([ + + function(next) { + helpers.stubUtxos(server, wallet, [1, 2], function() { + next(); + }); + }, + function(next) { + server.createTx(txOpts, next); + }, + function(txp, next) { + txp1 = txp; + server.createTx(txOpts, next); + }, + function(txp, next) { + txp2 = txp; + should.exist(txp1); + should.exist(txp2); + var publishOpts = helpers.getProposalSignatureOpts(txp1, TestData.copayers[0].privKey_1H_0); + server.publishTx(publishOpts, next); + }, + function(txp, next) { + // Sign & Broadcast txp1 + var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey_44H_0H_0H); + server.signTx({ + txProposalId: txp.id, + signatures: signatures, + }, function(err, txp) { + should.not.exist(err); + + helpers.stubBroadcast(); + server.broadcastTx({ + txProposalId: txp.id + }, function(err, txp) { + should.not.exist(err); + should.exist(txp.txid); + txp.status.should.equal('broadcasted'); + next(); + }); + }); + }, + function(next) { + var publishOpts = helpers.getProposalSignatureOpts(txp2, TestData.copayers[0].privKey_1H_0); + server.publishTx(publishOpts, function(err) { + should.exist(err); + err.code.should.equal('UNAVAILABLE_UTXOS'); + next(); + }); + }, + ], function(err) { + should.not.exist(err); + done(); + }); + }); }); describe('Fee levels', function() {