Merge pull request #286 from isocolsky/confirmed_first
Make confirmed UTXOs have higher priority when selecting inputs
This commit is contained in:
commit
892f7a0a40
|
@ -643,7 +643,8 @@ WalletService.prototype._getUtxos = function(cb) {
|
||||||
return cb(new ClientError('BLOCKCHAINERROR', 'Could not fetch unspent outputs'));
|
return cb(new ClientError('BLOCKCHAINERROR', 'Could not fetch unspent outputs'));
|
||||||
}
|
}
|
||||||
var utxos = _.map(inutxos, function(utxo) {
|
var utxos = _.map(inutxos, function(utxo) {
|
||||||
var u = _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']);
|
var u = _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis', 'confirmations']);
|
||||||
|
u.confirmations = u.confirmations || 0;
|
||||||
u.locked = false;
|
u.locked = false;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
|
@ -767,6 +768,20 @@ WalletService.prototype.getBalance = function(opts, cb) {
|
||||||
WalletService.prototype._selectTxInputs = function(txp, cb) {
|
WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
function sortUtxos(utxos) {
|
||||||
|
var confirmed = [];
|
||||||
|
var unconfirmed = [];
|
||||||
|
|
||||||
|
_.each(utxos, function(utxo) {
|
||||||
|
if (utxo.confirmations > 0) {
|
||||||
|
confirmed.push(utxo);
|
||||||
|
} else {
|
||||||
|
unconfirmed.push(utxo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return confirmed.concat(unconfirmed);
|
||||||
|
};
|
||||||
|
|
||||||
self._getUtxos(function(err, utxos) {
|
self._getUtxos(function(err, utxos) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -784,7 +799,8 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var selected = [];
|
var selected = [];
|
||||||
var inputs = _.sortBy(utxos, 'amount');
|
var inputs = sortUtxos(utxos);
|
||||||
|
|
||||||
var bitcoreTx, bitcoreError;
|
var bitcoreTx, bitcoreError;
|
||||||
|
|
||||||
while (i < inputs.length) {
|
while (i < inputs.length) {
|
||||||
|
|
|
@ -148,10 +148,11 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) {
|
||||||
var address = addresses[i % addresses.length];
|
var address = addresses[i % addresses.length];
|
||||||
var obj = {
|
var obj = {
|
||||||
txid: helpers.randomTXID(),
|
txid: helpers.randomTXID(),
|
||||||
vout: Math.floor((Math.random() * 10) + 1),
|
vout: Math.floor(Math.random() * 10 + 1),
|
||||||
satoshis: helpers.toSatoshi(amount).toString(),
|
satoshis: helpers.toSatoshi(amount).toString(),
|
||||||
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
||||||
address: address.address,
|
address: address.address,
|
||||||
|
confirmations: Math.floor(Math.random() * 100 + 1),
|
||||||
};
|
};
|
||||||
obj.toObject = function() {
|
obj.toObject = function() {
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -1537,7 +1538,8 @@ describe('Wallet service', function() {
|
||||||
server.getBalance({}, function(err, balance) {
|
server.getBalance({}, function(err, balance) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
||||||
balance.lockedAmount.should.equal(helpers.toSatoshi(100));
|
balance.lockedAmount.should.equal(tx.inputs[0].satoshis);
|
||||||
|
balance.lockedAmount.should.be.below(balance.totalAmount);
|
||||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var change = _.filter(addresses, {
|
var change = _.filter(addresses, {
|
||||||
|
@ -1563,22 +1565,54 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a tx using the uxtos with minimum amount first', function(done) {
|
it('should create a tx using confirmed utxos first', function(done) {
|
||||||
helpers.stubUtxos(server, wallet, [100, 200, 300], function() {
|
server.createAddress({}, function(err, address) {
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 150, 'some message', TestData.copayers[0].privKey_1H_0);
|
var utxos = _.map([1.3, 0.5, 0.1, 1.2], function(amount, i) {
|
||||||
|
return {
|
||||||
|
txid: helpers.randomTXID(),
|
||||||
|
vout: Math.floor((Math.random() * 10) + 1),
|
||||||
|
satoshis: helpers.toSatoshi(amount).toString(),
|
||||||
|
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
||||||
|
address: address.address,
|
||||||
|
confirmations: amount < 1 ? 0 : 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
||||||
|
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1.5, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
tx.inputs.length.should.equal(2);
|
||||||
should.not.exist(err);
|
_.difference(_.pluck(tx.inputs, 'txid'), [utxos[0].txid, utxos[3].txid]).length.should.equal(0);
|
||||||
txs.length.should.equal(1);
|
done();
|
||||||
server.getBalance({}, function(err, balance) {
|
});
|
||||||
should.not.exist(err);
|
});
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(600));
|
});
|
||||||
balance.lockedAmount.should.equal(helpers.toSatoshi(300));
|
|
||||||
done();
|
it('should use unconfirmed utxos only when no more confirmed utxos are available', function(done) {
|
||||||
});
|
server.createAddress({}, function(err, address) {
|
||||||
});
|
var utxos = _.map([1.3, 0.5, 0.1, 1.2], function(amount, i) {
|
||||||
|
return {
|
||||||
|
txid: helpers.randomTXID(),
|
||||||
|
vout: Math.floor((Math.random() * 10) + 1),
|
||||||
|
satoshis: helpers.toSatoshi(amount).toString(),
|
||||||
|
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
||||||
|
address: address.address,
|
||||||
|
confirmations: amount < 1 ? 0 : 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
||||||
|
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2.55, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
tx.inputs.length.should.equal(3);
|
||||||
|
var txids = _.pluck(tx.inputs, 'txid');
|
||||||
|
txids.should.contain(utxos[0].txid);
|
||||||
|
txids.should.contain(utxos[3].txid);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1852,7 +1886,11 @@ describe('Wallet service', function() {
|
||||||
server.getBalance({}, function(err, balance) {
|
server.getBalance({}, function(err, balance) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(30.6));
|
balance.totalAmount.should.equal(helpers.toSatoshi(30.6));
|
||||||
balance.lockedAmount.should.equal(helpers.toSatoshi(20.3));
|
var amountInputs = _.reduce(_.pluck(txs[0].inputs, 'satoshis'), function(memo, satoshis) {
|
||||||
|
return memo + satoshis;
|
||||||
|
}, 0);
|
||||||
|
balance.lockedAmount.should.equal(amountInputs);
|
||||||
|
balance.lockedAmount.should.be.below(balance.totalAmount);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1867,7 +1905,7 @@ describe('Wallet service', function() {
|
||||||
server.getBalance({}, function(err, balance) {
|
server.getBalance({}, function(err, balance) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
|
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
|
||||||
balance.lockedAmount.should.equal(helpers.toSatoshi(0));
|
balance.lockedAmount.should.equal(0);
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey_1H_0);
|
||||||
async.map(_.range(N), function(i, cb) {
|
async.map(_.range(N), function(i, cb) {
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
@ -2168,7 +2206,7 @@ describe('Wallet service', function() {
|
||||||
server = s;
|
server = s;
|
||||||
wallet = w;
|
wallet = w;
|
||||||
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
|
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
|
||||||
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0);
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 20, null, TestData.copayers[0].privKey_1H_0);
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
|
@ -2262,7 +2300,7 @@ describe('Wallet service', function() {
|
||||||
var tx = txs[0];
|
var tx = txs[0];
|
||||||
tx.id.should.equal(txid);
|
tx.id.should.equal(txid);
|
||||||
|
|
||||||
var signatures = _.take(helpers.clientSign(tx, TestData.copayers[0].xPrivKey), 2);
|
var signatures = _.take(helpers.clientSign(tx, TestData.copayers[0].xPrivKey), tx.inputs.length - 1);
|
||||||
server.signTx({
|
server.signTx({
|
||||||
txProposalId: txid,
|
txProposalId: txid,
|
||||||
signatures: signatures,
|
signatures: signatures,
|
||||||
|
|
Loading…
Reference in New Issue