discard recently spent inputs from utxo selection

This commit is contained in:
Ivan Socolsky 2016-05-23 16:32:28 -03:00
parent cf7d1cd55f
commit 3db28a4e2d
No known key found for this signature in database
GPG Key ID: FAECE6A05FAA4F56
2 changed files with 106 additions and 41 deletions

View File

@ -902,50 +902,79 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) {
return utxo.txid + '|' + utxo.vout return utxo.txid + '|' + utxo.vout
}; };
async.waterfall([ var allAddresses, allUtxos, utxoIndex;
async.series([
function(next) { function(next) {
if (_.isArray(addresses)) { if (_.isArray(addresses)) {
if (!_.isEmpty(addresses)) { allAddresses = addresses;
next(null, addresses); return next();
} else {
next(null, []);
}
} else {
self.storage.fetchAddresses(self.walletId, next);
} }
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
allAddresses = addresses;
return next();
});
}, },
function(addresses, next) { function(next) {
if (addresses.length == 0) return next(null, []); if (allAddresses.length == 0) return cb(null, []);
var addressStrs = _.pluck(addresses, 'address'); var addressStrs = _.pluck(allAddresses, 'address');
self._getUtxos(addressStrs, function(err, utxos) { self._getUtxos(addressStrs, function(err, utxos) {
if (err) return next(err); if (err) return next(err);
if (utxos.length == 0) return next(null, []); if (utxos.length == 0) return next(null, []);
allUtxos = utxos;
utxoIndex = _.indexBy(allUtxos, utxoKey);
return next();
});
},
function(next) {
self.getPendingTxs({}, function(err, txps) { self.getPendingTxs({}, function(err, txps) {
if (err) return next(err); if (err) return next(err);
var lockedInputs = _.map(_.flatten(_.pluck(txps, 'inputs')), utxoKey); var lockedInputs = _.map(_.flatten(_.pluck(txps, 'inputs')), utxoKey);
var utxoIndex = _.indexBy(utxos, utxoKey);
_.each(lockedInputs, function(input) { _.each(lockedInputs, function(input) {
if (utxoIndex[input]) { if (utxoIndex[input]) {
utxoIndex[input].locked = true; utxoIndex[input].locked = true;
} }
}); });
return next();
});
},
function(next) {
var fromTs = Math.floor(Date.now() / 1000) - 24 * 3600;
self.storage.fetchTxs(self.walletId, {
minTs: fromTs,
limit: 100
}, function(err, txs) {
if (err) return next(err);
var broadcasted = _.filter(txs, {
status: 'broadcasted'
});
var spentInputs = _.map(_.flatten(_.pluck(broadcasted, 'inputs')), utxoKey);
_.each(spentInputs, function(input) {
if (utxoIndex[input]) {
utxoIndex[input].spent = true;
}
});
allUtxos = _.reject(allUtxos, {
spent: true
});
return next();
});
},
function(next) {
// Needed for the clients to sign UTXOs // Needed for the clients to sign UTXOs
var addressToPath = _.indexBy(addresses, 'address'); var addressToPath = _.indexBy(allAddresses, 'address');
_.each(utxos, function(utxo) { _.each(allUtxos, function(utxo) {
utxo.path = addressToPath[utxo.address].path; utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys; utxo.publicKeys = addressToPath[utxo.address].publicKeys;
}); });
return next();
return next(null, utxos);
});
});
}, },
], cb); ], function(err) {
return cb(err, allUtxos);
});
}; };
/** /**
@ -1883,7 +1912,6 @@ WalletService.prototype.createTx = function(opts, cb) {
var self = this; var self = this;
self._runLocked(cb, function(cb) { self._runLocked(cb, function(cb) {
var wallet, txp, changeAddress; var wallet, txp, changeAddress;
async.series([ async.series([
@ -2442,14 +2470,14 @@ WalletService.prototype.getPendingTxs = function(opts, cb) {
txp.deleteLockTime = self.getRemainingDeleteLockTime(txp); txp.deleteLockTime = self.getRemainingDeleteLockTime(txp);
}); });
async.each(txps, function(txp, a_cb) { async.each(txps, function(txp, next) {
if (txp.status != 'accepted') return a_cb(); if (txp.status != 'accepted') return next();
self._checkTxInBlockchain(txp, function(err, isInBlockchain) { self._checkTxInBlockchain(txp, function(err, isInBlockchain) {
if (err || !isInBlockchain) return a_cb(err); if (err || !isInBlockchain) return next(err);
self._processBroadcast(txp, { self._processBroadcast(txp, {
byThirdParty: true byThirdParty: true
}, a_cb); }, next);
}); });
}, function(err) { }, function(err) {
return cb(err, _.reject(txps, function(txp) { return cb(err, _.reject(txps, function(txp) {

View File

@ -3469,7 +3469,7 @@ describe('Wallet service', function() {
var server, wallet; var server, wallet;
beforeEach(function(done) { beforeEach(function(done) {
// log.level = 'debug'; // log.level = 'debug';
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(1, 2, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
done(); done();
@ -3644,7 +3644,7 @@ describe('Wallet service', function() {
var _old1 = Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR; var _old1 = Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR;
var _old2 = Defaults.MAX_TX_SIZE_IN_KB; var _old2 = Defaults.MAX_TX_SIZE_IN_KB;
Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR = 0.0001; Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR = 0.0001;
Defaults.MAX_TX_SIZE_IN_KB = 3; Defaults.MAX_TX_SIZE_IN_KB = 2;
helpers.stubUtxos(server, wallet, [100].concat(_.range(1, 20, 0)), function() { helpers.stubUtxos(server, wallet, [100].concat(_.range(1, 20, 0)), function() {
var txOpts = { var txOpts = {
@ -3826,7 +3826,7 @@ describe('Wallet service', function() {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 200e2, amount: 200e2,
}], }],
feePerKb: 50e2, feePerKb: 80e2,
}; };
server.createTx(txOpts, function(err, txp) { server.createTx(txOpts, function(err, txp) {
should.exist(err); should.exist(err);
@ -3851,6 +3851,43 @@ describe('Wallet service', function() {
}); });
}); });
}); });
it('should not use UTXOs of recently broadcasted txs', function(done) {
helpers.stubUtxos(server, wallet, [1, 1], function() {
var txOpts = {
outputs: [{
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 1.5e8,
}],
feePerKb: 100e2,
};
helpers.createAndPublishTx(server, txOpts, TestData.copayers[0].privKey_1H_0, function(txp) {
should.exist(txp);
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);
should.exist(txp);
helpers.stubBroadcast();
server.broadcastTx({
txProposalId: txp.id
}, function(err, txp) {
should.not.exist(err);
should.exist(txp.txid);
txp.status.should.equal('broadcasted');
server.createTx(txOpts, function(err, txp) {
should.exist(err);
err.code.should.equal('INSUFFICIENT_FUNDS');
should.not.exist(txp);
done();
});
});
});
});
});
});
}); });
}); });