improve error detection when building tx
This commit is contained in:
parent
b5f6582b77
commit
5d537afc60
|
@ -86,7 +86,7 @@ TxProposal.prototype._getCurrentSignatures = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype._getBitcoreTx = function() {
|
TxProposal.prototype.getBitcoreTx = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
var t = new Bitcore.Transaction();
|
var t = new Bitcore.Transaction();
|
||||||
|
@ -113,7 +113,7 @@ TxProposal.prototype.getNetworkName = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.getRawTx = function() {
|
TxProposal.prototype.getRawTx = function() {
|
||||||
var t = this._getBitcoreTx();
|
var t = this.getBitcoreTx();
|
||||||
|
|
||||||
return t.uncheckedSerialize();
|
return t.uncheckedSerialize();
|
||||||
};
|
};
|
||||||
|
@ -186,7 +186,7 @@ TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) {
|
||||||
TxProposal.prototype.sign = function(copayerId, signatures, xpub) {
|
TxProposal.prototype.sign = function(copayerId, signatures, xpub) {
|
||||||
try {
|
try {
|
||||||
// Tests signatures are OK
|
// Tests signatures are OK
|
||||||
var t = this._getBitcoreTx();
|
var t = this.getBitcoreTx();
|
||||||
this._addSignaturesToBitcoreTx(t, signatures, xpub);
|
this._addSignaturesToBitcoreTx(t, signatures, xpub);
|
||||||
|
|
||||||
this.addAction(copayerId, 'accept', null, signatures, xpub);
|
this.addAction(copayerId, 'accept', null, signatures, xpub);
|
||||||
|
|
|
@ -421,6 +421,20 @@ WalletService.prototype._getUtxos = function(cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WalletService.prototype._totalizeUtxos = function(utxos) {
|
||||||
|
var balance = {};
|
||||||
|
balance.totalAmount = Utils.strip(_.reduce(utxos, function(sum, utxo) {
|
||||||
|
return sum + utxo.satoshis;
|
||||||
|
}, 0));
|
||||||
|
|
||||||
|
balance.lockedAmount = Utils.strip(_.reduce(_.filter(utxos, {
|
||||||
|
locked: true
|
||||||
|
}), function(sum, utxo) {
|
||||||
|
return sum + utxo.satoshis;
|
||||||
|
}, 0));
|
||||||
|
return balance;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new transaction proposal.
|
* Creates a new transaction proposal.
|
||||||
|
@ -433,16 +447,7 @@ WalletService.prototype.getBalance = function(opts, cb) {
|
||||||
self._getUtxos(function(err, utxos) {
|
self._getUtxos(function(err, utxos) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var balance = {};
|
var balance = self._totalizeUtxos(utxos);
|
||||||
balance.totalAmount = Utils.strip(_.reduce(utxos, function(sum, utxo) {
|
|
||||||
return sum + utxo.satoshis;
|
|
||||||
}, 0));
|
|
||||||
|
|
||||||
balance.lockedAmount = Utils.strip(_.reduce(_.filter(utxos, {
|
|
||||||
locked: true
|
|
||||||
}), function(sum, utxo) {
|
|
||||||
return sum + utxo.satoshis;
|
|
||||||
}, 0));
|
|
||||||
|
|
||||||
// Compute balance by address
|
// Compute balance by address
|
||||||
var byAddress = {};
|
var byAddress = {};
|
||||||
|
@ -470,6 +475,16 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
self._getUtxos(function(err, utxos) {
|
self._getUtxos(function(err, utxos) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
var balance = self._totalizeUtxos(utxos);
|
||||||
|
|
||||||
|
var txMinAmount = txp.amount + Bitcore.Transaction.FEE_PER_KB;
|
||||||
|
if (balance.totalAmount < txMinAmount)
|
||||||
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds'));
|
||||||
|
|
||||||
|
if ((balance.totalAmount - balance.lockedAmount) < txMinAmount)
|
||||||
|
return cb(new ClientError('LOCKEDFUNDS', 'Funds are locked by pending transaction proposals'));
|
||||||
|
|
||||||
|
|
||||||
utxos = _.reject(utxos, {
|
utxos = _.reject(utxos, {
|
||||||
locked: true
|
locked: true
|
||||||
});
|
});
|
||||||
|
@ -478,16 +493,17 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var selected = [];
|
var selected = [];
|
||||||
var inputs = _.sortBy(utxos, 'amount');
|
var inputs = _.sortBy(utxos, 'amount');
|
||||||
|
var bitcoreTx;
|
||||||
|
|
||||||
while (i < inputs.length) {
|
while (i < inputs.length) {
|
||||||
selected.push(inputs[i]);
|
selected.push(inputs[i]);
|
||||||
total += inputs[i].satoshis;
|
total += inputs[i].satoshis;
|
||||||
|
|
||||||
if (total >= txp.amount + Bitcore.Transaction.FEE_PER_KB) {
|
if (total >= txMinAmount) {
|
||||||
try {
|
try {
|
||||||
// Check if there are enough fees
|
// Check if there are enough fees
|
||||||
txp.inputs = selected;
|
txp.inputs = selected;
|
||||||
var raw = txp.getRawTx();
|
bitcoreTx = txp.getBitcoreTx();
|
||||||
txp.inputPaths = _.pluck(txp.inputs, 'path');
|
txp.inputPaths = _.pluck(txp.inputs, 'path');
|
||||||
return cb();
|
return cb();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -498,7 +514,8 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
};
|
};
|
||||||
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds'));
|
|
||||||
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds for fee'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ var WalletUtils = require('bitcore-wallet-utils');
|
||||||
var Storage = require('../../lib/storage');
|
var Storage = require('../../lib/storage');
|
||||||
|
|
||||||
var Wallet = require('../../lib/model/wallet');
|
var Wallet = require('../../lib/model/wallet');
|
||||||
|
var TxProposal = require('../../lib/model/txproposal');
|
||||||
var Address = require('../../lib/model/address');
|
var Address = require('../../lib/model/address');
|
||||||
var Copayer = require('../../lib/model/copayer');
|
var Copayer = require('../../lib/model/copayer');
|
||||||
var WalletService = require('../../lib/server');
|
var WalletService = require('../../lib/server');
|
||||||
|
@ -1005,6 +1006,25 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail with insufficient funds if fee is too large', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, 10, function() {
|
||||||
|
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, null, TestData.copayers[0].privKey_1H_0);
|
||||||
|
|
||||||
|
var txpStub = sinon.stub(TxProposal.prototype, 'getBitcoreTx').throws({
|
||||||
|
name: 'bitcore.ErrorTransactionFeeError'
|
||||||
|
});
|
||||||
|
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.exist(err);
|
||||||
|
err.code.should.equal('INSUFFICIENTFUNDS');
|
||||||
|
err.message.should.equal('Insufficient funds for fee');
|
||||||
|
|
||||||
|
txpStub.restore();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail gracefully when bitcore throws exception on raw tx creation', function(done) {
|
it('should fail gracefully when bitcore throws exception on raw tx creation', function(done) {
|
||||||
helpers.stubUtxos(server, wallet, [10], function() {
|
helpers.stubUtxos(server, wallet, [10], function() {
|
||||||
var bitcoreStub = sinon.stub(Bitcore, 'Transaction');
|
var bitcoreStub = sinon.stub(Bitcore, 'Transaction');
|
||||||
|
@ -1055,8 +1075,7 @@ describe('Copay server', function() {
|
||||||
should.exist(tx);
|
should.exist(tx);
|
||||||
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey_1H_0);
|
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey_1H_0);
|
||||||
server.createTx(txOpts2, function(err, tx) {
|
server.createTx(txOpts2, function(err, tx) {
|
||||||
err.code.should.equal('INSUFFICIENTFUNDS');
|
err.code.should.equal('LOCKEDFUNDS');
|
||||||
err.message.should.equal('Insufficient funds');
|
|
||||||
should.not.exist(tx);
|
should.not.exist(tx);
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe('TXProposal', function() {
|
||||||
describe('#_getBitcoreTx', function() {
|
describe('#_getBitcoreTx', function() {
|
||||||
it('should create a valid bitcore TX', function() {
|
it('should create a valid bitcore TX', function() {
|
||||||
var txp = TXP.fromObj(aTXP());
|
var txp = TXP.fromObj(aTXP());
|
||||||
var t = txp._getBitcoreTx();
|
var t = txp.getBitcoreTx();
|
||||||
should.exist(t);
|
should.exist(t);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue