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
};
async.waterfall([
var allAddresses, allUtxos, utxoIndex;
async.series([
function(next) {
if (_.isArray(addresses)) {
if (!_.isEmpty(addresses)) {
next(null, addresses);
} else {
next(null, []);
}
} else {
self.storage.fetchAddresses(self.walletId, next);
allAddresses = addresses;
return next();
}
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
allAddresses = addresses;
return next();
});
},
function(addresses, next) {
if (addresses.length == 0) return next(null, []);
function(next) {
if (allAddresses.length == 0) return cb(null, []);
var addressStrs = _.pluck(addresses, 'address');
var addressStrs = _.pluck(allAddresses, 'address');
self._getUtxos(addressStrs, function(err, utxos) {
if (err) return next(err);
if (utxos.length == 0) return next(null, []);
self.getPendingTxs({}, function(err, txps) {
if (err) return next(err);
var lockedInputs = _.map(_.flatten(_.pluck(txps, 'inputs')), utxoKey);
var utxoIndex = _.indexBy(utxos, utxoKey);
_.each(lockedInputs, function(input) {
if (utxoIndex[input]) {
utxoIndex[input].locked = true;
}
});
// Needed for the clients to sign UTXOs
var addressToPath = _.indexBy(addresses, 'address');
_.each(utxos, function(utxo) {
utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
});
return next(null, utxos);
});
allUtxos = utxos;
utxoIndex = _.indexBy(allUtxos, utxoKey);
return next();
});
},
], cb);
function(next) {
self.getPendingTxs({}, function(err, txps) {
if (err) return next(err);
var lockedInputs = _.map(_.flatten(_.pluck(txps, 'inputs')), utxoKey);
_.each(lockedInputs, function(input) {
if (utxoIndex[input]) {
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
var addressToPath = _.indexBy(allAddresses, 'address');
_.each(allUtxos, function(utxo) {
utxo.path = addressToPath[utxo.address].path;
utxo.publicKeys = addressToPath[utxo.address].publicKeys;
});
return next();
},
], function(err) {
return cb(err, allUtxos);
});
};
/**
@ -1883,7 +1912,6 @@ WalletService.prototype.createTx = function(opts, cb) {
var self = this;
self._runLocked(cb, function(cb) {
var wallet, txp, changeAddress;
async.series([
@ -2442,14 +2470,14 @@ WalletService.prototype.getPendingTxs = function(opts, cb) {
txp.deleteLockTime = self.getRemainingDeleteLockTime(txp);
});
async.each(txps, function(txp, a_cb) {
if (txp.status != 'accepted') return a_cb();
async.each(txps, function(txp, next) {
if (txp.status != 'accepted') return next();
self._checkTxInBlockchain(txp, function(err, isInBlockchain) {
if (err || !isInBlockchain) return a_cb(err);
if (err || !isInBlockchain) return next(err);
self._processBroadcast(txp, {
byThirdParty: true
}, a_cb);
}, next);
});
}, function(err) {
return cb(err, _.reject(txps, function(txp) {

View File

@ -3469,7 +3469,7 @@ describe('Wallet service', function() {
var server, wallet;
beforeEach(function(done) {
// log.level = 'debug';
helpers.createAndJoinWallet(2, 3, function(s, w) {
helpers.createAndJoinWallet(1, 2, function(s, w) {
server = s;
wallet = w;
done();
@ -3644,7 +3644,7 @@ describe('Wallet service', function() {
var _old1 = Defaults.UTXO_SELECTION_MIN_TX_AMOUNT_VS_UTXO_FACTOR;
var _old2 = Defaults.MAX_TX_SIZE_IN_KB;
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() {
var txOpts = {
@ -3826,7 +3826,7 @@ describe('Wallet service', function() {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: 200e2,
}],
feePerKb: 50e2,
feePerKb: 80e2,
};
server.createTx(txOpts, function(err, txp) {
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();
});
});
});
});
});
});
});
});