From ed437421892ddf0d380f88297d7588dedbdd7be4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 1 Apr 2015 16:42:12 -0300 Subject: [PATCH] scan + basic tests --- lib/blockchainexplorer.js | 4 +- lib/server.js | 76 ++++++++++++++++++++++++++++++++++++++ lib/storage.js | 18 +++++---- test/blockchainexplorer.js | 2 +- test/integration/server.js | 67 +++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 10 deletions(-) diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index f32772f..062b085 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -29,7 +29,7 @@ function BlockChainExplorer(opts) { } var explorer = new Explorers.Insight(url, network); explorer.getTransactions = _.bind(getTransactionsInsight, explorer, url); - explorer.getActivity = _.bind(getActivityInsight, explorer, url); + explorer.getAddressActivity = _.bind(getAddressActivityInsight, explorer, url); explorer.initSocket = _.bind(initSocketInsight, explorer, url); return explorer; default: @@ -56,7 +56,7 @@ function getTransactionsInsight(url, addresses, from, to, cb) { }); }; -function getActivityInsight(url, addresses, cb) { +function getAddressActivityInsight(url, addresses, cb) { getTransactionsInsight(url, addresses, 0, 0, function(err, result) { if (err) return cb(err); return cb(null, result.items > 0); diff --git a/lib/server.js b/lib/server.js index 3316fae..efa0533 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1044,6 +1044,10 @@ WalletService.prototype.getTxHistory = function(opts, cb) { }; +WalletService.scanConfig = { + SCAN_WINDOW: 10, +}; + /** * Scan the blockchain looking for addresses having some activity * @@ -1051,6 +1055,78 @@ WalletService.prototype.getTxHistory = function(opts, cb) { * @param {Boolean} opts.includeCopayerBranches (defaults to false) */ WalletService.prototype.scan = function(opts, cb) { + var self = this; + + opts = opts || {}; + + var allAddresses = []; + + function deriveAddresses(size, isChange, derivator, cb) { + async.map(_.range(size), function(i, next) { + next(null, derivator(isChange)); + }, cb); + }; + + function checkActivity(addresses, cb) { + var bc = self._getBlockchainExplorer(); + bc.getAddressActivity(addresses, cb); + }; + + function scanBranch(isChange, derivator, cb) { + var activity = true; + async.whilst(function() { + return activity; + }, function(next) { + deriveAddresses(WalletService.scanConfig.SCAN_WINDOW, isChange, derivator, function(err, addresses) { + if (err) return next(err); + allAddresses.push(addresses); + checkActivity(_.pluck(addresses, 'address'), function(err, thereIsActivity) { + if (err) return next(err); + activity = thereIsActivity; + next(); + }); + }); + }, cb); + }; + + + Utils.runLocked(self.walletId, cb, function(cb) { + self.getWallet({}, function(err, wallet) { + if (err) return cb(err); + if (!wallet.isComplete()) + return cb(new ClientError('Wallet is not complete')); + + var derivators = []; + derivators.push(_.bind(wallet.createAddress, wallet)); + if (opts.includeCopayerBranches) { + _.each(wallet.copayers, function(copayer) { + derivators.push(_.bind(copayer.createAddress, copayer, wallet)); + }); + } + + var branches = _.flatten( + _.map(derivators, function(derivator) { + return _.map([false, true], function(isChange) { + return { + derivator: derivator, + isChange: isChange + }; + }) + }) + ); + + async.each(branches, function(branch, next) { + scanBranch(branch.isChange, branch.derivator, function(err) { + next(err); + }); + }, function(err) { + if (err) return cb(err); + self.storage.storeAddressAndWallet(wallet, _.flatten(allAddresses), function(err) { + return cb(err); + }); + }); + }); + }); }; diff --git a/lib/storage.js b/lib/storage.js index badd80b..37db926 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -343,16 +343,20 @@ Storage.prototype.fetchAddresses = function(walletId, cb) { }); }; -Storage.prototype.storeAddressAndWallet = function(wallet, address, cb) { - var ops = [{ +Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) { + var ops = _.map([].concat(addresses), function(address) { + return { + type: 'put', + key: KEY.ADDRESS(wallet.id, address.address), + value: address, + }; + }); + ops.unshift({ type: 'put', key: KEY.WALLET(wallet.id), value: wallet, - }, { - type: 'put', - key: KEY.ADDRESS(wallet.id, address.address), - value: address, - }, ]; + }); + this.db.batch(ops, cb); }; diff --git a/test/blockchainexplorer.js b/test/blockchainexplorer.js index b57e33a..2e49139 100644 --- a/test/blockchainexplorer.js +++ b/test/blockchainexplorer.js @@ -16,7 +16,7 @@ describe('Blockchain explorer', function() { should.exist(exp); exp.should.respondTo('broadcast'); exp.should.respondTo('getTransactions'); - exp.should.respondTo('getActivity'); + exp.should.respondTo('getAddressActivity'); exp.should.respondTo('getUnspentUtxos'); exp.should.respondTo('initSocket'); var exp = BlockchainExplorer({ diff --git a/test/integration/server.js b/test/integration/server.js index 5aa0fd9..e477989 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -169,6 +169,12 @@ helpers.stubHistory = function(txs) { blockchainExplorer.getTransactions = sinon.stub().callsArgWith(3, null, txs); }; +helpers.stubAddressActivity = function(activeAddresses) { + blockchainExplorer.getAddressActivity = function(addresses, cb) { + return cb(null, _.intersection(activeAddresses, addresses).length > 0); + }; +}; + helpers.clientSign = WalletUtils.signTxp; helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { @@ -2473,6 +2479,67 @@ describe('Wallet service', function() { }, done); }); }); + + describe('#scan', function() { + WalletService.scanConfig.SCAN_WINDOW = 2; + + it('should scan main addresses', function(done) { + helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']); + helpers.createAndJoinWallet(1, 2, function(server, wallet) { + var expectedPaths = [ + 'm/2147483647/0/0', + 'm/2147483647/0/1', + 'm/2147483647/0/2', + 'm/2147483647/0/3', + 'm/2147483647/1/0', + 'm/2147483647/1/1', + ]; + server.scan({}, function(err) { + should.not.exist(err); + server.storage.fetchAddresses(wallet.id, function(err, addresses) { + should.exist(addresses); + addresses.length.should.equal(expectedPaths.length); + var paths = _.pluck(addresses, 'path'); + _.difference(paths, expectedPaths).length.should.equal(0); + done(); + }) + }); + }); + }); + it('should scan main addresses & copayer addresses', function(done) { + helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']); + helpers.createAndJoinWallet(1, 2, function(server, wallet) { + var expectedPaths = [ + 'm/2147483647/0/0', + 'm/2147483647/0/1', + 'm/2147483647/0/2', + 'm/2147483647/0/3', + 'm/2147483647/1/0', + 'm/2147483647/1/1', + 'm/0/0/0', + 'm/0/0/1', + 'm/0/1/0', + 'm/0/1/1', + 'm/1/0/0', + 'm/1/0/1', + 'm/1/1/0', + 'm/1/1/1', + ]; + server.scan({ + includeCopayerBranches: true + }, function(err) { + should.not.exist(err); + server.storage.fetchAddresses(wallet.id, function(err, addresses) { + should.exist(addresses); + addresses.length.should.equal(expectedPaths.length); + var paths = _.pluck(addresses, 'path'); + _.difference(paths, expectedPaths).length.should.equal(0); + done(); + }) + }); + }); + }); + }); });