From 29920abdb3771940d0f5d4eb5f551a3f19d93507 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 18 Jun 2014 10:58:34 -0300 Subject: [PATCH] Add wallet addresses index discovery on importing backup --- js/models/blockchain/Insight.js | 21 +++++++++++ js/models/core/AddressIndex.js | 2 +- js/models/core/PublicKeyRing.js | 2 - js/models/core/Wallet.js | 66 +++++++++++++++++++++++++++++++++ js/models/core/WalletFactory.js | 5 +++ test/test.Wallet.js | 44 ++++++++++++++++++++++ test/test.blockchain.Insight.js | 30 +++++++++++++++ 7 files changed, 167 insertions(+), 3 deletions(-) diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 897dd22c8..7803fbd10 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -159,6 +159,27 @@ Insight.prototype.sendRawTransaction = function(rawtx, cb) { }); }; +Insight.prototype.checkActivity = function(addresses, cb) { + if (!addresses) throw new Error('address must be set'); + + this.getTransactions(addresses, function onResult(txs) { + var flatArray = function (xss) { return xss.reduce(function(r, xs) { return r.concat(xs); }, []); }; + var getInputs = function (t) { return t.vin.map(function (vin) { return vin.addr }); }; + var getOutputs = function (t) { return flatArray( + t.vout.map(function (vout) { return vout.scriptPubKey.addresses; }) + );}; + + var activityMap = new Array(addresses.length); + var activeAddress = flatArray(txs.map(function(t) { return getInputs(t).concat(getOutputs(t)); })); + activeAddress.forEach(function (addr) { + var index = addresses.indexOf(addr); + if (index != -1) activityMap[index] = true; + }); + + cb(null, activityMap); + }); +}; + Insight.prototype._request = function(options, callback) { diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index 8bfe2ffb6..7920b44a2 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -30,7 +30,7 @@ AddressIndex.prototype.toObj = function() { AddressIndex.prototype.checkRange = function(index, isChange) { if ((isChange && index > this.changeIndex) || (!isChange && index > this.receiveIndex)) { - throw new Error('Out of bounds at index %d isChange: %d', index, isChange); + throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange); } }; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 94cc110f4..fdb0ad8e8 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -157,8 +157,6 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) { // TODO this could be cached PublicKeyRing.prototype.getRedeemScript = function (index, isChange) { - this.indexes.checkRange(index, isChange); - var pubKeys = this.getPubKeys(index, isChange); var script = Script.createMultisig(this.requiredCopayers, pubKeys); return script; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2bb53df60..925bdb674 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -8,6 +8,7 @@ var coinUtil = bitcore.util; var buffertools = bitcore.buffertools; var Builder = bitcore.TransactionBuilder; var http = require('http'); +var async = require('async'); var EventEmitter = imports.EventEmitter || require('events').EventEmitter; var copay = copay || require('../../../copay'); var SecureRandom = bitcore.SecureRandom; @@ -711,6 +712,71 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos return ntxid; }; +Wallet.prototype.updateIndexes = function(callback) { + var self = this; + var start = self.publicKeyRing.indexes.changeIndex; + self.indexDiscovery(start, true, 20, function(err, changeIndex) { + if (err) return callback(err); + if (changeIndex != -1) + self.publicKeyRing.indexes.changeIndex = changeIndex + 1; + + start = self.publicKeyRing.indexes.receiveIndex; + self.indexDiscovery(start, false, 20, function(err, receiveIndex) { + if (err) return callback(err); + if (receiveIndex != -1) + self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1; + + self.emit('publicKeyRingUpdated'); + self.store(); + callback(); + }); + }); +} + +Wallet.prototype.deriveAddresses = function(index, amout, isChange) { + var ret = new Array(amout); + for(var i = 0; i < amout; i++) { + ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString(); + } + return ret; +} + +// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, +// using a scan window of @gap. The argument @change defines the branch to scan: internal or external. +// Returns -1 if no activity is found in range. +Wallet.prototype.indexDiscovery = function(start, change, gap, cb) { + var scanIndex = start; + var lastActive = -1; + var hasActivity = false; + + var self = this; + async.doWhilst( + function _do(next) { + // Optimize window to minimize the derivations. + var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; + var addresses = self.deriveAddresses(scanIndex, scanWindow, change); + self.blockchain.checkActivity(addresses, function(err, actives){ + if (err) throw err; + + // Check for new activities in the newlly scanned addresses + var recentActive = actives.reduce(function(r, e, i) { + return e ? scanIndex + i : r; + }, lastActive); + hasActivity = lastActive != recentActive; + lastActive = recentActive; + scanIndex += scanWindow; + next(); + }); + }, + function _while() { return hasActivity; }, + function _finnaly(err) { + if (err) return cb(err); + cb(null, lastActive); + } + ); +} + + Wallet.prototype.disconnect = function() { this.log('## DISCONNECTING'); this.network.disconnect(); diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 6ecc70b46..b84233e4e 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -71,6 +71,11 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) { var walletObj = this.storage.import(base64); if (!walletObj) return false; var w = this.fromObj(walletObj); + var self = this; + w.updateIndexes(function(err) { + if (err) throw err; + self.log('Indexes updated'); + }); return w; }; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index c1557b3b6..5b894eba6 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -593,4 +593,48 @@ describe('Wallet model', function() { w.getNetworkName().should.equal('testnet'); }); + var mockFakeActivity = function(w, isChange, f) { + var ADDRESSES = w.deriveAddresses(0, 20, isChange); + w.blockchain.checkActivity = function(addresses, cb) { + var activity = new Array(addresses.length); + for(var i=0; i