diff --git a/lib/server.js b/lib/server.js index 062f54e..fc79cf2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -992,8 +992,6 @@ WalletService.prototype._getBalanceFromAddresses = function(addresses, cb) { }); }; -var prioritaryAddresses; - /** * Get wallet balance. * @param {Object} opts @@ -1009,11 +1007,15 @@ WalletService.prototype.getBalance = function(opts, cb) { // Update cache var addressIndex = _.indexBy(addresses, 'address'); - prioritaryAddresses = _.map(_.pluck(balance.byAddress, 'address'), function(addrStr) { + var freshAddresses = _.map(_.pluck(balance.byAddress, 'address'), function(addrStr) { return addressIndex[addrStr]; }); - - return cb(null, balance); + self.storage.storeCacheData(self.walletId, 'freshAddresses', freshAddresses, function(err) { + if (err) { + log.warn('Could not update wallet cache', err); + } + return cb(null, balance); + }); }); }); }; @@ -1027,20 +1029,24 @@ WalletService.prototype.getBalance = function(opts, cb) { WalletService.prototype.getBalance2Steps = function(opts, cb) { var self = this; - if (!prioritaryAddresses) return self.getBalance(opts, cb); - - self._getBalanceFromAddresses(prioritaryAddresses, function(err, partialBalance) { - if (err) return cb(err); - cb(null, partialBalance); - setTimeout(function() { - self.getBalance(opts, function(err, fullBalance) { - if (err) return; - if (!_.isEqual(partialBalance, fullBalance)) { - console.log('*** [server.js ln1015] ACTUALIZAR BALANCE!!!!, partialBalance, fullBalance:', partialBalance, fullBalance); // TODO - } + self.storage.fetchCacheData(self.walletId, 'freshAddresses', function(err, freshAddresses) { + if (err || _.isEmpty(freshAddresses)) { + return self.getBalance(opts, cb); + } else { + self._getBalanceFromAddresses(freshAddresses, function(err, partialBalance) { + if (err) return cb(err); + cb(null, partialBalance); + setTimeout(function() { + self.getBalance(opts, function(err, fullBalance) { + if (err) return; + if (!_.isEqual(partialBalance, fullBalance)) { + self._notify('BalanceUpdated', fullBalance); + } + }); + }, 1); + return; }); - }, 1); - return; + } }); }; diff --git a/lib/storage.js b/lib/storage.js index 59be36c..4f0f760 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -20,6 +20,7 @@ var collections = { COPAYERS_LOOKUP: 'copayers_lookup', PREFERENCES: 'preferences', EMAIL_QUEUE: 'email_queue', + CACHE: 'cache', }; var Storage = function(opts) { @@ -56,6 +57,10 @@ Storage.prototype._createIndexes = function() { this.db.collection(collections.EMAIL_QUEUE).createIndex({ notificationId: 1, }); + this.db.collection(collections.CACHE).createIndex({ + walletId: 1, + key: 1, + }); }; Storage.prototype.connect = function(opts, cb) { @@ -501,6 +506,37 @@ Storage.prototype.fetchEmailByNotification = function(notificationId, cb) { }); }; +Storage.prototype.storeCacheData = function(walletId, key, value, cb) { + var self = this; + + var record = { + walletId: walletId, + key: key, + data: value + }; + self.db.collection(collections.CACHE).update({ + walletId: walletId, + key: key, + }, record, { + w: 1, + upsert: true, + }, cb); +}; + +Storage.prototype.fetchCacheData = function(walletId, key, cb) { + var self = this; + + self.db.collection(collections.CACHE).findOne({ + walletId: walletId, + key: key, + }, function(err, result) { + if (err) return cb(err); + if (!result) return cb(); + + return cb(null, result.data); + }); +}; + Storage.prototype._dump = function(cb, fn) { fn = fn || console.log; cb = cb || function() {}; diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 7e57cd1..6626be6 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -211,52 +211,77 @@ helpers.toSatoshi = function(btc) { } }; -helpers.stubUtxos = function(server, wallet, amounts, cb) { - async.mapSeries(_.range(0, amounts.length > 2 ? 2 : 1), function(i, next) { - server.createAddress({}, next); - }, function(err, addresses) { - should.not.exist(err); - addresses.should.not.be.empty; - var utxos = _.compact(_.map([].concat(amounts), function(amount, i) { - var confirmations; - if (_.isString(amount) && _.startsWith(amount, 'u')) { - amount = parseFloat(amount.substring(1)); - confirmations = 0; +helpers.stubUtxos = function(server, wallet, amounts, opts, cb) { + if (_.isFunction(opts)) { + cb = opts; + opts = {}; + } + opts = opts || {}; + + if (!helpers._utxos) helpers._utxos = {}; + + async.waterfall([ + + function(next) { + if (opts.addresses) return next(null, [].concat(opts.addresses)); + async.mapSeries(_.range(0, amounts.length > 2 ? 2 : 1), function(i, next) { + server.createAddress({}, next); + }, next); + }, + function(addresses, next) { + addresses.should.not.be.empty; + + var utxos = _.compact(_.map([].concat(amounts), function(amount, i) { + var confirmations; + if (_.isString(amount) && _.startsWith(amount, 'u')) { + amount = parseFloat(amount.substring(1)); + confirmations = 0; + } else { + confirmations = Math.floor(Math.random() * 100 + 1); + } + if (amount <= 0) return null; + + var address = addresses[i % addresses.length]; + + var scriptPubKey; + switch (wallet.addressType) { + case Constants.SCRIPT_TYPES.P2SH: + scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut(); + break; + case Constants.SCRIPT_TYPES.P2PKH: + scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address); + break; + } + should.exist(scriptPubKey); + + return { + txid: helpers.randomTXID(), + vout: Math.floor(Math.random() * 10 + 1), + satoshis: helpers.toSatoshi(amount), + scriptPubKey: scriptPubKey.toBuffer().toString('hex'), + address: address.address, + confirmations: confirmations + }; + })); + + if (opts.keepUtxos) { + helpers._utxos = helpers._utxos.concat(utxos); } else { - confirmations = Math.floor(Math.random() * 100 + 1); + helpers._utxos = utxos; } - if (amount <= 0) return null; - var address = addresses[i % addresses.length]; - - var scriptPubKey; - switch (wallet.addressType) { - case Constants.SCRIPT_TYPES.P2SH: - scriptPubKey = Bitcore.Script.buildMultisigOut(address.publicKeys, wallet.m).toScriptHashOut(); - break; - case Constants.SCRIPT_TYPES.P2PKH: - scriptPubKey = Bitcore.Script.buildPublicKeyHashOut(address.address); - break; - } - should.exist(scriptPubKey); - - return { - txid: helpers.randomTXID(), - vout: Math.floor(Math.random() * 10 + 1), - satoshis: helpers.toSatoshi(amount), - scriptPubKey: scriptPubKey.toBuffer().toString('hex'), - address: address.address, - confirmations: confirmations + blockchainExplorer.getUnspentUtxos = function(addresses, cb) { + var selected = _.filter(helpers._utxos, function(utxo) { + return _.contains(addresses, utxo.address); + }); + return cb(null, selected); }; - })); - blockchainExplorer.getUnspentUtxos = function(addresses, cb) { - var selected = _.filter(utxos, function(utxo) { - return _.contains(addresses, utxo.address); - }); - return cb(null, selected); - }; - return cb(utxos); + return next(); + }, + ], function(err) { + should.not.exist(err); + return cb(helpers._utxos); }); }; @@ -451,14 +476,14 @@ helpers.createProposalOpts = function(type, outputs, signingKey, moreOpts, input }; helpers.createAddresses = function(server, wallet, main, change, cb) { var clock = sinon.useFakeTimers(Date.now(), 'Date'); - async.map(_.range(main + change), function(i, next) { + async.mapSeries(_.range(main + change), function(i, next) { clock.tick(1000); var address = wallet.createAddress(i >= main); server.storage.storeAddressAndWallet(wallet, address, function(err) { next(err, address); }); }, function(err, addresses) { - if (err) throw new Error('Could not generate addresses'); + should.not.exist(err); clock.restore(); return cb(_.take(addresses, main), _.takeRight(addresses, change)); }); diff --git a/test/integration/server.js b/test/integration/server.js index e87897d..c12e17b 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -1504,7 +1504,7 @@ describe('Wallet service', function() { }); }); - describe('#getBalance 2 steps', function() { + describe.only('#getBalance 2 steps', function() { var server, wallet; beforeEach(function(done) { helpers.createAndJoinWallet(1, 1, function(s, w) { @@ -1536,22 +1536,118 @@ describe('Wallet service', function() { }); }); }); - it.only('should trigger notification when balance of non-prioritary addresses is updated', function(done) { - helpers.stubUtxos(server, wallet, [1, 2], function() { - server.getBalance2Steps({}, function(err, balance) { - should.not.exist(err); - should.exist(balance); - balance.totalAmount.should.equal(helpers.toSatoshi(3)); - helpers.stubUtxos(server, wallet, [0.5, 0.6], function() { - server.getBalance2Steps({}, function(err, balance) { - should.not.exist(err); - should.exist(balance); - //balance.totalAmount.should.equal(helpers.toSatoshi(1.1)); - //done(); + it('should trigger notification when balance of non-prioritary addresses is updated', function(done) { + var addresses; + + async.series([ + + function(next) { + helpers.createAddresses(server, wallet, 4, 0, function(addrs) { + addresses = addrs; + helpers.stubUtxos(server, wallet, [1, 2], { + addresses: _.take(addresses, 2), + }, function() { + next(); }); }); - }); + }, + function(next) { + server.getBalance2Steps({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(3)); + next(); + }); + }, + function(next) { + helpers.stubUtxos(server, wallet, 0.5, { + addresses: addresses[2], + keepUtxos: true, + }, function() { + next(); + }); + }, + function(next) { + server.getBalance2Steps({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(3)); + next(); + }); + }, + function(next) { + setTimeout(next, 100); + }, + function(next) { + server.getNotifications({}, function(err, notifications) { + should.not.exist(err); + var last = _.last(notifications); + last.type.should.equal('BalanceUpdated'); + var balance = last.data; + balance.totalAmount.should.equal(helpers.toSatoshi(3.5)); + next(); + }); + }, + ], function(err) { + should.not.exist(err); + done(); + }); + }); + + it('should not trigger notification when only balance of prioritary addresses is updated', function(done) { + var addresses; + + async.series([ + + function(next) { + helpers.createAddresses(server, wallet, 4, 0, function(addrs) { + addresses = addrs; + helpers.stubUtxos(server, wallet, [1, 2], { + addresses: _.take(addresses, 2), + }, function() { + next(); + }); + }); + }, + function(next) { + server.getBalance2Steps({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(3)); + next(); + }); + }, + function(next) { + helpers.stubUtxos(server, wallet, 0.5, { + addresses: addresses[0], + keepUtxos: true, + }, function() { + next(); + }); + }, + function(next) { + server.getBalance2Steps({}, function(err, balance) { + should.not.exist(err); + should.exist(balance); + balance.totalAmount.should.equal(helpers.toSatoshi(3.5)); + next(); + }); + }, + function(next) { + setTimeout(next, 100); + }, + function(next) { + server.getNotifications({}, function(err, notifications) { + should.not.exist(err); + var last = _.last(notifications); + last.type.should.not.equal('BalanceUpdated'); + next(); + }); + }, + ], function(err) { + should.not.exist(err); + done(); }); }); });