Merge pull request #699 from yemel/feature/one-time-backups

Add wallet addresses index discovery on importing backup
This commit is contained in:
Matias Alejo Garcia 2014-06-19 15:32:37 -03:00
commit 50823ebd1c
8 changed files with 222 additions and 18 deletions

View file

@ -6,21 +6,16 @@ angular.module('copayApp.controllers').controller('ImportController',
var reader = new FileReader();
var _importBackup = function(encryptedObj) {
Passphrase.getBase64Async($scope.password, function(passphrase){
var w, errMsg;
try {
w = walletFactory.fromEncryptedObj(encryptedObj, passphrase);
} catch(e) {
errMsg = e.message;
}
if (!w) {
$scope.loading = false;
$rootScope.$flashMessage = { message: errMsg || 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
walletFactory.import(encryptedObj, passphrase, function(err, w) {
if (err) {
$scope.loading = false;
$rootScope.$flashMessage = { message: err.errMsg || 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet, $scope);
});
});
};

View file

@ -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) {

View file

@ -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);
}
};

View file

@ -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;

View file

@ -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();

View file

@ -74,6 +74,16 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) {
return w;
};
WalletFactory.prototype.import = function(base64, password, cb) {
var self = this;
var w = self.fromEncryptedObj(base64, password);
w.updateIndexes(function(err) {
if (err) return cb(err);
self.log('Indexes updated');
cb(null, w);
});
}
WalletFactory.prototype.read = function(walletId) {
if (!this._checkRead(walletId))
return false;