From edfc50fca1476942105e666af9a6f559737da765 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 18 Jun 2015 13:19:27 -0300 Subject: [PATCH 1/4] test resulting kb to send max --- test/integration/server.js | 67 +++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/integration/server.js b/test/integration/server.js index ac54ffa..debae21 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -216,7 +216,7 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var storage, blockchainExplorer; -var useMongo = false; +var useMongo = true; function initStorage(cb) { function getDb(cb) { @@ -1115,6 +1115,7 @@ describe('Wallet service', function() { should.exist(balance); balance.totalAmount.should.equal(helpers.toSatoshi(6)); balance.lockedAmount.should.equal(0); + balance.totalKbToSendMax.should.equal(1); should.exist(balance.byAddress); balance.byAddress.length.should.equal(2); balance.byAddress[0].amount.should.equal(helpers.toSatoshi(4)); @@ -1134,6 +1135,7 @@ describe('Wallet service', function() { should.exist(balance); balance.totalAmount.should.equal(0); balance.lockedAmount.should.equal(0); + balance.totalKbToSendMax.should.equal(0); should.exist(balance.byAddress); balance.byAddress.length.should.equal(0); done(); @@ -1148,6 +1150,7 @@ describe('Wallet service', function() { should.exist(balance); balance.totalAmount.should.equal(0); balance.lockedAmount.should.equal(0); + balance.totalKbToSendMax.should.equal(0); should.exist(balance.byAddress); balance.byAddress.length.should.equal(0); done(); @@ -1168,6 +1171,18 @@ describe('Wallet service', function() { }); }); }); + it('should return correct kb to send max', function(done) { + helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { + server.getBalance({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(9)); + balance.lockedAmount.should.equal(0); + balance.totalKbToSendMax.should.equal(2); + done(); + }); + }); + }); it('should fail gracefully when blockchain is unreachable', function(done) { blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, 'dummy error'); server.createAddress({}, function(err, address) { @@ -1618,6 +1633,56 @@ describe('Wallet service', function() { }); }); }); + it('should be able to send max amount', function(done) { + helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(helpers.toSatoshi(9)); + balance.lockedAmount.should.equal(0); + balance.totalKbToSendMax.should.equal(3); + var max = (balance.totalAmount - balance.lockedAmount) - (balance.totalKbToSendMax * 10000); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + should.exist(tx); + tx.amount.should.equal(max); + tx.fee.should.equal(3 * 10000); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.lockedAmount.should.equal(helpers.toSatoshi(9)); + done(); + }); + }); + }); + }); + }); + it('should be able to send max non-locked amount', function(done) { + helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(helpers.toSatoshi(9)); + balance.lockedAmount.should.equal(helpers.toSatoshi(4)); + balance.totalKbToSendMax.should.equal(2); + var max = (balance.totalAmount - balance.lockedAmount) - (balance.totalKbToSendMax * 2000); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0, 2000); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + should.exist(tx); + tx.amount.should.equal(max); + tx.fee.should.equal(2 * 2000); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.lockedAmount.should.equal(helpers.toSatoshi(9)); + done(); + }); + }); + }); + }); + }); + }); }); describe('#createTx backoff time', function(done) { From 931923f585d12fad4f36d9a784479eb4b5262583 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 18 Jun 2015 13:20:19 -0300 Subject: [PATCH 2/4] compute kbs needed to send max in getBalance --- lib/model/txproposal.js | 4 ++++ lib/server.js | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 97ae8fb..ef120d3 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -74,6 +74,10 @@ TxProposal.fromObj = function(obj) { return x; }; +TxProposal.prototype.setInputs = function(inputs) { + this.inputs = inputs; + this.inputPaths = _.pluck(inputs, 'path'); +}; TxProposal.prototype._updateStatus = function() { if (this.status != 'pending') return; diff --git a/lib/server.js b/lib/server.js index 740107d..627d0c7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -602,7 +602,9 @@ WalletService.prototype._getUtxos = function(cb) { return cb(new ClientError('BLOCKCHAINERROR', 'Could not fetch unspent outputs')); } var utxos = _.map(inutxos, function(utxo) { - return _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']); + var u = _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']); + u.locked = false; + return u; }); self.getPendingTxs({}, function(err, txps) { if (err) return cb(err); @@ -686,7 +688,33 @@ WalletService.prototype.getBalance = function(opts, cb) { balance.byAddress = _.values(byAddress); - return cb(null, balance); + // Compute KB of a tx to send all non-locked funds + balance.totalKbToSendMax = 0; + self.getWallet({}, function(err, wallet) { + if (err) return cb(err); + + var unlockedUtxos = _.filter(utxos, { + locked: false + }); + if (unlockedUtxos.length > 0) { + var t = WalletUtils.newBitcoreTransaction(); + + try { + _.each(unlockedUtxos, function(i) { + t.from(i, i.publicKeys, wallet.m); + }); + + var address = utxos[0].address; + t.to(address, balance.totalAmount - balance.lockedAmount); + + balance.totalKbToSendMax = Math.ceil(t._estimateSize() / 1000); + } catch (ex) { + log.error('Could not compute fees needed to transfer max amount', ex); + } + } + + return cb(null, balance); + }); }); }; @@ -720,13 +748,12 @@ WalletService.prototype._selectTxInputs = function(txp, cb) { if (total >= txp.amount) { try { - txp.inputs = selected; + txp.setInputs(selected); bitcoreTx = txp.getBitcoreTx(); bitcoreError = bitcoreTx.getSerializationError({ disableIsFullySigned: true, }); if (!bitcoreError) { - txp.inputPaths = _.pluck(txp.inputs, 'path'); txp.fee = bitcoreTx.getFee(); return cb(); } From 621fe1777ad9c1a58453242fc9249ad89463896b Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 18 Jun 2015 13:20:50 -0300 Subject: [PATCH 3/4] v0.0.36 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99b9eed..95f0490 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-service", "description": "A service for Mutisig HD Bitcoin Wallets", "author": "BitPay Inc", - "version": "0.0.35", + "version": "0.0.36", "keywords": [ "bitcoin", "copay", From 999bcbbe7dbe7b2a763014c548c0f1137b2d1279 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 18 Jun 2015 13:31:53 -0300 Subject: [PATCH 4/4] refactor code --- lib/server.js | 53 +++++++++++++++++++++----------------- test/integration/server.js | 2 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/lib/server.js b/lib/server.js index 627d0c7..dd1d114 100644 --- a/lib/server.js +++ b/lib/server.js @@ -659,6 +659,31 @@ WalletService.prototype._totalizeUtxos = function(utxos) { }; +WalletService.prototype._computeKbToSendMax = function(utxos, amount, cb) { + var self = this; + + var unlockedUtxos = _.filter(utxos, { + locked: false + }); + if (_.isEmpty(unlockedUtxos)) return cb(null, 0); + + self.getWallet({}, function(err, wallet) { + if (err) return cb(err); + + var t = WalletUtils.newBitcoreTransaction(); + try { + _.each(unlockedUtxos, function(i) { + t.from(i, i.publicKeys, wallet.m); + }); + t.to(utxos[0].address, amount); + var sizeInKb = Math.ceil(t._estimateSize() / 1000); + return cb(null, sizeInKb); + } catch (ex) { + return cb(ex); + } + }); +}; + /** * Creates a new transaction proposal. * @param {Object} opts @@ -688,31 +713,11 @@ WalletService.prototype.getBalance = function(opts, cb) { balance.byAddress = _.values(byAddress); - // Compute KB of a tx to send all non-locked funds - balance.totalKbToSendMax = 0; - self.getWallet({}, function(err, wallet) { - if (err) return cb(err); - - var unlockedUtxos = _.filter(utxos, { - locked: false - }); - if (unlockedUtxos.length > 0) { - var t = WalletUtils.newBitcoreTransaction(); - - try { - _.each(unlockedUtxos, function(i) { - t.from(i, i.publicKeys, wallet.m); - }); - - var address = utxos[0].address; - t.to(address, balance.totalAmount - balance.lockedAmount); - - balance.totalKbToSendMax = Math.ceil(t._estimateSize() / 1000); - } catch (ex) { - log.error('Could not compute fees needed to transfer max amount', ex); - } + self._computeKbToSendMax(utxos, balance.totalAmount - balance.lockedAmount, function(err, sizeInKb) { + if (err) { + log.error('Could not compute fees needed to transfer max amount', err); } - + balance.totalKbToSendMax = sizeInKb || 0; return cb(null, balance); }); }); diff --git a/test/integration/server.js b/test/integration/server.js index debae21..2266865 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -216,7 +216,7 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var storage, blockchainExplorer; -var useMongo = true; +var useMongo = false; function initStorage(cb) { function getDb(cb) {