discard recently spent inputs from utxo selection
This commit is contained in:
parent
cf7d1cd55f
commit
3db28a4e2d
104
lib/server.js
104
lib/server.js
|
@ -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;
|
||||||
self.getPendingTxs({}, function(err, txps) {
|
utxoIndex = _.indexBy(allUtxos, utxoKey);
|
||||||
if (err) return next(err);
|
return next();
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
], 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;
|
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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue