check available utxos when sending temporary txp

This commit is contained in:
Ivan Socolsky 2015-11-26 12:28:02 -03:00
parent 30c8072b8b
commit 8ef05c8841
3 changed files with 99 additions and 3 deletions

View File

@ -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',

View File

@ -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();
});
});
});
});

View File

@ -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;