commit
616f9513e2
|
@ -140,6 +140,12 @@ Returns:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.status` is probably needed in this case.
|
* TX Proposal object. (see [fields on the source code](https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/txproposal.js)). `.status` is probably needed in this case.
|
||||||
|
|
||||||
|
`/v1/addresses/scan`: Start an address scan process looking for activity.
|
||||||
|
|
||||||
|
Optional Arguments:
|
||||||
|
* includeCopayerBranches: Scan all copayer branches following BIP45 recommendation (defaults to false).
|
||||||
|
|
||||||
|
|
||||||
## DELETE Endpoinds
|
## DELETE Endpoinds
|
||||||
`/v1/txproposals/:id/`: Deletes a transaction proposal. Only the creator can delete a TX Proposal, and only if it has no other signatures or rejections
|
`/v1/txproposals/:id/`: Deletes a transaction proposal. Only the creator can delete a TX Proposal, and only if it has no other signatures or rejections
|
||||||
|
|
|
@ -53,6 +53,8 @@ ExpressApp.start = function(opts) {
|
||||||
//var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'})
|
//var accessLogStream = fs.createWriteStream(__dirname + '/access.log', {flags: 'a'})
|
||||||
//app.use(morgan('combined', {stream: accessLogStream}))
|
//app.use(morgan('combined', {stream: accessLogStream}))
|
||||||
app.use(require('morgan')('dev'));
|
app.use(require('morgan')('dev'));
|
||||||
|
} else {
|
||||||
|
log.level = 'silent';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -284,6 +286,16 @@ ExpressApp.start = function(opts) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/v1/addresses/scan/', function(req, res) {
|
||||||
|
getServerWithAuth(req, res, function(server) {
|
||||||
|
server.startScan(req.body, function(err) {
|
||||||
|
if (err) return returnError(err, res, req);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
app.use(opts.basePath || '/bws/api', router);
|
app.use(opts.basePath || '/bws/api', router);
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ Wallet.create = function(opts) {
|
||||||
x.m = opts.m;
|
x.m = opts.m;
|
||||||
x.n = opts.n;
|
x.n = opts.n;
|
||||||
x.status = 'pending';
|
x.status = 'pending';
|
||||||
|
x.scanning = false;
|
||||||
x.publicKeyRing = [];
|
x.publicKeyRing = [];
|
||||||
x.addressIndex = 0;
|
x.addressIndex = 0;
|
||||||
x.copayers = [];
|
x.copayers = [];
|
||||||
|
@ -44,6 +45,7 @@ Wallet.fromObj = function(obj) {
|
||||||
x.m = obj.m;
|
x.m = obj.m;
|
||||||
x.n = obj.n;
|
x.n = obj.n;
|
||||||
x.status = obj.status;
|
x.status = obj.status;
|
||||||
|
x.scanning = obj.scanning;
|
||||||
x.publicKeyRing = obj.publicKeyRing;
|
x.publicKeyRing = obj.publicKeyRing;
|
||||||
x.copayers = _.map(obj.copayers, function(copayer) {
|
x.copayers = _.map(obj.copayers, function(copayer) {
|
||||||
return Copayer.fromObj(copayer);
|
return Copayer.fromObj(copayer);
|
||||||
|
@ -135,6 +137,10 @@ Wallet.prototype.isComplete = function() {
|
||||||
return this.status == 'complete';
|
return this.status == 'complete';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.isScanning = function() {
|
||||||
|
return this.scanning;
|
||||||
|
};
|
||||||
|
|
||||||
Wallet.prototype.createAddress = function(isChange) {
|
Wallet.prototype.createAddress = function(isChange) {
|
||||||
$.checkState(this.isComplete());
|
$.checkState(this.isComplete());
|
||||||
|
|
||||||
|
|
|
@ -253,10 +253,11 @@ WalletService.prototype._emit = function(eventName, args) {
|
||||||
/**
|
/**
|
||||||
* _notify
|
* _notify
|
||||||
*
|
*
|
||||||
* @param type
|
* @param {String} type
|
||||||
* @param data
|
* @param {Object} data
|
||||||
|
* @param {Boolean} isGlobal - If true, the notification is not issued on behalf of any particular copayer (defaults to false)
|
||||||
*/
|
*/
|
||||||
WalletService.prototype._notify = function(type, data) {
|
WalletService.prototype._notify = function(type, data, isGlobal) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
log.debug('Notification', type, data);
|
log.debug('Notification', type, data);
|
||||||
|
@ -270,7 +271,7 @@ WalletService.prototype._notify = function(type, data) {
|
||||||
type: type,
|
type: type,
|
||||||
data: data,
|
data: data,
|
||||||
ticker: this.notifyTicker++,
|
ticker: this.notifyTicker++,
|
||||||
creatorId: copayerId,
|
creatorId: isGlobal ? null : copayerId,
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
});
|
});
|
||||||
this.storage.storeNotification(walletId, n, function() {
|
this.storage.storeNotification(walletId, n, function() {
|
||||||
|
@ -1143,8 +1144,6 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
|
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
var allAddresses = [];
|
|
||||||
|
|
||||||
function deriveAddresses(size, derivator, cb) {
|
function deriveAddresses(size, derivator, cb) {
|
||||||
async.mapSeries(_.range(size), function(i, next) {
|
async.mapSeries(_.range(size), function(i, next) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
@ -1160,6 +1159,7 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
|
|
||||||
function scanBranch(derivator, cb) {
|
function scanBranch(derivator, cb) {
|
||||||
var activity = true;
|
var activity = true;
|
||||||
|
var allAddresses = [];
|
||||||
async.whilst(function() {
|
async.whilst(function() {
|
||||||
return activity;
|
return activity;
|
||||||
}, function(next) {
|
}, function(next) {
|
||||||
|
@ -1172,15 +1172,16 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, cb);
|
}, function(err) {
|
||||||
|
return cb(err, _.flatten(allAddresses));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Utils.runLocked(self.walletId, cb, function(cb) {
|
Utils.runLocked(self.walletId, cb, function(cb) {
|
||||||
self.getWallet({}, function(err, wallet) {
|
self.getWallet({}, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!wallet.isComplete())
|
if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete'));
|
||||||
return cb(new ClientError('Wallet is not complete'));
|
|
||||||
|
|
||||||
var derivators = [];
|
var derivators = [];
|
||||||
_.each([false, true], function(isChange) {
|
_.each([false, true], function(isChange) {
|
||||||
|
@ -1193,17 +1194,49 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
async.eachSeries(derivators, function(derivator, next) {
|
async.eachSeries(derivators, function(derivator, next) {
|
||||||
scanBranch(derivator, next);
|
scanBranch(derivator, function(err, addresses) {
|
||||||
}, function(err) {
|
if (err) return next(err);
|
||||||
if (err) return cb(err);
|
self.storage.storeAddressAndWallet(wallet, addresses, next);
|
||||||
self.storage.storeAddressAndWallet(wallet, _.flatten(allAddresses), function(err) {
|
|
||||||
return cb(err);
|
|
||||||
});
|
});
|
||||||
});
|
}, cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a scan process.
|
||||||
|
*
|
||||||
|
* @param {Object} opts
|
||||||
|
* @param {Boolean} opts.includeCopayerBranches (defaults to false)
|
||||||
|
*/
|
||||||
|
WalletService.prototype.startScan = function(opts, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
function scanFinished(err) {
|
||||||
|
var data = {};
|
||||||
|
if (err) {
|
||||||
|
data.result = 'error';
|
||||||
|
data.error = err;
|
||||||
|
} else {
|
||||||
|
data.result = 'success';
|
||||||
|
}
|
||||||
|
self._notify('ScanFinished', data, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
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'));
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
self.scan(opts, scanFinished);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return cb()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = WalletService;
|
module.exports = WalletService;
|
||||||
module.exports.ClientError = ClientError;
|
module.exports.ClientError = ClientError;
|
||||||
|
|
|
@ -20,7 +20,7 @@ var io, bcMonitor;
|
||||||
|
|
||||||
var WsApp = function() {};
|
var WsApp = function() {};
|
||||||
|
|
||||||
WsApp._unauthorized = function() {
|
WsApp._unauthorized = function(socket) {
|
||||||
socket.emit('unauthorized');
|
socket.emit('unauthorized');
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
};
|
};
|
||||||
|
@ -52,10 +52,10 @@ WsApp.start = function(server) {
|
||||||
socket.emit('challenge', socket.nonce);
|
socket.emit('challenge', socket.nonce);
|
||||||
|
|
||||||
socket.on('authorize', function(data) {
|
socket.on('authorize', function(data) {
|
||||||
if (data.message != socket.nonce) return WsApp.unauthorized();
|
if (data.message != socket.nonce) return WsApp._unauthorized(socket);
|
||||||
|
|
||||||
WalletService.getInstanceWithAuth(data, function(err, service) {
|
WalletService.getInstanceWithAuth(data, function(err, service) {
|
||||||
if (err) return WsApp.unauthorized();
|
if (err) return WsApp._unauthorized(socket);
|
||||||
|
|
||||||
socket.join(service.walletId);
|
socket.join(service.walletId);
|
||||||
socket.emit('authorized');
|
socket.emit('authorized');
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "bitcore-wallet-service",
|
"name": "bitcore-wallet-service",
|
||||||
"description": "A service for Mutisig HD Bitcoin Wallets",
|
"description": "A service for Mutisig HD Bitcoin Wallets",
|
||||||
"author": "BitPay Inc",
|
"author": "BitPay Inc",
|
||||||
"version": "0.0.18",
|
"version": "0.0.19",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"copay",
|
"copay",
|
||||||
|
|
|
@ -2518,11 +2518,18 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#scan', function() {
|
describe('#scan', function() {
|
||||||
|
var server, wallet;
|
||||||
var scanConfigOld = WalletService.scanConfig;
|
var scanConfigOld = WalletService.scanConfig;
|
||||||
beforeEach(function() {
|
beforeEach(function(done) {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
WalletService.scanConfig.SCAN_WINDOW = 2;
|
WalletService.scanConfig.SCAN_WINDOW = 2;
|
||||||
WalletService.scanConfig.DERIVATION_DELAY = 0;
|
WalletService.scanConfig.DERIVATION_DELAY = 0;
|
||||||
|
|
||||||
|
helpers.createAndJoinWallet(1, 2, function(s, w) {
|
||||||
|
server = s;
|
||||||
|
wallet = w;
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
WalletService.scanConfig = scanConfigOld;
|
WalletService.scanConfig = scanConfigOld;
|
||||||
|
@ -2530,76 +2537,70 @@ describe('Wallet service', function() {
|
||||||
|
|
||||||
it('should scan main addresses', function(done) {
|
it('should scan main addresses', function(done) {
|
||||||
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
|
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
|
||||||
helpers.createAndJoinWallet(1, 2, function(server, wallet) {
|
var expectedPaths = [
|
||||||
var expectedPaths = [
|
'm/2147483647/0/0',
|
||||||
'm/2147483647/0/0',
|
'm/2147483647/0/1',
|
||||||
'm/2147483647/0/1',
|
'm/2147483647/0/2',
|
||||||
'm/2147483647/0/2',
|
'm/2147483647/0/3',
|
||||||
'm/2147483647/0/3',
|
'm/2147483647/1/0',
|
||||||
'm/2147483647/1/0',
|
'm/2147483647/1/1',
|
||||||
'm/2147483647/1/1',
|
];
|
||||||
];
|
server.scan({}, function(err) {
|
||||||
server.scan({}, function(err) {
|
should.not.exist(err);
|
||||||
should.not.exist(err);
|
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
should.exist(addresses);
|
||||||
should.exist(addresses);
|
addresses.length.should.equal(expectedPaths.length);
|
||||||
addresses.length.should.equal(expectedPaths.length);
|
var paths = _.pluck(addresses, 'path');
|
||||||
var paths = _.pluck(addresses, 'path');
|
_.difference(paths, expectedPaths).length.should.equal(0);
|
||||||
_.difference(paths, expectedPaths).length.should.equal(0);
|
server.createAddress({}, function(err, address) {
|
||||||
server.createAddress({}, function(err, address) {
|
should.not.exist(err);
|
||||||
should.not.exist(err);
|
address.path.should.equal('m/2147483647/0/4');
|
||||||
address.path.should.equal('m/2147483647/0/4');
|
done();
|
||||||
done();
|
});
|
||||||
});
|
})
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should scan main addresses & copayer addresses', function(done) {
|
it('should scan main addresses & copayer addresses', function(done) {
|
||||||
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
|
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
|
||||||
helpers.createAndJoinWallet(1, 2, function(server, wallet) {
|
var expectedPaths = [
|
||||||
var expectedPaths = [
|
'm/2147483647/0/0',
|
||||||
'm/2147483647/0/0',
|
'm/2147483647/0/1',
|
||||||
'm/2147483647/0/1',
|
'm/2147483647/0/2',
|
||||||
'm/2147483647/0/2',
|
'm/2147483647/0/3',
|
||||||
'm/2147483647/0/3',
|
'm/2147483647/1/0',
|
||||||
'm/2147483647/1/0',
|
'm/2147483647/1/1',
|
||||||
'm/2147483647/1/1',
|
'm/0/0/0',
|
||||||
'm/0/0/0',
|
'm/0/0/1',
|
||||||
'm/0/0/1',
|
'm/0/1/0',
|
||||||
'm/0/1/0',
|
'm/0/1/1',
|
||||||
'm/0/1/1',
|
'm/1/0/0',
|
||||||
'm/1/0/0',
|
'm/1/0/1',
|
||||||
'm/1/0/1',
|
'm/1/1/0',
|
||||||
'm/1/1/0',
|
'm/1/1/1',
|
||||||
'm/1/1/1',
|
];
|
||||||
];
|
server.scan({
|
||||||
server.scan({
|
includeCopayerBranches: true
|
||||||
includeCopayerBranches: true
|
}, function(err) {
|
||||||
}, function(err) {
|
should.not.exist(err);
|
||||||
should.not.exist(err);
|
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
should.exist(addresses);
|
||||||
should.exist(addresses);
|
addresses.length.should.equal(expectedPaths.length);
|
||||||
addresses.length.should.equal(expectedPaths.length);
|
var paths = _.pluck(addresses, 'path');
|
||||||
var paths = _.pluck(addresses, 'path');
|
_.difference(paths, expectedPaths).length.should.equal(0);
|
||||||
_.difference(paths, expectedPaths).length.should.equal(0);
|
done();
|
||||||
done();
|
})
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should restore wallet balance', function(done) {
|
it('should restore wallet balance', function(done) {
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
|
|
||||||
function(next) {
|
function(next) {
|
||||||
helpers.createAndJoinWallet(1, 2, function(server, wallet) {
|
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
|
||||||
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
|
should.exist(utxos);
|
||||||
should.exist(utxos);
|
helpers.stubAddressActivity(_.pluck(utxos, 'address'));
|
||||||
helpers.stubAddressActivity(_.pluck(utxos, 'address'));
|
server.getBalance({}, function(err, balance) {
|
||||||
server.getBalance({}, function(err, balance) {
|
balance.totalAmount.should.equal(helpers.toSatoshi(6));
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(6));
|
next(null, server, wallet);
|
||||||
next(null, server, wallet);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -2834,6 +2835,57 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#startScan', function() {
|
||||||
|
var server, wallet;
|
||||||
|
var scanConfigOld = WalletService.scanConfig;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
this.timeout(5000);
|
||||||
|
WalletService.scanConfig.SCAN_WINDOW = 2;
|
||||||
|
WalletService.scanConfig.DERIVATION_DELAY = 0;
|
||||||
|
|
||||||
|
helpers.createAndJoinWallet(1, 2, function(s, w) {
|
||||||
|
server = s;
|
||||||
|
wallet = w;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
WalletService.scanConfig = scanConfigOld;
|
||||||
|
WalletService.onNotification(function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start an asynchronous scan', function(done) {
|
||||||
|
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
|
||||||
|
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',
|
||||||
|
];
|
||||||
|
WalletService.onNotification(function(n) {
|
||||||
|
if (n.type == 'ScanFinished') {
|
||||||
|
should.not.exist(n.creatorId);
|
||||||
|
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);
|
||||||
|
server.createAddress({}, function(err, address) {
|
||||||
|
should.not.exist(err);
|
||||||
|
address.path.should.equal('m/2147483647/0/4');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.startScan({}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue