From fca67fba8ab9bfc7f79b162d4196681d3556327a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 13 Aug 2015 12:01:22 -0300 Subject: [PATCH] implement utxos for external addresses --- lib/server.js | 67 +++++++++++++++++++++++++------------- test/integration/server.js | 33 ++++++++++++++++++- 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/lib/server.js b/lib/server.js index bf21404..bc6e0b5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -618,15 +618,31 @@ WalletService.prototype._getBlockchainExplorer = function(network) { return this.blockchainExplorer; }; -/** - * Returns list of UTXOs - * @param {Object} opts - * @returns {Array} utxos - List of UTXOs. - */ -WalletService.prototype.getUtxos = function(opts, cb) { +WalletService.prototype._getUtxosForAddresses = function(addresses, cb) { var self = this; - opts = opts || {}; + if (addresses.length == 0) return cb(null, []); + var networkName = Bitcore.Address(addresses[0]).toObject().network; + + var bc = self._getBlockchainExplorer(networkName); + bc.getUnspentUtxos(addresses, function(err, utxos) { + if (err) return cb(err); + + var utxos = _.map(utxos, function(utxo) { + var u = _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis', 'confirmations']); + u.confirmations = u.confirmations || 0; + u.locked = false; + u.satoshis = u.satoshis ? +u.satoshis : Utils.strip(u.amount * 1e8); + delete u.amount; + return u; + }); + + return cb(null, utxos); + }); +}; + +WalletService.prototype._getUtxosForCurrentWallet = function(cb) { + var self = this; function utxoKey(utxo) { return utxo.txid + '|' + utxo.vout @@ -635,29 +651,17 @@ WalletService.prototype.getUtxos = function(opts, cb) { // Get addresses for this wallet self.storage.fetchAddresses(self.walletId, function(err, addresses) { if (err) return cb(err); - if (addresses.length == 0) return cb(null, []); var addressStrs = _.pluck(addresses, 'address'); - var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance - var networkName = Bitcore.Address(addressStrs[0]).toObject().network; - - var bc = self._getBlockchainExplorer(networkName); - bc.getUnspentUtxos(addressStrs, function(err, inutxos) { + self._getUtxosForAddresses(addressStrs, function(err, utxos) { if (err) return cb(err); + if (utxos.length == 0) return cb(null, []); - var utxos = _.map(inutxos, function(utxo) { - var u = _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis', 'confirmations']); - u.confirmations = u.confirmations || 0; - u.locked = false; - return u; - }); self.getPendingTxs({}, function(err, txps) { if (err) return cb(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; @@ -665,9 +669,8 @@ WalletService.prototype.getUtxos = function(opts, cb) { }); // Needed for the clients to sign UTXOs + var addressToPath = _.indexBy(addresses, 'address'); _.each(utxos, function(utxo) { - utxo.satoshis = utxo.satoshis ? +utxo.satoshis : Utils.strip(utxo.amount * 1e8); - delete utxo.amount; utxo.path = addressToPath[utxo.address].path; utxo.publicKeys = addressToPath[utxo.address].publicKeys; }); @@ -678,6 +681,24 @@ WalletService.prototype.getUtxos = function(opts, cb) { }); }; +/** + * Returns list of UTXOs + * @param {Object} opts + * @param {Array} opts.addresses (optional) - List of addresses from where to fetch UTXOs. + * @returns {Array} utxos - List of UTXOs. + */ +WalletService.prototype.getUtxos = function(opts, cb) { + var self = this; + + opts = opts || {}; + + if (_.isUndefined(opts.addresses)) { + self._getUtxosForCurrentWallet(cb); + } else { + self._getUtxosForAddresses(opts.addresses, cb); + } +}; + WalletService.prototype._totalizeUtxos = function(utxos) { var balance = { totalAmount: _.sum(utxos, 'satoshis'), diff --git a/test/integration/server.js b/test/integration/server.js index 9f3aa59..0dceb24 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -159,7 +159,12 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) { confirmations: confirmations, }; }); - blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); + blockchainExplorer.getUnspentUtxos = function(addresses, cb) { + var selected = _.filter(utxos, function(utxo) { + return _.contains(addresses, utxo.address); + }); + return cb(null, selected); + }; return cb(utxos); }); @@ -1367,6 +1372,32 @@ describe('Wallet service', function() { should.exist(utxos); utxos.length.should.equal(2); _.sum(utxos, 'satoshis').should.equal(3 * 1e8); + server.getMainAddresses({}, function(err, addresses) { + var utxo = utxos[0]; + var address = _.find(addresses, { + address: utxo.address + }); + should.exist(address); + utxo.path.should.equal(address.path); + utxo.publicKeys.should.deep.equal(address.publicKeys); + done(); + }); + }); + }); + }); + it('should get UTXOs for specific addresses', function(done) { + helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) { + _.uniq(utxos, 'address').length.should.be.above(1); + var address = utxos[0].address; + var amount = _.sum(_.filter(utxos, { + address: address + }), 'satoshis'); + server.getUtxos({ + addresses: [address] + }, function(err, utxos) { + should.not.exist(err); + should.exist(utxos); + _.sum(utxos, 'satoshis').should.equal(amount); done(); }); });