bitcore-wallet-service/test/integration/server.js

3053 lines
98 KiB
JavaScript
Raw Normal View History

2015-01-27 05:18:45 -08:00
'use strict';
var _ = require('lodash');
var async = require('async');
2015-02-21 17:35:12 -08:00
var inspect = require('util').inspect;
2015-01-27 05:18:45 -08:00
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var levelup = require('levelup');
var memdown = require('memdown');
2015-02-21 17:35:12 -08:00
var log = require('npmlog');
log.debug = log.verbose;
2015-01-27 05:18:45 -08:00
2015-02-16 10:02:20 -08:00
var Utils = require('../../lib/utils');
2015-03-04 07:44:28 -08:00
var WalletUtils = require('bitcore-wallet-utils');
2015-03-12 07:34:41 -07:00
var Bitcore = WalletUtils.Bitcore;
2015-02-16 10:02:20 -08:00
var Storage = require('../../lib/storage');
2015-03-31 08:04:02 -07:00
var BlockchainMonitor = require('../../lib/blockchainmonitor');
2015-01-28 05:52:45 -08:00
2015-02-16 10:02:20 -08:00
var Wallet = require('../../lib/model/wallet');
var TxProposal = require('../../lib/model/txproposal');
2015-02-16 10:02:20 -08:00
var Address = require('../../lib/model/address');
var Copayer = require('../../lib/model/copayer');
2015-02-20 12:32:19 -08:00
var WalletService = require('../../lib/server');
var NotificationBroadcaster = require('../../lib/notificationbroadcaster');
2015-02-16 10:02:20 -08:00
var TestData = require('../testdata');
2015-02-01 11:50:58 -08:00
2015-01-29 10:00:35 -08:00
var helpers = {};
2015-02-06 23:09:45 -08:00
helpers.getAuthServer = function(copayerId, cb) {
2015-02-20 12:32:19 -08:00
var signatureStub = sinon.stub(WalletService.prototype, '_verifySignature');
2015-02-06 12:56:51 -08:00
signatureStub.returns(true);
2015-02-20 12:32:19 -08:00
WalletService.getInstanceWithAuth({
2015-02-06 12:56:51 -08:00
copayerId: copayerId,
message: 'dummy',
signature: 'dummy',
2015-02-06 23:09:45 -08:00
}, function(err, server) {
if (err || !server) throw new Error('Could not login as copayerId ' + copayerId);
2015-02-06 12:56:51 -08:00
signatureStub.restore();
return cb(server);
});
};
2015-03-09 14:11:25 -07:00
helpers._generateCopayersTestData = function(n) {
console.log('var copayers = [');
_.each(_.range(n), function(c) {
var xpriv = new Bitcore.HDPrivateKey();
var xpub = Bitcore.HDPublicKey(xpriv);
var xpriv_45H = xpriv.derive(45, true);
var xpub_45H = Bitcore.HDPublicKey(xpriv_45H);
var id = WalletUtils.xPubToCopayerId(xpub_45H.toString());
var xpriv_1H = xpriv.derive(1, true);
var xpub_1H = Bitcore.HDPublicKey(xpriv_1H);
var priv = xpriv_1H.derive(0).privateKey;
var pub = xpub_1H.derive(0).publicKey;
console.log('{id: ', "'" + id + "',");
console.log('xPrivKey: ', "'" + xpriv.toString() + "',");
console.log('xPubKey: ', "'" + xpub.toString() + "',");
console.log('xPrivKey_45H: ', "'" + xpriv_45H.toString() + "',");
console.log('xPubKey_45H: ', "'" + xpub_45H.toString() + "',");
console.log('xPrivKey_1H: ', "'" + xpriv_1H.toString() + "',");
console.log('xPubKey_1H: ', "'" + xpub_1H.toString() + "',");
console.log('privKey_1H_0: ', "'" + priv.toString() + "',");
console.log('pubKey_1H_0: ', "'" + pub.toString() + "'},");
});
console.log('];');
};
2015-03-10 07:23:23 -07:00
helpers.getSignedCopayerOpts = function(opts) {
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
opts.copayerSignature = WalletUtils.signMessage(hash, TestData.keyPair.priv);
return opts;
};
2015-02-07 08:13:29 -08:00
helpers.createAndJoinWallet = function(m, n, cb) {
2015-02-20 12:32:19 -08:00
var server = new WalletService();
2015-02-07 07:48:57 -08:00
var copayerIds = [];
2015-02-17 12:36:45 -08:00
var offset = helpers.offset || 0;
2015-02-06 12:56:51 -08:00
2015-01-29 10:00:35 -08:00
var walletOpts = {
2015-02-07 08:13:29 -08:00
name: 'a wallet',
2015-01-29 10:00:35 -08:00
m: m,
n: n,
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
2015-01-29 10:00:35 -08:00
};
2015-02-07 08:13:29 -08:00
server.createWallet(walletOpts, function(err, walletId) {
2015-01-29 10:00:35 -08:00
if (err) return cb(err);
2015-02-10 08:20:41 -08:00
async.each(_.range(n), function(i, cb) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-07 08:13:29 -08:00
walletId: walletId,
2015-02-10 08:20:41 -08:00
name: 'copayer ' + (i + 1),
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[i + offset].xPubKey_45H,
requestPubKey: TestData.copayers[i + offset].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-02 06:55:03 -08:00
2015-02-12 19:00:54 -08:00
server.joinWallet(copayerOpts, function(err, result) {
2015-02-17 12:36:45 -08:00
should.not.exist(err);
2015-02-12 19:00:54 -08:00
copayerIds.push(result.copayerId);
2015-01-29 10:00:35 -08:00
return cb(err);
});
2015-02-06 23:09:45 -08:00
}, function(err) {
2015-02-06 12:56:51 -08:00
if (err) return new Error('Could not generate wallet');
2015-02-07 07:48:57 -08:00
helpers.getAuthServer(copayerIds[0], function(s) {
2015-02-06 23:09:45 -08:00
s.getWallet({}, function(err, w) {
2015-02-12 19:16:35 -08:00
cb(s, w);
2015-02-06 12:56:51 -08:00
});
2015-01-29 10:00:35 -08:00
});
});
});
};
2015-02-03 11:46:28 -08:00
helpers.randomTXID = function() {
2015-02-03 18:17:06 -08:00
return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');;
2015-02-03 11:46:28 -08:00
};
2015-02-04 11:18:36 -08:00
helpers.toSatoshi = function(btc) {
if (_.isArray(btc)) {
return _.map(btc, helpers.toSatoshi);
} else {
return Utils.strip(btc * 1e8);
}
};
// Amounts in satoshis
2015-02-20 12:23:42 -08:00
helpers.stubUtxos = function(server, wallet, amounts, cb) {
var amounts = [].concat(amounts);
2015-02-03 11:46:28 -08:00
2015-02-21 18:42:14 -08:00
async.map(_.range(1, Math.ceil(amounts.length / 2) + 1), function(i, next) {
2015-02-21 06:31:15 -08:00
server.createAddress({}, function(err, address) {
next(err, address);
2015-02-06 10:15:54 -08:00
});
2015-02-21 06:31:15 -08:00
}, function(err, addresses) {
if (err) throw new Error('Could not generate addresses');
var utxos = _.map(amounts, function(amount, i) {
var address = addresses[i % addresses.length];
2015-02-21 22:46:47 -08:00
var obj = {
2015-02-21 06:31:15 -08:00
txid: helpers.randomTXID(),
vout: Math.floor((Math.random() * 10) + 1),
satoshis: helpers.toSatoshi(amount).toString(),
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
address: address.address,
};
2015-02-22 12:44:37 -08:00
obj.toObject = function() {
return obj;
};
2015-02-21 22:46:47 -08:00
return obj;
2015-02-21 06:31:15 -08:00
});
2015-03-30 16:16:51 -07:00
blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
2015-02-21 06:31:15 -08:00
return cb(utxos);
});
2015-02-06 10:15:54 -08:00
};
2015-02-04 06:43:12 -08:00
2015-02-20 12:23:42 -08:00
helpers.stubBroadcast = function(txid) {
2015-03-30 16:16:51 -07:00
blockchainExplorer.broadcast = sinon.stub().callsArgWith(1, null, txid);
2015-01-30 12:37:30 -08:00
};
2015-01-29 10:00:35 -08:00
2015-02-20 12:23:42 -08:00
helpers.stubBroadcastFail = function() {
2015-03-30 16:16:51 -07:00
blockchainExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error');
2015-02-20 12:23:42 -08:00
};
2015-02-06 10:15:54 -08:00
2015-02-21 17:35:12 -08:00
helpers.stubHistory = function(txs) {
blockchainExplorer.getTransactions = sinon.stub().callsArgWith(3, null, txs);
2015-02-21 17:35:12 -08:00
};
2015-02-06 10:15:54 -08:00
2015-04-01 12:42:12 -07:00
helpers.stubAddressActivity = function(activeAddresses) {
blockchainExplorer.getAddressActivity = function(addresses, cb) {
return cb(null, _.intersection(activeAddresses, addresses).length > 0);
};
};
helpers.clientSign = WalletUtils.signTxp;
2015-02-04 06:43:12 -08:00
2015-02-10 11:15:05 -08:00
helpers.createProposalOpts = function(toAddress, amount, message, signingKey) {
var opts = {
toAddress: toAddress,
amount: helpers.toSatoshi(amount),
message: message,
2015-02-13 11:57:28 -08:00
proposalSignature: null,
2015-02-10 11:15:05 -08:00
};
2015-02-17 08:11:14 -08:00
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
2015-02-13 11:57:28 -08:00
try {
2015-02-17 11:42:47 -08:00
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
2015-02-13 11:57:28 -08:00
} catch (ex) {}
2015-02-10 11:15:05 -08:00
return opts;
2015-02-10 05:22:23 -08:00
};
2015-02-21 17:35:12 -08:00
helpers.createAddresses = function(server, wallet, main, change, cb) {
async.map(_.range(main + change), function(i, next) {
var address = wallet.createAddress(i >= main);
server.storage.storeAddressAndWallet(wallet, address, function(err) {
if (err) return next(err);
next(null, address);
});
}, function(err, addresses) {
if (err) throw new Error('Could not generate addresses');
return cb(_.take(addresses, main), _.takeRight(addresses, change));
});
};
2015-03-30 16:16:51 -07:00
var db, storage, blockchainExplorer;
2015-01-27 05:18:45 -08:00
2015-01-29 10:00:35 -08:00
2015-03-31 08:04:02 -07:00
describe('Wallet service', function() {
2015-02-12 12:15:48 -08:00
beforeEach(function() {
db = levelup(memdown, {
valueEncoding: 'json'
2015-02-06 23:09:45 -08:00
});
2015-02-12 12:15:48 -08:00
storage = new Storage({
db: db
});
2015-03-30 16:16:51 -07:00
blockchainExplorer = sinon.stub();
2015-02-20 12:23:42 -08:00
2015-02-20 12:32:19 -08:00
WalletService.initialize({
2015-02-20 12:23:42 -08:00
storage: storage,
2015-03-30 16:16:51 -07:00
blockchainExplorer: blockchainExplorer,
2015-02-12 12:15:48 -08:00
});
2015-02-17 12:36:45 -08:00
helpers.offset = 0;
2015-02-12 12:15:48 -08:00
});
2015-01-27 05:18:45 -08:00
2015-02-21 22:46:47 -08:00
2015-02-12 12:15:48 -08:00
describe('#getInstanceWithAuth', function() {
beforeEach(function() {});
2015-02-11 08:44:04 -08:00
2015-02-12 12:15:48 -08:00
it('should get server instance for existing copayer', function(done) {
2015-02-17 12:36:45 -08:00
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(1, 2, function(s, wallet) {
var xpriv = TestData.copayers[0].xPrivKey;
2015-03-09 14:11:25 -07:00
var priv = TestData.copayers[0].privKey_1H_0;
2015-02-11 08:44:04 -08:00
2015-02-26 05:37:21 -08:00
var sig = WalletUtils.signMessage('hello world', priv);
2015-02-11 08:44:04 -08:00
2015-02-20 12:32:19 -08:00
WalletService.getInstanceWithAuth({
2015-02-12 19:16:35 -08:00
copayerId: wallet.copayers[0].id,
2015-02-26 05:37:21 -08:00
message: 'hello world',
2015-02-12 12:15:48 -08:00
signature: sig,
}, function(err, server) {
should.not.exist(err);
done();
2015-02-11 08:44:04 -08:00
});
2015-02-12 12:15:48 -08:00
});
});
it('should fail when requesting for non-existent copayer', function(done) {
2015-02-20 12:32:19 -08:00
WalletService.getInstanceWithAuth({
2015-02-12 12:15:48 -08:00
copayerId: 'ads',
message: TestData.message.text,
signature: TestData.message.signature,
}, function(err, server) {
err.code.should.equal('NOTAUTHORIZED');
err.message.should.contain('Copayer not found');
done();
});
});
2015-01-27 05:18:45 -08:00
2015-02-12 12:15:48 -08:00
it('should fail when message signature cannot be verified', function(done) {
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(1, 2, function(s, wallet) {
2015-02-20 12:32:19 -08:00
WalletService.getInstanceWithAuth({
2015-02-12 19:16:35 -08:00
copayerId: wallet.copayers[0].id,
2015-02-11 08:44:04 -08:00
message: 'dummy',
signature: 'dummy',
}, function(err, server) {
2015-02-12 12:15:48 -08:00
err.code.should.equal('NOTAUTHORIZED');
err.message.should.contain('Invalid signature');
2015-02-11 08:44:04 -08:00
done();
});
});
});
2015-02-12 12:15:48 -08:00
});
2015-01-27 05:18:45 -08:00
describe('#createWallet', function() {
2015-02-06 12:56:51 -08:00
var server;
2015-01-27 05:18:45 -08:00
beforeEach(function() {
2015-02-20 12:32:19 -08:00
server = new WalletService();
2015-01-27 05:18:45 -08:00
});
it('should create and store wallet', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
2015-01-27 05:18:45 -08:00
};
2015-02-07 08:13:29 -08:00
server.createWallet(opts, function(err, walletId) {
2015-01-27 05:18:45 -08:00
should.not.exist(err);
2015-02-07 08:13:29 -08:00
server.storage.fetchWallet(walletId, function(err, wallet) {
2015-01-27 05:18:45 -08:00
should.not.exist(err);
2015-02-07 08:13:29 -08:00
wallet.id.should.equal(walletId);
2015-01-27 05:18:45 -08:00
wallet.name.should.equal('my wallet');
done();
});
});
});
2015-03-31 13:28:01 -07:00
it('should create wallet with given id', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
id: '1234',
};
server.createWallet(opts, function(err, walletId) {
should.not.exist(err);
server.storage.fetchWallet('1234', function(err, wallet) {
should.not.exist(err);
wallet.id.should.equal(walletId);
wallet.name.should.equal('my wallet');
done();
});
});
});
it('should fail to create wallets with same id', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
id: '1234',
};
server.createWallet(opts, function(err, walletId) {
server.createWallet(opts, function(err, walletId) {
err.message.should.contain('Wallet already exists');
done();
});
});
});
2015-02-08 08:16:41 -08:00
it('should fail to create wallet with no name', function(done) {
var opts = {
name: '',
m: 2,
n: 3,
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
2015-02-08 08:16:41 -08:00
};
server.createWallet(opts, function(err, walletId) {
should.not.exist(walletId);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-08 08:16:41 -08:00
err.message.should.contain('name');
done();
});
});
2015-02-03 04:40:39 -08:00
it('should fail to create wallet with invalid copayer pairs', function(done) {
2015-02-03 11:46:28 -08:00
var invalidPairs = [{
m: 0,
n: 0
}, {
m: 0,
n: 2
}, {
m: 2,
n: 1
}, {
m: 0,
n: 10
}, {
m: 1,
n: 20
}, {
m: 10,
n: 10
}, ];
2015-02-03 04:40:39 -08:00
var opts = {
id: '123',
name: 'my wallet',
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
2015-02-03 04:40:39 -08:00
};
2015-02-03 11:46:28 -08:00
async.each(invalidPairs, function(pair, cb) {
2015-02-03 04:40:39 -08:00
opts.m = pair.m;
opts.n = pair.n;
server.createWallet(opts, function(err) {
should.exist(err);
2015-02-04 07:46:31 -08:00
err.message.should.equal('Invalid combination of required copayers / total copayers');
2015-02-03 04:40:39 -08:00
return cb();
});
2015-02-03 11:46:28 -08:00
}, function(err) {
2015-02-03 04:40:39 -08:00
done();
});
2015-02-03 11:46:28 -08:00
});
2015-02-23 13:31:27 -08:00
2015-02-24 05:36:14 -08:00
it('should fail to create wallet with invalid pubKey argument', function(done) {
var opts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: 'dummy',
};
server.createWallet(opts, function(err, walletId) {
should.not.exist(walletId);
should.exist(err);
err.message.should.contain('Invalid public key');
done();
});
});
2015-01-27 05:18:45 -08:00
});
describe('#joinWallet', function() {
2015-02-08 08:36:19 -08:00
var server, walletId;
beforeEach(function(done) {
2015-02-20 12:32:19 -08:00
server = new WalletService();
2015-01-27 05:18:45 -08:00
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
2015-01-27 05:18:45 -08:00
};
2015-02-08 08:36:19 -08:00
server.createWallet(walletOpts, function(err, wId) {
should.not.exist(err);
should.exist.walletId;
walletId = wId;
done();
});
});
2015-02-01 11:50:58 -08:00
2015-02-08 08:36:19 -08:00
it('should join existing wallet', function(done) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-08 08:36:19 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-12 19:00:54 -08:00
server.joinWallet(copayerOpts, function(err, result) {
2015-01-27 05:18:45 -08:00
should.not.exist(err);
2015-02-12 19:00:54 -08:00
var copayerId = result.copayerId;
2015-02-08 08:36:19 -08:00
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
wallet.id.should.equal(walletId);
wallet.copayers.length.should.equal(1);
var copayer = wallet.copayers[0];
copayer.name.should.equal('me');
copayer.id.should.equal(copayerId);
done();
2015-01-27 05:18:45 -08:00
});
});
});
});
2015-02-08 08:36:19 -08:00
it('should fail to join with no name', function(done) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-08 08:36:19 -08:00
walletId: walletId,
name: '',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-12 19:00:54 -08:00
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(result);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-08 08:36:19 -08:00
err.message.should.contain('name');
done();
});
});
2015-02-01 11:50:58 -08:00
it('should fail to join non-existent wallet', function(done) {
2015-02-07 08:13:29 -08:00
var copayerOpts = {
2015-02-08 08:36:19 -08:00
walletId: '123',
2015-02-07 08:13:29 -08:00
name: 'me',
xPubKey: 'dummy',
2015-03-10 07:23:23 -07:00
requestPubKey: 'dummy',
copayerSignature: 'dummy',
2015-01-27 05:18:45 -08:00
};
2015-02-07 08:13:29 -08:00
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
done();
2015-01-27 05:18:45 -08:00
});
});
2015-02-01 11:50:58 -08:00
it('should fail to join full wallet', function(done) {
2015-02-08 08:36:19 -08:00
helpers.createAndJoinWallet(1, 1, function(s, wallet) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-08 08:36:19 -08:00
walletId: wallet.id,
2015-01-27 05:18:45 -08:00
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-08 08:36:19 -08:00
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
err.code.should.equal('WFULL');
err.message.should.equal('Wallet full');
done();
2015-01-27 05:18:45 -08:00
});
});
});
2015-02-01 11:50:58 -08:00
it('should fail to re-join wallet', function(done) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-08 08:36:19 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-08 08:36:19 -08:00
server.joinWallet(copayerOpts, function(err) {
2015-01-27 05:18:45 -08:00
should.not.exist(err);
2015-02-01 11:50:58 -08:00
server.joinWallet(copayerOpts, function(err) {
2015-02-08 08:36:19 -08:00
should.exist(err);
err.code.should.equal('CINWALLET');
err.message.should.equal('Copayer already in wallet');
done();
2015-01-27 05:18:45 -08:00
});
});
});
2015-02-24 05:36:14 -08:00
it('should fail two wallets with same xPubKey', function(done) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-24 05:36:14 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-24 05:36:14 -08:00
server.joinWallet(copayerOpts, function(err) {
should.not.exist(err);
var walletOpts = {
name: 'my other wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
2015-03-10 07:23:23 -07:00
copayerOpts = helpers.getSignedCopayerOpts({
2015-02-24 05:36:14 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-24 05:36:14 -08:00
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
err.code.should.equal('CREGISTERED');
err.message.should.equal('Copayer ID already registered on server');
done();
});
});
});
});
2015-02-23 13:31:27 -08:00
2015-02-01 11:50:58 -08:00
it('should fail to join with bad formated signature', function(done) {
2015-02-08 08:36:19 -08:00
var copayerOpts = {
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
copayerSignature: 'bad sign',
2015-02-01 11:50:58 -08:00
};
2015-02-08 08:36:19 -08:00
server.joinWallet(copayerOpts, function(err) {
err.message.should.equal('Bad request');
done();
2015-02-01 11:50:58 -08:00
});
});
it('should fail to join with null signature', function(done) {
2015-02-08 08:36:19 -08:00
var copayerOpts = {
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
2015-03-10 07:23:23 -07:00
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-02-01 11:50:58 -08:00
};
server.joinWallet(copayerOpts, function(err) {
2015-02-24 05:36:14 -08:00
should.exist(err);
err.message.should.contain('argument missing');
2015-02-08 08:36:19 -08:00
done();
});
2015-02-01 11:50:58 -08:00
});
it('should fail to join with wrong signature', function(done) {
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-08 08:36:19 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
copayerOpts.name = 'me2';
2015-02-08 08:36:19 -08:00
server.joinWallet(copayerOpts, function(err) {
err.message.should.equal('Bad request');
done();
2015-02-01 11:50:58 -08:00
});
});
2015-02-02 11:16:14 -08:00
it('should set pkr and status = complete on last copayer joining (2-3)', function(done) {
2015-02-07 08:13:29 -08:00
helpers.createAndJoinWallet(2, 3, function(server) {
2015-02-06 12:56:51 -08:00
server.getWallet({}, function(err, wallet) {
2015-01-27 05:18:45 -08:00
should.not.exist(err);
wallet.status.should.equal('complete');
wallet.publicKeyRing.length.should.equal(3);
done();
});
});
2015-02-01 11:50:58 -08:00
});
2015-01-27 05:18:45 -08:00
});
2015-01-28 07:16:19 -08:00
describe('#verifyMessageSignature', function() {
2015-02-06 15:59:59 -08:00
var server, wallet;
beforeEach(function(done) {
2015-02-10 13:04:50 -08:00
helpers.createAndJoinWallet(2, 3, function(s, w) {
2015-02-06 15:59:59 -08:00
server = s;
wallet = w;
done();
});
2015-01-28 07:06:34 -08:00
});
2015-02-01 11:50:58 -08:00
it('should successfully verify message signature', function(done) {
2015-02-06 15:59:59 -08:00
var opts = {
2015-02-10 08:20:41 -08:00
message: TestData.message.text,
signature: TestData.message.signature,
2015-02-06 15:59:59 -08:00
};
server.verifyMessageSignature(opts, function(err, isValid) {
should.not.exist(err);
isValid.should.equal(true);
done();
2015-01-28 07:06:34 -08:00
});
});
2015-02-06 12:56:51 -08:00
it('should fail to verify message signature for different copayer', function(done) {
2015-02-06 15:59:59 -08:00
var opts = {
2015-02-10 08:20:41 -08:00
message: TestData.message.text,
signature: TestData.message.signature,
2015-02-06 15:59:59 -08:00
};
2015-02-07 08:13:29 -08:00
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
2015-02-06 15:59:59 -08:00
server.verifyMessageSignature(opts, function(err, isValid) {
should.not.exist(err);
isValid.should.be.false;
done();
2015-01-28 07:06:34 -08:00
});
});
});
});
2015-01-27 11:40:21 -08:00
describe('#createAddress', function() {
2015-02-06 15:59:59 -08:00
var server, wallet;
beforeEach(function(done) {
2015-02-07 08:13:29 -08:00
helpers.createAndJoinWallet(2, 2, function(s, w) {
2015-02-06 15:59:59 -08:00
server = s;
wallet = w;
done();
});
2015-01-27 05:18:45 -08:00
});
2015-03-30 08:45:43 -07:00
it('should create address', function(done) {
server.createAddress({}, function(err, address) {
2015-02-06 15:59:59 -08:00
should.not.exist(err);
address.should.exist;
2015-03-09 14:11:25 -07:00
address.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg');
2015-02-21 14:13:15 -08:00
address.isChange.should.be.false;
2015-02-06 15:59:59 -08:00
address.path.should.equal('m/2147483647/0/0');
2015-03-30 08:43:43 -07:00
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
type: 'NewAddress'
});
should.exist(notif);
2015-03-30 08:45:43 -07:00
notif.data.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg');
2015-03-30 08:43:43 -07:00
done();
});
2015-02-02 11:32:13 -08:00
});
});
it('should create many addresses on simultaneous requests', function(done) {
2015-02-08 13:35:19 -08:00
var N = 5;
async.map(_.range(N), function(i, cb) {
server.createAddress({}, cb);
2015-02-06 15:59:59 -08:00
}, function(err, addresses) {
2015-02-08 13:35:19 -08:00
addresses.length.should.equal(N);
_.each(_.range(N), function(i) {
addresses[i].path.should.equal('m/2147483647/0/' + i);
});
2015-02-06 15:59:59 -08:00
// No two identical addresses
2015-02-08 13:35:19 -08:00
_.uniq(_.pluck(addresses, 'address')).length.should.equal(N);
2015-02-06 15:59:59 -08:00
done();
});
});
2015-02-03 12:32:40 -08:00
2015-02-08 15:46:02 -08:00
it('should not create address if unable to store it', function(done) {
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
server.createAddress({}, function(err, address) {
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-06 15:59:59 -08:00
should.not.exist(address);
2015-02-03 12:32:40 -08:00
2015-02-22 08:04:23 -08:00
server.getMainAddresses({}, function(err, addresses) {
2015-02-06 15:59:59 -08:00
addresses.length.should.equal(0);
2015-02-08 15:46:02 -08:00
server.storage.storeAddressAndWallet.restore();
server.createAddress({}, function(err, address) {
2015-02-06 15:59:59 -08:00
should.not.exist(err);
address.should.exist;
2015-03-09 14:11:25 -07:00
address.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg');
address.path.should.equal('m/2147483647/0/0');
2015-02-06 15:59:59 -08:00
done();
2015-02-03 12:32:40 -08:00
});
});
});
});
2015-01-27 05:18:45 -08:00
});
2015-01-27 11:40:21 -08:00
describe('#getBalance', function() {
2015-03-06 08:07:44 -08:00
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should get balance', function(done) {
2015-03-06 09:58:22 -08:00
helpers.stubUtxos(server, wallet, [1, 2, 3], function() {
2015-03-06 08:07:44 -08:00
server.getBalance({}, function(err, balance) {
should.not.exist(err);
should.exist(balance);
2015-03-06 09:58:22 -08:00
balance.totalAmount.should.equal(helpers.toSatoshi(6));
2015-03-06 08:07:44 -08:00
balance.lockedAmount.should.equal(0);
should.exist(balance.byAddress);
2015-03-06 09:58:22 -08:00
balance.byAddress.length.should.equal(2);
balance.byAddress[0].amount.should.equal(helpers.toSatoshi(4));
balance.byAddress[1].amount.should.equal(helpers.toSatoshi(2));
server.getMainAddresses({}, function(err, addresses) {
should.not.exist(err);
var addresses = _.uniq(_.pluck(addresses, 'address'));
_.intersection(addresses, _.pluck(balance.byAddress, 'address')).length.should.equal(2);
done();
});
2015-03-06 08:07:44 -08:00
});
});
});
it('should get balance when there are no addresses', function(done) {
server.getBalance({}, function(err, balance) {
should.not.exist(err);
should.exist(balance);
balance.totalAmount.should.equal(0);
balance.lockedAmount.should.equal(0);
should.exist(balance.byAddress);
balance.byAddress.length.should.equal(0);
done();
});
});
it('should get balance when there are no funds', function(done) {
2015-03-30 16:16:51 -07:00
blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, []);
2015-03-06 08:07:44 -08:00
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
should.exist(balance);
balance.totalAmount.should.equal(0);
balance.lockedAmount.should.equal(0);
should.exist(balance.byAddress);
2015-03-06 09:58:22 -08:00
balance.byAddress.length.should.equal(0);
2015-03-06 08:07:44 -08:00
done();
});
});
});
it('should only include addresses with balance', function(done) {
helpers.stubUtxos(server, wallet, 1, function(utxos) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.byAddress.length.should.equal(1);
balance.byAddress[0].amount.should.equal(helpers.toSatoshi(1));
balance.byAddress[0].address.should.equal(utxos[0].address);
done();
});
});
});
});
2015-03-06 08:07:44 -08:00
});
2015-01-27 11:40:21 -08:00
2015-02-17 12:36:45 -08:00
describe('Wallet not complete tests', function() {
it('should fail to create address when wallet is not complete', function(done) {
2015-02-20 12:32:19 -08:00
var server = new WalletService();
2015-02-17 12:36:45 -08:00
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
2015-02-17 12:36:45 -08:00
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-17 12:36:45 -08:00
server.joinWallet(copayerOpts, function(err, result) {
2015-01-28 12:40:37 -08:00
should.not.exist(err);
2015-02-17 12:36:45 -08:00
helpers.getAuthServer(result.copayerId, function(server) {
server.createAddress({}, function(err, address) {
should.not.exist(address);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-17 12:36:45 -08:00
err.message.should.contain('not complete');
2015-02-03 11:46:28 -08:00
done();
});
2015-01-30 13:29:46 -08:00
});
2015-01-28 12:40:37 -08:00
});
2015-01-27 11:40:21 -08:00
});
});
2015-01-30 12:37:30 -08:00
it('should fail to create tx when wallet is not complete', function(done) {
2015-02-20 12:32:19 -08:00
var server = new WalletService();
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
2015-02-10 08:20:41 -08:00
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, walletId) {
should.not.exist(err);
2015-03-10 07:23:23 -07:00
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
2015-03-09 14:11:25 -07:00
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
2015-03-10 07:23:23 -07:00
});
2015-02-12 19:00:54 -08:00
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
2015-02-12 19:00:54 -08:00
helpers.getAuthServer(result.copayerId, function(server, wallet) {
2015-02-12 19:16:35 -08:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
2015-02-24 05:36:14 -08:00
should.exist(err);
err.message.should.contain('not complete');
done();
});
});
});
});
});
2015-02-17 12:36:45 -08:00
});
describe('#createTx', function() {
var server, wallet;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
done();
2015-02-17 12:36:45 -08:00
});
});
it('should create a tx', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-17 12:36:45 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
2015-02-21 18:42:14 -08:00
should.exist(tx);
2015-03-30 07:29:19 -07:00
tx.walletId.should.equal(wallet.id);
tx.creatorId.should.equal(wallet.copayers[0].id);
2015-02-17 12:36:45 -08:00
tx.message.should.equal('some message');
tx.isAccepted().should.equal.false;
tx.isRejected().should.equal.false;
2015-03-12 11:21:24 -07:00
tx.amount.should.equal(helpers.toSatoshi(80));
tx.fee.should.equal(Bitcore.Transaction.FEE_PER_KB);
2015-02-17 12:36:45 -08:00
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.length.should.equal(1);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.totalAmount.should.equal(helpers.toSatoshi(300));
balance.lockedAmount.should.equal(helpers.toSatoshi(100));
2015-02-22 12:44:37 -08:00
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
2015-02-21 14:13:15 -08:00
should.not.exist(err);
var change = _.filter(addresses, {
isChange: true
});
change.length.should.equal(1);
});
2015-02-17 12:36:45 -08:00
done();
});
});
});
});
});
2015-03-09 11:40:10 -07:00
it('should create a tx using the uxtos with minimum amount first', function(done) {
helpers.stubUtxos(server, wallet, [100, 200, 300], function() {
2015-03-10 15:03:33 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 150, 'some message', TestData.copayers[0].privKey_1H_0);
2015-03-09 11:40:10 -07:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.length.should.equal(1);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.totalAmount.should.equal(helpers.toSatoshi(600));
balance.lockedAmount.should.equal(helpers.toSatoshi(300));
done();
});
});
});
});
});
2015-02-07 06:15:54 -08:00
2015-02-13 11:57:28 -08:00
it('should fail to create tx with invalid proposal signature', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-02-13 11:57:28 -08:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, 'dummy');
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-13 11:57:28 -08:00
err.message.should.equal('Invalid proposal signature');
done();
});
});
});
it('should fail to create tx with proposal signed by another copayer', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[1].privKey_1H_0);
2015-02-13 11:57:28 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-13 11:57:28 -08:00
err.message.should.equal('Invalid proposal signature');
done();
});
});
});
2015-02-17 06:52:29 -08:00
it('should fail to create tx for invalid address', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0);
2015-02-08 14:10:06 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
2015-02-24 05:36:14 -08:00
should.exist(err);
2015-02-08 14:10:06 -08:00
err.code.should.equal('INVALIDADDRESS');
err.message.should.equal('Invalid address');
done();
});
});
});
it('should fail to create tx for address of different network', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, null, TestData.copayers[0].privKey_1H_0);
2015-02-08 14:10:06 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
2015-02-16 09:27:01 -08:00
should.exist(err);
2015-02-08 14:10:06 -08:00
err.code.should.equal('INVALIDADDRESS');
err.message.should.equal('Incorrect address network');
done();
});
});
});
2015-02-08 08:16:41 -08:00
2015-02-24 05:36:14 -08:00
it('should fail to create tx for invalid amount', function(done) {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0, null, TestData.copayers[0].privKey_1H_0);
2015-02-24 05:36:14 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(tx);
should.exist(err);
err.message.should.equal('Invalid amount');
done();
});
});
2015-02-23 13:31:27 -08:00
2015-02-03 18:17:06 -08:00
it('should fail to create tx when insufficient funds', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, TestData.copayers[0].privKey_1H_0);
2015-02-03 18:17:06 -08:00
server.createTx(txOpts, function(err, tx) {
2015-02-16 09:27:01 -08:00
should.exist(err);
err.code.should.equal('INSUFFICIENTFUNDS');
err.message.should.equal('Insufficient funds');
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
txs.length.should.equal(0);
2015-02-06 12:56:51 -08:00
server.getBalance({}, function(err, balance) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
balance.lockedAmount.should.equal(0);
2015-02-04 11:18:36 -08:00
balance.totalAmount.should.equal(10000000000);
2015-02-03 18:17:06 -08:00
done();
});
});
});
});
});
2015-02-16 09:27:01 -08:00
it('should fail to create tx when insufficient funds for fee', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [100], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 100, null, TestData.copayers[0].privKey_1H_0);
2015-02-16 09:27:01 -08:00
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.code.should.equal('INSUFFICIENTFUNDS');
2015-03-14 05:51:45 -07:00
err.message.should.equal('Insufficient funds for fee');
2015-02-16 09:27:01 -08:00
done();
});
});
});
2015-02-08 08:16:41 -08:00
2015-02-16 09:41:12 -08:00
it('should fail to create tx for dust amount', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [1], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0);
2015-02-16 09:41:12 -08:00
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.code.should.equal('DUSTAMOUNT');
err.message.should.equal('Amount below dust threshold');
done();
});
});
});
2015-02-08 08:16:41 -08:00
2015-03-25 08:17:41 -07:00
it('should fail to create tx that would return change for dust amount', function(done) {
helpers.stubUtxos(server, wallet, [1], function() {
var fee = Bitcore.Transaction.FEE_PER_KB / 1e8;
var change = 0.00000001;
var amount = 1 - fee - change;
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.code.should.equal('DUSTAMOUNT');
err.message.should.equal('Amount below dust threshold');
done();
});
});
});
2015-03-11 11:04:42 -07:00
it('should fail with different error for insufficient funds and locked funds', function(done) {
2015-03-11 10:29:13 -07:00
helpers.stubUtxos(server, wallet, [10, 10], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 11, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.totalAmount.should.equal(helpers.toSatoshi(20));
balance.lockedAmount.should.equal(helpers.toSatoshi(20));
txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.code.should.equal('LOCKEDFUNDS');
err.message.should.equal('Funds are locked by pending transaction proposals');
done();
});
});
});
});
});
2015-03-25 08:17:41 -07:00
it('should create tx with 0 change output', function(done) {
helpers.stubUtxos(server, wallet, [1], function() {
var fee = Bitcore.Transaction.FEE_PER_KB / 1e8;
var amount = 1 - fee;
2015-03-25 08:17:41 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
2015-03-25 08:17:41 -07:00
should.not.exist(err);
should.exist(tx);
var bitcoreTx = tx.getBitcoreTx();
bitcoreTx.outputs.length.should.equal(1);
bitcoreTx.outputs[0].satoshis.should.equal(tx.amount);
done();
});
});
});
2015-02-24 05:36:14 -08:00
it('should fail gracefully when bitcore throws exception on raw tx creation', function(done) {
helpers.stubUtxos(server, wallet, [10], function() {
var bitcoreStub = sinon.stub(Bitcore, 'Transaction');
bitcoreStub.throws({
name: 'dummy',
message: 'dummy exception'
});
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2, null, TestData.copayers[0].privKey_1H_0);
2015-02-24 05:36:14 -08:00
server.createTx(txOpts, function(err, tx) {
should.exist(err);
err.message.should.equal('dummy exception');
bitcoreStub.restore();
done();
});
});
});
2015-02-23 13:31:27 -08:00
2015-02-03 18:17:06 -08:00
it('should create tx when there is a pending tx and enough UTXOs', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0);
2015-02-03 18:17:06 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
2015-02-21 18:42:14 -08:00
should.exist(tx);
2015-03-09 14:11:25 -07:00
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0);
2015-02-03 18:17:06 -08:00
server.createTx(txOpts2, function(err, tx) {
should.not.exist(err);
2015-02-21 18:42:14 -08:00
should.exist(tx);
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
txs.length.should.equal(2);
2015-02-06 12:56:51 -08:00
server.getBalance({}, function(err, balance) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
2015-02-04 11:18:36 -08:00
balance.totalAmount.should.equal(3060000000);
balance.lockedAmount.should.equal(3060000000);
2015-02-03 18:17:06 -08:00
done();
});
});
});
});
});
});
it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) {
2015-02-20 12:23:42 -08:00
helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0);
2015-02-03 18:17:06 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
2015-02-21 18:42:14 -08:00
should.exist(tx);
2015-03-09 14:11:25 -07:00
var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey_1H_0);
2015-02-03 18:17:06 -08:00
server.createTx(txOpts2, function(err, tx) {
err.code.should.equal('LOCKEDFUNDS');
2015-02-03 18:17:06 -08:00
should.not.exist(tx);
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
txs.length.should.equal(1);
2015-02-06 12:56:51 -08:00
server.getBalance({}, function(err, balance) {
2015-02-03 18:17:06 -08:00
should.not.exist(err);
2015-02-04 11:18:36 -08:00
balance.totalAmount.should.equal(helpers.toSatoshi(30.6));
balance.lockedAmount.should.equal(helpers.toSatoshi(20.3));
2015-02-03 18:17:06 -08:00
done();
});
});
});
});
});
});
2015-02-08 13:29:58 -08:00
it('should create tx using different UTXOs for simultaneous requests', function(done) {
2015-02-08 13:35:19 -08:00
var N = 5;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(100, 100 + N, 0), function(utxos) {
2015-02-08 13:29:58 -08:00
server.getBalance({}, function(err, balance) {
should.not.exist(err);
2015-02-08 13:35:19 -08:00
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
2015-02-08 13:29:58 -08:00
balance.lockedAmount.should.equal(helpers.toSatoshi(0));
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey_1H_0);
2015-02-08 13:35:19 -08:00
async.map(_.range(N), function(i, cb) {
2015-02-08 13:29:58 -08:00
server.createTx(txOpts, function(err, tx) {
cb(err, tx);
});
}, function(err) {
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
2015-02-08 13:35:19 -08:00
txs.length.should.equal(N);
_.uniq(_.pluck(txs, 'changeAddress')).length.should.equal(N);
2015-02-08 13:29:58 -08:00
server.getBalance({}, function(err, balance) {
should.not.exist(err);
2015-02-08 13:35:19 -08:00
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
2015-02-08 13:29:58 -08:00
balance.lockedAmount.should.equal(balance.totalAmount);
done();
});
});
});
});
});
});
2015-01-27 11:40:21 -08:00
});
2015-02-04 06:43:12 -08:00
2015-02-10 11:55:00 -08:00
describe('#rejectTx', function() {
2015-02-12 19:16:35 -08:00
var server, wallet, txid;
2015-02-10 11:55:00 -08:00
beforeEach(function(done) {
2015-02-24 05:36:14 -08:00
helpers.createAndJoinWallet(2, 2, function(s, w) {
2015-02-10 11:55:00 -08:00
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0);
2015-02-21 18:42:14 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
2015-02-10 11:55:00 -08:00
});
});
});
});
it('should reject a TX', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
server.rejectTx({
txProposalId: txid,
2015-02-15 10:46:29 -08:00
reason: 'some reason',
2015-02-10 11:55:00 -08:00
}, function(err) {
should.not.exist(err);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
2015-02-24 05:36:14 -08:00
txs.should.be.empty;
server.getTx({
2015-02-26 05:41:55 -08:00
txProposalId: txid
2015-02-24 05:36:14 -08:00
}, function(err, tx) {
var actors = tx.getActors();
actors.length.should.equal(1);
actors[0].should.equal(wallet.copayers[0].id);
var action = tx.getActionBy(wallet.copayers[0].id);
action.type.should.equal('reject');
action.comment.should.equal('some reason');
done();
});
2015-02-10 11:55:00 -08:00
});
});
});
});
2015-02-23 13:31:27 -08:00
2015-02-24 05:36:14 -08:00
it('should fail to reject non-pending TX', function(done) {
async.waterfall([
function(next) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
next();
});
},
function(next) {
server.rejectTx({
txProposalId: txid,
reason: 'some reason',
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.should.be.empty;
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.rejectTx({
txProposalId: txid,
reason: 'some other reason',
}, function(err) {
should.exist(err);
err.code.should.equal('TXNOTPENDING');
done();
});
});
},
]);
});
2015-02-10 11:55:00 -08:00
});
2015-02-04 11:18:36 -08:00
describe('#signTx', function() {
2015-02-12 19:16:35 -08:00
var server, wallet, txid;
2015-02-04 06:43:12 -08:00
beforeEach(function(done) {
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(2, 3, function(s, w) {
2015-02-06 12:56:51 -08:00
server = s;
2015-02-04 06:43:12 -08:00
wallet = w;
2015-02-21 17:35:12 -08:00
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0);
2015-02-21 17:35:12 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
2015-02-04 06:43:12 -08:00
});
});
});
});
2015-02-06 23:09:45 -08:00
it('should sign a TX with multiple inputs, different paths', function(done) {
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-04 06:43:12 -08:00
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-04 11:18:36 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
2015-02-05 10:50:18 -08:00
should.not.exist(err);
2015-02-10 11:55:00 -08:00
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
var tx = txs[0];
tx.id.should.equal(txid);
var actors = tx.getActors();
actors.length.should.equal(1);
2015-02-12 19:16:35 -08:00
actors[0].should.equal(wallet.copayers[0].id);
tx.getActionBy(wallet.copayers[0].id).type.should.equal('accept');
2015-02-10 11:55:00 -08:00
done();
});
2015-02-04 11:18:36 -08:00
});
2015-02-05 10:50:18 -08:00
});
});
2015-02-04 06:43:12 -08:00
2015-02-10 13:04:50 -08:00
it('should fail to sign with a xpriv from other copayer', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[1].xPrivKey);
2015-02-10 13:04:50 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.code.should.contain('BADSIG');
done();
});
});
});
2015-02-06 23:09:45 -08:00
it('should fail if one signature is broken', function(done) {
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-05 10:50:18 -08:00
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-06 10:15:54 -08:00
signatures[0] = 1;
2015-02-05 10:50:18 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.message.should.contain('signatures');
done();
});
2015-02-04 06:43:12 -08:00
});
2015-02-05 10:50:18 -08:00
});
2015-02-10 13:04:50 -08:00
2015-02-07 08:13:29 -08:00
it('should fail on invalid signature', function(done) {
2015-02-06 12:56:51 -08:00
server.getPendingTxs({}, function(err, txs) {
2015-02-05 10:50:18 -08:00
var tx = txs[0];
tx.id.should.equal(txid);
2015-02-04 06:43:12 -08:00
2015-02-16 09:27:01 -08:00
var signatures = ['11', '22', '33', '44', '55'];
2015-02-05 10:50:18 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
2015-02-16 09:27:01 -08:00
should.exist(err);
err.message.should.contain('Bad signatures');
done();
});
});
});
it('should fail on wrong number of invalid signatures', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = _.take(helpers.clientSign(tx, TestData.copayers[0].xPrivKey), 2);
2015-02-16 09:27:01 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.message.should.contain('Bad signatures');
2015-02-05 10:50:18 -08:00
done();
});
});
2015-02-04 06:43:12 -08:00
});
2015-02-10 11:55:00 -08:00
it('should fail when signing a TX previously rejected', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-10 11:55:00 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
server.rejectTx({
txProposalId: txid,
}, function(err) {
err.code.should.contain('CVOTED');
done();
});
});
});
});
it('should fail when rejected a previously signed TX', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
server.rejectTx({
txProposalId: txid,
}, function(err) {
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-10 11:55:00 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.code.should.contain('CVOTED');
done();
});
});
});
});
2015-02-24 05:36:14 -08:00
it('should fail to sign a non-pending TX', function(done) {
async.waterfall([
function(next) {
server.rejectTx({
txProposalId: txid,
reason: 'some reason',
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.rejectTx({
txProposalId: txid,
reason: 'some reason',
}, function(err) {
should.not.exist(err);
next();
});
});
},
function(next) {
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.should.be.empty;
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[2].id, function(server) {
server.getTx({
2015-02-26 05:41:55 -08:00
txProposalId: txid
2015-02-24 05:36:14 -08:00
}, function(err, tx) {
should.not.exist(err);
var signatures = helpers.clientSign(tx, TestData.copayers[2].xPrivKey);
2015-02-24 05:36:14 -08:00
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.code.should.equal('TXNOTPENDING');
done();
});
});
});
},
]);
});
2015-02-06 10:15:54 -08:00
});
2015-02-07 06:15:54 -08:00
2015-02-24 07:27:44 -08:00
describe('#broadcastTx', function() {
var server, wallet, txpid;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, [10, 10], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-24 07:27:44 -08:00
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
2015-02-24 07:27:44 -08:00
server.signTx({
txProposalId: txp.id,
signatures: signatures,
}, function(err, txp) {
2015-02-26 05:37:21 -08:00
should.not.exist(err);
2015-02-24 07:27:44 -08:00
should.exist(txp);
txp.isAccepted().should.be.true;
2015-02-26 05:46:53 -08:00
txp.isBroadcasted().should.be.false;
2015-02-24 07:27:44 -08:00
txpid = txp.id;
done();
});
});
});
});
});
it('should brodcast a tx', function(done) {
var clock = sinon.useFakeTimers(1234000);
helpers.stubBroadcast('999');
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.not.exist(err);
server.getTx({
2015-02-26 05:41:55 -08:00
txProposalId: txpid
2015-02-24 07:27:44 -08:00
}, function(err, txp) {
should.not.exist(err);
txp.txid.should.equal('999');
txp.isBroadcasted().should.be.true;
txp.broadcastedOn.should.equal(1234);
clock.restore();
done();
});
});
});
it('should fail to brodcast an already broadcasted tx', function(done) {
helpers.stubBroadcast('999');
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.not.exist(err);
server.broadcastTx({
txProposalId: txpid
2015-02-26 05:37:21 -08:00
}, function(err) {
2015-02-24 07:27:44 -08:00
should.exist(err);
err.code.should.equal('TXALREADYBROADCASTED');
done();
});
});
});
it('should fail to brodcast a not yet accepted tx', function(done) {
helpers.stubBroadcast('999');
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some other message', TestData.copayers[0].privKey_1H_0);
2015-02-24 07:27:44 -08:00
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
server.broadcastTx({
txProposalId: txp.id
2015-02-26 05:37:21 -08:00
}, function(err) {
2015-02-24 07:27:44 -08:00
should.exist(err);
err.code.should.equal('TXNOTACCEPTED');
done();
});
});
});
2015-02-26 05:37:21 -08:00
it('should keep tx as accepted if unable to broadcast it', function(done) {
helpers.stubBroadcastFail();
server.broadcastTx({
txProposalId: txpid
}, function(err) {
should.exist(err);
server.getTx({
2015-02-26 05:41:55 -08:00
txProposalId: txpid
2015-02-26 05:37:21 -08:00
}, function(err, txp) {
should.not.exist(err);
should.not.exist(txp.txid);
txp.isBroadcasted().should.be.false;
should.not.exist(txp.broadcastedOn);
txp.isAccepted().should.be.true;
done();
});
});
});
2015-02-23 13:31:27 -08:00
});
2015-02-08 08:16:41 -08:00
describe('Tx proposal workflow', function() {
2015-02-20 12:23:42 -08:00
var server, wallet;
beforeEach(function(done) {
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
helpers.stubBroadcast('999');
done();
});
});
});
it('other copayers should see pending proposal created by one copayer', function(done) {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
2015-02-24 07:27:44 -08:00
should.exist(txp);
helpers.getAuthServer(wallet.copayers[1].id, function(server2, wallet) {
server2.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
txps[0].id.should.equal(txp.id);
2015-02-08 13:29:58 -08:00
txps[0].message.should.equal('some message');
done();
});
});
});
});
2015-02-07 06:15:54 -08:00
2015-02-25 11:00:41 -08:00
it('tx proposals should not be finally accepted until quorum is reached', function(done) {
2015-02-15 11:15:45 -08:00
var txpId;
async.waterfall([
2015-02-15 11:50:50 -08:00
function(next) {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-15 11:50:50 -08:00
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
2015-02-24 07:27:44 -08:00
should.exist(txp);
2015-02-15 11:50:50 -08:00
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
2015-02-20 07:33:46 -08:00
txp.actions.should.be.empty;
2015-02-15 11:50:50 -08:00
next(null, txp);
});
},
function(txp, next) {
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
2015-02-15 11:50:50 -08:00
server.signTx({
txProposalId: txpId,
signatures: signatures,
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
txp.isPending().should.be.true;
txp.isAccepted().should.be.false;
2015-02-25 11:00:41 -08:00
txp.isRejected().should.be.false;
txp.isBroadcasted().should.be.false;
2015-02-20 07:33:46 -08:00
txp.actions.length.should.equal(1);
var action = txp.getActionBy(wallet.copayers[0].id);
2015-02-15 11:50:50 -08:00
action.type.should.equal('accept');
2015-02-25 11:00:41 -08:00
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var last = _.last(notifications);
last.type.should.not.equal('TxProposalFinallyAccepted');
next(null, txp);
});
2015-02-15 11:50:50 -08:00
});
},
function(txp, next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server, wallet) {
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
2015-02-15 11:15:45 -08:00
server.signTx({
txProposalId: txpId,
signatures: signatures,
}, function(err) {
should.not.exist(err);
next();
});
2015-02-15 11:50:50 -08:00
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
2015-02-25 11:00:41 -08:00
txps.length.should.equal(1);
var txp = txps[0];
txp.isPending().should.be.true;
2015-02-15 11:50:50 -08:00
txp.isAccepted().should.be.true;
2015-02-25 11:00:41 -08:00
txp.isBroadcasted().should.be.false;
should.not.exist(txp.txid);
2015-02-20 07:33:46 -08:00
txp.actions.length.should.equal(2);
2015-02-25 11:00:41 -08:00
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var last = _.last(notifications);
last.type.should.equal('TxProposalFinallyAccepted');
2015-03-30 07:24:33 -07:00
last.walletId.should.equal(wallet.id);
2015-03-24 08:46:54 -07:00
last.creatorId.should.equal(wallet.copayers[1].id);
2015-02-25 11:00:41 -08:00
last.data.txProposalId.should.equal(txp.id);
done();
});
2015-02-15 11:50:50 -08:00
});
},
]);
2015-02-15 11:15:45 -08:00
});
2015-02-08 08:16:41 -08:00
2015-02-15 10:46:29 -08:00
it('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {
var txpId;
2015-02-15 11:15:45 -08:00
async.waterfall([
2015-02-07 06:15:54 -08:00
2015-02-15 11:50:50 -08:00
function(next) {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-15 11:50:50 -08:00
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
2015-02-24 07:27:44 -08:00
should.exist(txp);
2015-02-15 11:50:50 -08:00
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
2015-02-20 07:33:46 -08:00
txp.actions.should.be.empty;
2015-02-15 11:50:50 -08:00
next();
});
},
function(next) {
server.rejectTx({
txProposalId: txpId,
reason: 'just because'
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
txp.isPending().should.be.true;
txp.isRejected().should.be.false;
txp.isAccepted().should.be.false;
2015-02-20 07:33:46 -08:00
txp.actions.length.should.equal(1);
var action = txp.getActionBy(wallet.copayers[0].id);
2015-02-15 11:50:50 -08:00
action.type.should.equal('reject');
action.comment.should.equal('just because');
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server, wallet) {
2015-02-15 10:46:29 -08:00
server.rejectTx({
txProposalId: txpId,
2015-02-15 11:50:50 -08:00
reason: 'some other reason'
2015-02-15 10:46:29 -08:00
}, function(err) {
should.not.exist(err);
next();
});
2015-02-15 11:50:50 -08:00
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(0);
next();
});
},
function(next) {
server.getTx({
2015-02-26 05:41:55 -08:00
txProposalId: txpId
2015-02-15 11:50:50 -08:00
}, function(err, txp) {
should.not.exist(err);
txp.isPending().should.be.false;
txp.isRejected().should.be.true;
txp.isAccepted().should.be.false;
2015-02-20 07:33:46 -08:00
txp.actions.length.should.equal(2);
2015-02-15 11:50:50 -08:00
done();
});
},
]);
2015-02-15 10:46:29 -08:00
});
2015-02-07 06:15:54 -08:00
});
2015-02-06 23:09:45 -08:00
2015-02-26 07:34:53 -08:00
describe('#getTx', function() {
var server, wallet, txpid;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, 10, function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-26 07:34:53 -08:00
server.createTx(txOpts, function(err, txp) {
should.not.exist(err);
should.exist(txp);
txpid = txp.id;
done();
});
});
});
});
2015-02-26 08:18:26 -08:00
it('should get own transaction proposal', function(done) {
2015-02-26 07:34:53 -08:00
server.getTx({
txProposalId: txpid
}, function(err, txp) {
should.not.exist(err);
should.exist(txp);
txp.id.should.equal(txpid);
done();
});
});
2015-03-09 11:24:37 -07:00
it('should get someone elses transaction proposal', function(done) {
helpers.getAuthServer(wallet.copayers[1].id, function(server2, wallet) {
server2.getTx({
txProposalId: txpid
}, function(err, res) {
should.not.exist(err);
res.id.should.equal(txpid);
done();
});
});
});
2015-02-26 07:34:53 -08:00
it('should fail to get non-existent transaction proposal', function(done) {
server.getTx({
txProposalId: 'dummy'
}, function(err, txp) {
should.exist(err);
should.not.exist(txp);
err.message.should.contain('not found');
done();
});
});
2015-02-26 08:18:26 -08:00
it.skip('should get accepted/rejected transaction proposal', function(done) {});
it.skip('should get broadcasted transaction proposal', function(done) {});
2015-02-26 07:34:53 -08:00
});
2015-02-15 10:46:29 -08:00
2015-02-06 23:09:45 -08:00
describe('#getTxs', function() {
2015-02-12 19:16:35 -08:00
var server, wallet, clock;
2015-02-06 23:09:45 -08:00
beforeEach(function(done) {
2015-02-10 11:15:05 -08:00
if (server) return done();
2015-02-07 09:15:04 -08:00
this.timeout(5000);
2015-02-06 23:09:45 -08:00
console.log('\tCreating TXS...');
clock = sinon.useFakeTimers();
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(1, 1, function(s, w) {
2015-02-06 23:09:45 -08:00
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(10), function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey_1H_0);
2015-02-21 18:42:14 -08:00
async.eachSeries(_.range(10), function(i, next) {
clock.tick(10000);
server.createTx(txOpts, function(err, tx) {
next();
});
2015-02-21 18:42:14 -08:00
}, function(err) {
return done(err);
2015-02-06 23:09:45 -08:00
});
});
});
});
afterEach(function() {
clock.restore();
});
it('should pull 4 txs, down to to time 60', function(done) {
server.getTxs({
minTs: 60,
limit: 8
}, function(err, txps) {
should.not.exist(err);
2015-02-07 08:13:29 -08:00
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60]);
2015-02-06 23:09:45 -08:00
done();
});
});
it('should pull the first 5 txs', function(done) {
server.getTxs({
maxTs: 50,
limit: 5
}, function(err, txps) {
should.not.exist(err);
2015-02-07 08:13:29 -08:00
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([50, 40, 30, 20, 10]);
2015-02-06 23:09:45 -08:00
done();
});
});
it('should pull the last 4 txs', function(done) {
server.getTxs({
limit: 4
}, function(err, txps) {
should.not.exist(err);
2015-02-07 08:13:29 -08:00
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60]);
2015-02-06 23:09:45 -08:00
done();
});
});
it('should pull all txs', function(done) {
2015-02-07 08:13:29 -08:00
server.getTxs({}, function(err, txps) {
2015-02-06 23:09:45 -08:00
should.not.exist(err);
2015-02-07 08:13:29 -08:00
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([90, 80, 70, 60, 50, 40, 30, 20, 10]);
2015-02-06 23:09:45 -08:00
done();
});
});
it('should txs from times 50 to 70', function(done) {
server.getTxs({
minTs: 50,
maxTs: 70,
}, function(err, txps) {
should.not.exist(err);
2015-02-07 08:13:29 -08:00
var times = _.pluck(txps, 'createdOn');
times.should.deep.equal([70, 60, 50]);
2015-02-06 23:09:45 -08:00
done();
});
});
});
2015-02-09 13:07:15 -08:00
2015-02-12 05:26:13 -08:00
describe('Notifications', function() {
2015-02-12 19:16:35 -08:00
var server, wallet;
2015-02-12 05:26:13 -08:00
beforeEach(function(done) {
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(1, 1, function(s, w) {
2015-02-12 05:26:13 -08:00
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey_1H_0);
2015-02-21 18:42:14 -08:00
async.eachSeries(_.range(3), function(i, next) {
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
next();
2015-02-12 05:26:13 -08:00
});
2015-02-21 18:42:14 -08:00
}, function(err) {
return done(err);
2015-02-12 05:26:13 -08:00
});
});
});
});
it('should pull the last 4 notifications after 3 TXs', function(done) {
2015-02-12 05:26:13 -08:00
server.getNotifications({
limit: 4,
2015-02-12 05:26:13 -08:00
reverse: true,
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewTxProposal', 'NewAddress']);
2015-03-30 07:24:33 -07:00
var walletIds = _.uniq(_.pluck(notifications, 'walletId'));
walletIds.length.should.equal(1);
walletIds[0].should.equal(wallet.id);
2015-03-24 08:46:54 -07:00
var creators = _.uniq(_.pluck(notifications, 'creatorId'));
creators.length.should.equal(1);
creators[0].should.equal(wallet.copayers[0].id);
2015-02-12 05:26:13 -08:00
done();
});
});
it('should pull the last 4 notifications, using now', function(done) {
2015-02-12 06:41:14 -08:00
server.getNotifications({
limit: 4,
2015-02-12 06:41:14 -08:00
reverse: true,
2015-02-12 12:15:48 -08:00
maxTs: Date.now() / 1000,
minTs: Date.now() / 1000 - 1000,
2015-02-12 06:41:14 -08:00
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewTxProposal', 'NewAddress']);
2015-02-12 06:41:14 -08:00
done();
});
});
it('should pull all notifications after wallet creation', function(done) {
2015-02-12 05:26:13 -08:00
server.getNotifications({
minTs: 0,
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types[0].should.equal('NewCopayer');
types[types.length - 1].should.equal('NewTxProposal');
2015-02-12 05:26:13 -08:00
done();
});
});
it('should contain walletId & creatorId on NewCopayer', function(done) {
server.getNotifications({
minTs: 0,
}, function(err, notifications) {
should.not.exist(err);
var newCopayer = notifications[0];
newCopayer.type.should.equal('NewCopayer');
newCopayer.walletId.should.equal(wallet.id);
newCopayer.creatorId.should.equal(wallet.copayers[0].id);
done();
});
});
2015-02-12 05:26:13 -08:00
it('should notify sign and acceptance', function(done) {
server.getPendingTxs({}, function(err, txs) {
2015-02-20 12:23:42 -08:00
helpers.stubBroadcastFail();
2015-02-12 05:26:13 -08:00
var tx = txs[0];
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-12 05:26:13 -08:00
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err) {
server.getNotifications({
limit: 3,
reverse: true,
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['TxProposalFinallyAccepted', 'TxProposalAcceptedBy', 'NewTxProposal']);
done();
});
});
});
});
it('should notify rejection', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[1];
server.rejectTx({
txProposalId: tx.id,
}, function(err) {
should.not.exist(err);
server.getNotifications({
limit: 2,
reverse: true,
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['TxProposalFinallyRejected', 'TxProposalRejectedBy']);
done();
});
});
});
});
2015-02-12 05:57:10 -08:00
it('should notify sign, acceptance, and broadcast, and emit', function(done) {
2015-02-12 05:26:13 -08:00
server.getPendingTxs({}, function(err, txs) {
var tx = txs[2];
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-03-23 08:50:00 -07:00
sinon.spy(server, '_emit');
2015-02-12 05:26:13 -08:00
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err) {
2015-02-25 11:00:41 -08:00
should.not.exist(err);
helpers.stubBroadcast('1122334455');
server.broadcastTx({
txProposalId: tx.id
2015-02-25 13:16:09 -08:00
}, function(err, txp) {
2015-02-12 05:26:13 -08:00
should.not.exist(err);
2015-02-25 11:00:41 -08:00
server.getNotifications({
limit: 3,
reverse: true,
}, function(err, notifications) {
should.not.exist(err);
var types = _.pluck(notifications, 'type');
types.should.deep.equal(['NewOutgoingTx', 'TxProposalFinallyAccepted', 'TxProposalAcceptedBy']);
// Check also events
2015-03-24 08:46:54 -07:00
server._emit.getCall(0).args[1].type.should.equal('TxProposalAcceptedBy');
server._emit.getCall(1).args[1].type.should.equal('TxProposalFinallyAccepted');;
server._emit.getCall(2).args[1].type.should.equal('NewOutgoingTx');
2015-02-12 05:57:10 -08:00
2015-02-25 11:00:41 -08:00
done();
});
2015-02-12 05:26:13 -08:00
});
});
});
});
});
2015-02-09 13:07:15 -08:00
describe('#removeWallet', function() {
var server, wallet, clock;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, done);
2015-02-09 13:07:15 -08:00
});
});
});
it('should delete a wallet', function(done) {
var i = 0;
var count = function() {
return ++i;
};
server.storage._dump(function() {
i.should.above(1);
server.removeWallet({}, function(err) {
i = 0;
server.storage._dump(function() {
2015-02-11 18:11:30 -08:00
server.storage._dump();
2015-02-09 13:07:15 -08:00
i.should.equal(0);
done();
}, count);
});
}, count);
});
// creates 2 wallet, and deletes only 1.
it('should delete a wallet, and only that wallet', function(done) {
var i = 0;
var db = [];
var cat = function(data) {
db.push(data);
};
server.storage._dump(function() {
var before = _.clone(db);
db.length.should.above(1);
2015-02-17 12:36:45 -08:00
helpers.offset = 1;
2015-02-09 13:07:15 -08:00
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.stubUtxos(server, wallet, _.range(2), function() {
var txOpts = {
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
amount: helpers.toSatoshi(0.1),
};
async.eachSeries(_.range(2), function(i, next) {
server.createTx(txOpts, function(err, tx) {
next();
});
}, function() {
server.removeWallet({}, function(err) {
db = [];
server.storage._dump(function() {
var after = _.clone(db);
after.should.deep.equal(before);
done();
}, cat);
});
}, cat);
2015-02-09 13:07:15 -08:00
});
});
}, cat);
});
});
2015-02-10 13:04:50 -08:00
describe('#removePendingTx', function() {
2015-02-12 19:16:35 -08:00
var server, wallet, txp;
2015-02-10 13:04:50 -08:00
beforeEach(function(done) {
2015-02-12 19:16:35 -08:00
helpers.createAndJoinWallet(2, 3, function(s, w) {
2015-02-10 13:04:50 -08:00
server = s;
wallet = w;
2015-02-21 17:35:12 -08:00
helpers.stubUtxos(server, wallet, [100, 200], function() {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-21 17:35:12 -08:00
server.createTx(txOpts, function(err, tx) {
server.getPendingTxs({}, function(err, txs) {
txp = txs[0];
done();
2015-02-10 13:04:50 -08:00
});
});
});
});
});
it('should allow creator to remove an unsigned TX', function(done) {
server.removePendingTx({
2015-02-15 08:08:34 -08:00
txProposalId: txp.id
2015-02-10 13:04:50 -08:00
}, function(err) {
should.not.exist(err);
server.getPendingTxs({}, function(err, txs) {
txs.length.should.equal(0);
done();
});
});
});
it('should allow creator to remove an signed TX by himself', function(done) {
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
2015-02-10 13:04:50 -08:00
server.signTx({
2015-02-24 05:36:14 -08:00
txProposalId: txp.id,
2015-02-10 13:04:50 -08:00
signatures: signatures,
}, function(err) {
2015-02-24 05:36:14 -08:00
should.not.exist(err);
2015-02-10 13:04:50 -08:00
server.removePendingTx({
2015-02-15 08:08:34 -08:00
txProposalId: txp.id
2015-02-10 13:04:50 -08:00
}, function(err) {
should.not.exist(err);
server.getPendingTxs({}, function(err, txs) {
txs.length.should.equal(0);
done();
});
});
});
});
2015-02-24 05:36:14 -08:00
it('should fail to remove non-pending TX', function(done) {
async.waterfall([
function(next) {
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
2015-02-24 05:36:14 -08:00
server.signTx({
txProposalId: txp.id,
signatures: signatures,
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, function(err) {
should.not.exist(err);
next();
});
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[2].id, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, function(err) {
should.not.exist(err);
next();
});
});
},
function(next) {
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.should.be.empty;
next();
});
},
function(next) {
server.removePendingTx({
txProposalId: txp.id
}, function(err) {
should.exist(err);
err.code.should.equal('TXNOTPENDING');
done();
});
},
]);
});
2015-02-23 13:31:27 -08:00
2015-02-10 13:04:50 -08:00
it('should not allow non-creator copayer to remove an unsigned TX ', function(done) {
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
server2.removePendingTx({
2015-02-15 08:08:34 -08:00
txProposalId: txp.id
2015-02-10 13:04:50 -08:00
}, function(err) {
2015-02-11 07:05:21 -08:00
err.message.should.contain('creators');
2015-02-10 13:04:50 -08:00
server2.getPendingTxs({}, function(err, txs) {
txs.length.should.equal(1);
done();
});
});
});
});
it('should not allow creator copayer to remove an TX signed by other copayer', function(done) {
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
2015-02-10 13:04:50 -08:00
server2.signTx({
txProposalId: txp.id,
signatures: signatures,
}, function(err) {
should.not.exist(err);
server.removePendingTx({
2015-02-15 08:08:34 -08:00
txProposalId: txp.id
2015-02-10 13:04:50 -08:00
}, function(err) {
2015-02-23 13:31:27 -08:00
err.code.should.equal('TXACTIONED');
2015-02-11 07:05:21 -08:00
err.message.should.contain('other copayers');
2015-02-10 13:04:50 -08:00
done();
});
});
});
});
});
2015-02-21 17:35:12 -08:00
describe('#getTxHistory', function() {
var server, wallet, mainAddresses, changeAddresses;
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
2015-02-21 18:42:14 -08:00
helpers.createAddresses(server, wallet, 1, 1, function(main, change) {
2015-02-21 17:35:12 -08:00
mainAddresses = main;
changeAddresses = change;
done();
});
});
});
it('should get tx history from insight', function(done) {
helpers.stubHistory(TestData.history);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
done();
});
});
it('should get tx history for incoming txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
2015-03-17 06:59:00 -07:00
time: 1,
2015-02-21 17:35:12 -08:00
inputs: [{
address: 'external',
amount: 500,
}],
outputs: [{
address: mainAddresses[0].address,
amount: 200,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('received');
tx.amount.should.equal(200);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
2015-03-17 06:59:00 -07:00
time: 1,
2015-02-21 17:35:12 -08:00
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 400,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(400);
tx.fees.should.equal(100);
done();
});
});
it('should get tx history for outgoing txs + change', function(done) {
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var txs = [{
txid: '1',
confirmations: 1,
fees: 100,
2015-03-17 06:59:00 -07:00
time: 1,
2015-02-21 17:35:12 -08:00
inputs: [{
address: mainAddresses[0].address,
amount: 500,
}],
outputs: [{
address: 'external',
amount: 300,
}, {
address: changeAddresses[0].address,
amount: 100,
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(300);
tx.fees.should.equal(100);
done();
});
});
2015-02-21 18:42:14 -08:00
it('should get tx history with accepted proposal', function(done) {
2015-02-21 17:35:12 -08:00
server._normalizeTxHistory = sinon.stub().returnsArg(0);
helpers.stubUtxos(server, wallet, [100, 200], function(utxos) {
2015-03-09 14:11:25 -07:00
var txOpts = helpers.createProposalOpts(mainAddresses[0].address, 80, 'some message', TestData.copayers[0].privKey_1H_0);
2015-02-21 17:35:12 -08:00
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
2015-02-21 18:42:14 -08:00
should.exist(tx);
2015-02-21 17:35:12 -08:00
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
2015-02-21 17:35:12 -08:00
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err, tx) {
should.not.exist(err);
2015-02-25 11:00:41 -08:00
helpers.stubBroadcast('1122334455');
server.broadcastTx({
txProposalId: tx.id
2015-02-25 13:16:09 -08:00
}, function(err, txp) {
2015-02-21 17:35:12 -08:00
should.not.exist(err);
2015-02-25 11:00:41 -08:00
var txs = [{
txid: '1122334455',
confirmations: 1,
fees: 5460,
2015-03-17 06:59:00 -07:00
time: 1,
2015-02-25 11:00:41 -08:00
inputs: [{
address: tx.inputs[0].address,
amount: utxos[0].satoshis,
}],
outputs: [{
address: changeAddresses[0].address,
amount: helpers.toSatoshi(20) - 5460,
}, {
address: 'external',
amount: helpers.toSatoshi(80) - 5460,
2015-02-25 11:00:41 -08:00
}],
}];
helpers.stubHistory(txs);
server.getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(1);
var tx = txs[0];
tx.action.should.equal('sent');
tx.amount.should.equal(helpers.toSatoshi(80));
tx.message.should.equal('some message');
tx.addressTo.should.equal('external');
2015-02-25 11:00:41 -08:00
tx.actions.length.should.equal(1);
tx.actions[0].type.should.equal('accept');
tx.actions[0].copayerName.should.equal('copayer 1');
done();
});
2015-02-21 17:35:12 -08:00
});
});
});
});
});
it('should get various paginated tx history', function(done) {
var testCases = [{
2015-03-20 12:46:33 -07:00
opts: {},
expected: [50, 40, 30, 20, 10],
}, {
opts: {
2015-03-20 12:46:33 -07:00
skip: 1,
limit: 3,
},
2015-03-18 07:56:49 -07:00
expected: [40, 30, 20],
}, {
opts: {
2015-03-20 12:46:33 -07:00
skip: 1,
2015-03-17 06:59:00 -07:00
limit: 2,
},
2015-03-18 07:56:49 -07:00
expected: [40, 30],
}, {
opts: {
2015-03-20 12:46:33 -07:00
skip: 2,
},
2015-03-18 07:56:49 -07:00
expected: [30, 20, 10],
}, {
opts: {
2015-03-20 12:46:33 -07:00
limit: 4,
},
2015-03-18 07:56:49 -07:00
expected: [50, 40, 30, 20],
}, {
opts: {
2015-03-20 12:46:33 -07:00
skip: 0,
limit: 3,
},
2015-03-18 07:56:49 -07:00
expected: [50, 40, 30],
2015-03-20 12:46:33 -07:00
}, {
opts: {
skip: 0,
limit: 0,
},
expected: [],
}, {
opts: {
skip: 4,
limit: 20,
},
expected: [10],
}, {
opts: {
skip: 20,
limit: 1,
},
expected: [],
}];
server._normalizeTxHistory = sinon.stub().returnsArg(0);
var timestamps = [10, 50, 30, 40, 20];
var txs = _.map(timestamps, function(ts, idx) {
return {
txid: (idx + 1).toString(),
confirmations: ts / 10,
fees: 100,
time: ts,
inputs: [{
address: 'external',
amount: 500,
}],
outputs: [{
address: mainAddresses[0].address,
amount: 200,
}],
};
});
helpers.stubHistory(txs);
async.each(testCases, function(testCase, next) {
server.getTxHistory(testCase.opts, function(err, txs) {
2015-03-17 06:59:00 -07:00
should.not.exist(err);
should.exist(txs);
_.pluck(txs, 'time').should.deep.equal(testCase.expected);
next();
2015-03-17 06:59:00 -07:00
});
}, done);
2015-03-17 06:59:00 -07:00
});
2015-02-21 17:35:12 -08:00
});
2015-04-01 12:42:12 -07:00
describe('#scan', function() {
2015-04-02 07:18:39 -07:00
var server, wallet;
2015-04-01 13:21:06 -07:00
var scanConfigOld = WalletService.scanConfig;
2015-04-02 07:18:39 -07:00
beforeEach(function(done) {
2015-04-01 14:25:18 -07:00
this.timeout(5000);
2015-04-01 13:11:39 -07:00
WalletService.scanConfig.SCAN_WINDOW = 2;
2015-04-01 13:21:06 -07:00
WalletService.scanConfig.DERIVATION_DELAY = 0;
2015-04-02 07:18:39 -07:00
helpers.createAndJoinWallet(1, 2, function(s, w) {
server = s;
wallet = w;
done();
});
2015-04-01 13:11:39 -07:00
});
afterEach(function() {
2015-04-01 13:21:06 -07:00
WalletService.scanConfig = scanConfigOld;
2015-04-01 13:11:39 -07:00
});
2015-04-01 12:42:12 -07:00
it('should scan main addresses', function(done) {
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
2015-04-02 07:18:39 -07:00
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);
server.createAddress({}, function(err, address) {
should.not.exist(err);
address.path.should.equal('m/2147483647/0/4');
done();
});
})
2015-04-01 12:42:12 -07:00
});
});
it('should scan main addresses & copayer addresses', function(done) {
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
2015-04-02 07:18:39 -07:00
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();
})
2015-04-01 12:42:12 -07:00
});
});
2015-04-01 13:11:39 -07:00
it('should restore wallet balance', function(done) {
async.waterfall([
function(next) {
2015-04-02 07:18:39 -07:00
helpers.stubUtxos(server, wallet, [1, 2, 3], function(utxos) {
should.exist(utxos);
helpers.stubAddressActivity(_.pluck(utxos, 'address'));
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next(null, server, wallet);
2015-04-01 13:11:39 -07:00
});
});
},
function(server, wallet, next) {
server.removeWallet({}, function(err) {
next(err);
});
},
function(next) {
// NOTE: this works because it creates the exact same wallet!
helpers.createAndJoinWallet(1, 2, function(server, wallet) {
server.getBalance({}, function(err, balance) {
balance.totalAmount.should.equal(0);
next(null, server, wallet);
});
});
},
function(server, wallet, next) {
server.scan({}, function(err) {
should.not.exist(err);
server.getBalance(wallet.id, function(err, balance) {
balance.totalAmount.should.equal(helpers.toSatoshi(6));
next();
})
});
},
], function(err) {
should.not.exist(err);
done();
});
});
2015-04-01 13:22:36 -07:00
it.skip('should abort scan if there is an error checking address activity', function(done) {});
2015-03-31 13:28:01 -07:00
});
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;
NotificationBroadcaster.removeAllListeners();
});
2015-04-14 11:41:27 -07:00
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') {
2015-04-14 11:19:12 -07:00
server.getWallet({}, function(err, wallet) {
should.exist(wallet.scanStatus);
wallet.scanStatus.should.equal('success');
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);
2015-04-14 11:19:12 -07:00
server.getWallet({}, function(err, wallet) {
should.exist(wallet.scanStatus);
wallet.scanStatus.should.equal('running');
});
});
});
2015-04-14 11:45:52 -07:00
it('should set scan status error when unable to reach blockchain', function(done) {
blockchainExplorer.getAddressActivity = sinon.stub().yields('dummy error');
WalletService.onNotification(function(n) {
if (n.type == 'ScanFinished') {
should.exist(n.data.error);
server.getWallet({}, function(err, wallet) {
should.exist(wallet.scanStatus);
wallet.scanStatus.should.equal('error');
done();
});
}
});
server.startScan({}, function(err) {
should.not.exist(err);
});
});
it('should start multiple asynchronous scans for different wallets', function(done) {
helpers.stubAddressActivity(['3K2VWMXheGZ4qG35DyGjA2dLeKfaSr534A']);
2015-04-03 11:43:22 -07:00
WalletService.scanConfig.SCAN_WINDOW = 1;
var scans = 0;
WalletService.onNotification(function(n) {
if (n.type == 'ScanFinished') {
scans++;
if (scans == 2) done();
}
});
// Create a second wallet
var server2 = new WalletService();
var opts = {
name: 'second wallet',
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
};
server2.createWallet(opts, function(err, walletId) {
should.not.exist(err);
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'copayer 1',
xPubKey: TestData.copayers[3].xPubKey_45H,
requestPubKey: TestData.copayers[3].pubKey_1H_0,
});
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
helpers.getAuthServer(result.copayerId, function(server2) {
2015-04-03 11:43:22 -07:00
server.startScan({}, function(err) {
should.not.exist(err);
scans.should.equal(0);
});
server2.startScan({}, function(err) {
should.not.exist(err);
scans.should.equal(0);
});
scans.should.equal(0);
});
});
});
});
});
2015-03-31 13:28:01 -07:00
describe('#replaceTemporaryRequestKey', function() {
var server, walletId;
beforeEach(function(done) {
server = new WalletService();
var walletOpts = {
name: 'my wallet',
m: 2,
n: 2,
pubKey: TestData.keyPair.pub,
};
server.createWallet(walletOpts, function(err, wId) {
should.not.exist(err);
should.exist.walletId;
walletId = wId;
done();
});
});
it('should join existing wallet with temporaryRequestKey', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
wallet.id.should.equal(walletId);
var copayer = wallet.copayers[0];
copayer.isTemporaryRequestKey.should.equal(true);
done();
});
});
});
});
it('should fail to replace a temporaryRequestKey on a not-complete wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('WNOTFULL');
done();
});
});
});
});
});
it('should fail to replace a temporaryRequestKey is Copayer is not in wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('CDATAMISMATCH');
done();
});
});
});
});
});
it('should fail replace a temporaryRequestKey with invalid copayer', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerOpts2 = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts2.isTemporaryRequestKey = false;
server.joinWallet(copayerOpts2, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
err.code.should.equal('CDATAMISMATCH');
done();
});
});
});
});
});
});
it('should replace a temporaryRequestKey', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1_0,
});
copayerOpts.isTemporaryRequestKey = true;
server.joinWallet(copayerOpts, function(err, result) {
should.not.exist(err);
var copayerId = result.copayerId;
var copayerOpts2 = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_45H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
});
copayerOpts2.isTemporaryRequestKey = false;
server.joinWallet(copayerOpts2, function(err, result) {
should.not.exist(err);
var copayerId2 = result.copayerId;
helpers.getAuthServer(copayerId, function(server) {
server.getWallet({}, function(err, wallet) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
});
copayerOpts.isTemporaryRequestKey = false;
server.replaceTemporaryRequestKey(copayerOpts, function(err, wallet) {
should.not.exist(err);
server.getWallet({}, function(err, wallet) {
wallet.copayers[0].isTemporaryRequestKey.should.equal(false);
wallet.copayers[1].isTemporaryRequestKey.should.equal(false);
done();
});
});
});
});
});
});
});
2015-04-01 12:42:12 -07:00
});
2015-01-27 05:18:45 -08:00
});
2015-03-31 08:29:28 -07:00
describe('Blockchain monitor', function() {
var addressSubscriber;
beforeEach(function() {
db = levelup(memdown, {
valueEncoding: 'json'
});
storage = new Storage({
db: db
});
blockchainExplorer = sinon.stub();
WalletService.initialize({
storage: storage,
blockchainExplorer: blockchainExplorer,
});
helpers.offset = 0;
addressSubscriber = sinon.stub();
addressSubscriber.subscribe = sinon.stub();
sinon.stub(BlockchainMonitor.prototype, '_getAddressSubscriber').onFirstCall().returns(addressSubscriber);
});
afterEach(function() {
BlockchainMonitor.prototype._getAddressSubscriber.restore();
});
it('should subscribe wallet', function(done) {
var monitor = new BlockchainMonitor();
helpers.createAndJoinWallet(2, 2, function(server, wallet) {
server.createAddress({}, function(err, address1) {
should.not.exist(err);
server.createAddress({}, function(err, address2) {
should.not.exist(err);
monitor.subscribeWallet(server, function(err) {
should.not.exist(err);
addressSubscriber.subscribe.calledTwice.should.be.true;
addressSubscriber.subscribe.calledWith(address1.address).should.be.true;
addressSubscriber.subscribe.calledWith(address2.address).should.be.true;
done();
});
});
});
});
});
it('should be able to subscribe new address', function(done) {
var monitor = new BlockchainMonitor();
helpers.createAndJoinWallet(2, 2, function(server, wallet) {
server.createAddress({}, function(err, address1) {
should.not.exist(err);
monitor.subscribeWallet(server, function(err) {
should.not.exist(err);
addressSubscriber.subscribe.calledOnce.should.be.true;
addressSubscriber.subscribe.calledWith(address1.address).should.be.true;
server.createAddress({}, function(err, address2) {
should.not.exist(err);
monitor.subscribeAddresses(wallet.id, address2.address);
addressSubscriber.subscribe.calledTwice.should.be.true;
addressSubscriber.subscribe.calledWith(address2.address).should.be.true;
done();
});
});
});
});
});
it('should create NewIncomingTx notification when a new tx arrives on registered address', function(done) {
var monitor = new BlockchainMonitor();
helpers.createAndJoinWallet(2, 2, function(server, wallet) {
server.createAddress({}, function(err, address1) {
should.not.exist(err);
monitor.subscribeWallet(server, function(err) {
should.not.exist(err);
addressSubscriber.subscribe.calledOnce.should.be.true;
addressSubscriber.subscribe.getCall(0).args[0].should.equal(address1.address);
var handler = addressSubscriber.subscribe.getCall(0).args[1];
_.isFunction(handler).should.be.true;
monitor.on('notification', function(notification) {
notification.type.should.equal('NewIncomingTx');
notification.data.address.should.equal(address1.address);
notification.data.txid.should.equal('txid');
done();
});
handler('txid');
});
});
});
});
});