commit
8fc682953a
5
TODO.txt
5
TODO.txt
|
@ -1,8 +1,7 @@
|
||||||
- When creating a wallet, the id should be generated by the server and returned to the client to be used as part of the wallet secret.
|
- Check length < 100 for both wallet.name & copayer.name
|
||||||
- Check not blank & length < 100 for both wallet.name & copayer.name
|
|
||||||
- Proposal with spent input should be tagged as invalid or removed
|
- Proposal with spent input should be tagged as invalid or removed
|
||||||
- Cron job to broadcast accepted txps that failed to broadcast (we may need to track broadcast attempts for this).
|
- Cron job to broadcast accepted txps that failed to broadcast (we may need to track broadcast attempts for this).
|
||||||
|
- Payment protocol
|
||||||
|
|
||||||
|
|
||||||
- check parameters for KEY at storage
|
- check parameters for KEY at storage
|
||||||
|
|
|
@ -147,7 +147,7 @@ TxProposal.prototype.reject = function(copayerId) {
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.isPending = function() {
|
TxProposal.prototype.isPending = function() {
|
||||||
return this.status === 'pending';
|
return !_.any(['boradcasted', 'rejected'], this.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.prototype.isAccepted = function() {
|
TxProposal.prototype.isAccepted = function() {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var $ = require('preconditions').singleton();
|
||||||
|
|
||||||
var Bitcore = require('bitcore');
|
var Bitcore = require('bitcore');
|
||||||
var BitcoreAddress = Bitcore.Address;
|
var BitcoreAddress = Bitcore.Address;
|
||||||
|
@ -111,9 +112,15 @@ Wallet.prototype.getPublicKey = function(copayerId, path) {
|
||||||
return copayer.getPublicKey(path);
|
return copayer.getPublicKey(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.isComplete = function() {
|
||||||
|
return this.status == 'complete';
|
||||||
|
};
|
||||||
|
|
||||||
Wallet.prototype.createAddress = function(isChange) {
|
Wallet.prototype.createAddress = function(isChange) {
|
||||||
|
$.checkState(this.isComplete());
|
||||||
|
|
||||||
var path = this.addressManager.getNewAddressPath(isChange);
|
var path = this.addressManager.getNewAddressPath(isChange);
|
||||||
|
|
||||||
var publicKeys = _.map(this.copayers, function(copayer) {
|
var publicKeys = _.map(this.copayers, function(copayer) {
|
||||||
var xpub = new Bitcore.HDPublicKey(copayer.xPubKey);
|
var xpub = new Bitcore.HDPublicKey(copayer.xPubKey);
|
||||||
return xpub.derive(path).publicKey;
|
return xpub.derive(path).publicKey;
|
||||||
|
|
|
@ -91,6 +91,7 @@ CopayServer.prototype.createWallet = function(opts, cb) {
|
||||||
|
|
||||||
Utils.checkRequired(opts, ['name', 'm', 'n', 'pubKey']);
|
Utils.checkRequired(opts, ['name', 'm', 'n', 'pubKey']);
|
||||||
|
|
||||||
|
if (_.isEmpty(opts.name)) return cb(new ClientError('Invalid wallet name'));
|
||||||
if (!Wallet.verifyCopayerLimits(opts.m, opts.n))
|
if (!Wallet.verifyCopayerLimits(opts.m, opts.n))
|
||||||
return cb(new ClientError('Invalid combination of required copayers / total copayers'));
|
return cb(new ClientError('Invalid combination of required copayers / total copayers'));
|
||||||
|
|
||||||
|
@ -113,7 +114,7 @@ CopayServer.prototype.createWallet = function(opts, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.storage.storeWallet(wallet, function(err) {
|
self.storage.storeWallet(wallet, function(err) {
|
||||||
return cb(err,wallet.id);
|
return cb(err, wallet.id);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -156,6 +157,8 @@ CopayServer.prototype.joinWallet = function(opts, cb) {
|
||||||
|
|
||||||
Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'xPubKeySignature']);
|
Utils.checkRequired(opts, ['walletId', 'name', 'xPubKey', 'xPubKeySignature']);
|
||||||
|
|
||||||
|
if (_.isEmpty(opts.name)) return cb(new ClientError('Invalid copayer name'));
|
||||||
|
|
||||||
Utils.runLocked(opts.walletId, cb, function(cb) {
|
Utils.runLocked(opts.walletId, cb, function(cb) {
|
||||||
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
|
self.storage.fetchWallet(opts.walletId, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
@ -188,33 +191,22 @@ CopayServer.prototype.joinWallet = function(opts, cb) {
|
||||||
/**
|
/**
|
||||||
* Creates a new address.
|
* Creates a new address.
|
||||||
* @param {Object} opts
|
* @param {Object} opts
|
||||||
* @param {truthy} opts.isChange - Indicates whether this is a regular address or a change address.
|
|
||||||
* @returns {Address} address
|
* @returns {Address} address
|
||||||
*/
|
*/
|
||||||
CopayServer.prototype.createAddress = function(opts, cb) {
|
CopayServer.prototype.createAddress = function(opts, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var isChange = opts.isChange || false;
|
|
||||||
|
|
||||||
Utils.checkRequired(opts, ['isChange']);
|
|
||||||
|
|
||||||
Utils.runLocked(self.walletId, cb, function(cb) {
|
Utils.runLocked(self.walletId, cb, function(cb) {
|
||||||
self.getWallet({}, function(err, wallet) {
|
self.getWallet({}, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete'));
|
||||||
|
|
||||||
var address = wallet.createAddress(opts.isChange);
|
var address = wallet.createAddress(false);
|
||||||
|
|
||||||
self.storage.storeAddress(wallet.id, address, function(err) {
|
self.storage.storeAddressAndWallet(wallet, address, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
self.storage.storeWallet(wallet, function(err) {
|
return cb(null, address);
|
||||||
if (err) {
|
|
||||||
self.storage.removeAddress(wallet.id, address, function() {
|
|
||||||
return cb(err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return cb(null, address);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -399,45 +391,54 @@ CopayServer.prototype.createTx = function(opts, cb) {
|
||||||
|
|
||||||
Utils.checkRequired(opts, ['toAddress', 'amount']);
|
Utils.checkRequired(opts, ['toAddress', 'amount']);
|
||||||
|
|
||||||
|
Utils.runLocked(self.walletId, cb, function(cb) {
|
||||||
// TODO?
|
self.getWallet({}, function(err, wallet) {
|
||||||
// Check some parameters like:
|
|
||||||
// amount > dust
|
|
||||||
|
|
||||||
self.getWallet({}, function(err, wallet) {
|
|
||||||
if (err) return cb(err);
|
|
||||||
|
|
||||||
self._getUtxos(function(err, utxos) {
|
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete'));
|
||||||
|
|
||||||
var changeAddress = wallet.createAddress(true).address;
|
var toAddress;
|
||||||
|
try {
|
||||||
utxos = _.reject(utxos, {
|
toAddress = new Bitcore.Address(opts.toAddress);
|
||||||
locked: true
|
} catch (ex) {
|
||||||
});
|
return cb(new ClientError('INVALIDADDRESS', 'Invalid address'));
|
||||||
|
|
||||||
var txp = new TxProposal({
|
|
||||||
creatorId: self.copayerId,
|
|
||||||
toAddress: opts.toAddress,
|
|
||||||
amount: opts.amount,
|
|
||||||
changeAddress: changeAddress,
|
|
||||||
requiredSignatures: wallet.m,
|
|
||||||
maxRejections: wallet.n - wallet.m,
|
|
||||||
});
|
|
||||||
|
|
||||||
txp.inputs = self._selectUtxos(txp, utxos);
|
|
||||||
if (!txp.inputs) {
|
|
||||||
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds'));
|
|
||||||
}
|
}
|
||||||
|
if (toAddress.network != wallet.getNetworkName()) return cb(new ClientError('INVALIDADDRESS', 'Incorrect address network'));
|
||||||
|
|
||||||
txp.inputPaths = _.pluck(txp.inputs, 'path');
|
self._getUtxos(function(err, utxos) {
|
||||||
|
|
||||||
// no need to do this now: // TODO remove this comment
|
|
||||||
//self._createRawTx(txp);
|
|
||||||
self.storage.storeTx(wallet.id, txp, function(err) {
|
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
return cb(null, txp);
|
var changeAddress = wallet.createAddress(true);
|
||||||
|
|
||||||
|
utxos = _.reject(utxos, {
|
||||||
|
locked: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var txp = new TxProposal({
|
||||||
|
creatorId: self.copayerId,
|
||||||
|
toAddress: opts.toAddress,
|
||||||
|
amount: opts.amount,
|
||||||
|
message: opts.message,
|
||||||
|
changeAddress: changeAddress.address,
|
||||||
|
requiredSignatures: wallet.m,
|
||||||
|
maxRejections: wallet.n - wallet.m,
|
||||||
|
});
|
||||||
|
|
||||||
|
txp.inputs = self._selectUtxos(txp, utxos);
|
||||||
|
if (!txp.inputs) {
|
||||||
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds'));
|
||||||
|
}
|
||||||
|
|
||||||
|
txp.inputPaths = _.pluck(txp.inputs, 'path');
|
||||||
|
|
||||||
|
self.storage.storeAddressAndWallet(wallet, changeAddress, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
self.storage.storeTx(wallet.id, txp, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
return cb(null, txp);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,7 +25,7 @@ var opKey = function(key) {
|
||||||
|
|
||||||
var MAX_TS = '999999999999';
|
var MAX_TS = '999999999999';
|
||||||
var opKeyTs = function(key) {
|
var opKeyTs = function(key) {
|
||||||
return key ? '!' + ('000000000000'+key).slice(-12) : '';
|
return key ? '!' + ('000000000000' + key).slice(-12) : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,8 +201,17 @@ Storage.prototype.fetchAddresses = function(walletId, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Storage.prototype.storeAddress = function(walletId, address, cb) {
|
Storage.prototype.storeAddressAndWallet = function(wallet, address, cb) {
|
||||||
this.db.put(KEY.ADDRESS(walletId, address.address), address, cb);
|
var ops = [{
|
||||||
|
type: 'put',
|
||||||
|
key: KEY.WALLET(wallet.id),
|
||||||
|
value: wallet,
|
||||||
|
}, {
|
||||||
|
type: 'put',
|
||||||
|
key: KEY.ADDRESS(wallet.id, address.address),
|
||||||
|
value: address,
|
||||||
|
}, ];
|
||||||
|
this.db.batch(ops, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
Storage.prototype.removeAddress = function(walletId, address, cb) {
|
Storage.prototype.removeAddress = function(walletId, address, cb) {
|
||||||
|
|
|
@ -68,6 +68,7 @@ helpers.getAuthServer = function(copayerId, cb) {
|
||||||
message: 'dummy',
|
message: 'dummy',
|
||||||
signature: 'dummy',
|
signature: 'dummy',
|
||||||
}, function(err, server) {
|
}, function(err, server) {
|
||||||
|
if (err || !server) throw new Error('Could not login as copayerId ' + copayerId);
|
||||||
signatureStub.restore();
|
signatureStub.restore();
|
||||||
return cb(server);
|
return cb(server);
|
||||||
});
|
});
|
||||||
|
@ -129,9 +130,7 @@ helpers.createUtxos = function(server, wallet, amounts, cb) {
|
||||||
var addresses = [];
|
var addresses = [];
|
||||||
|
|
||||||
async.each(amounts, function(a, next) {
|
async.each(amounts, function(a, next) {
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
addresses.push(address);
|
addresses.push(address);
|
||||||
next(err);
|
next(err);
|
||||||
});
|
});
|
||||||
|
@ -252,26 +251,18 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// non sense with server generated UUIDs
|
it('should fail to create wallet with no name', function(done) {
|
||||||
it.skip('should fail to recreate existing wallet', function(done) {
|
|
||||||
var opts = {
|
var opts = {
|
||||||
id: '123',
|
name: '',
|
||||||
name: 'my wallet',
|
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
pubKey: aPubKey,
|
pubKey: aPubKey,
|
||||||
};
|
};
|
||||||
server.createWallet(opts, function(err) {
|
server.createWallet(opts, function(err, walletId) {
|
||||||
should.not.exist(err);
|
should.not.exist(walletId);
|
||||||
server.storage.fetchWallet('123', function(err, wallet) {
|
err.should.exist;
|
||||||
should.not.exist(err);
|
err.message.should.contain('name');
|
||||||
wallet.id.should.equal('123');
|
done();
|
||||||
wallet.name.should.equal('my wallet');
|
|
||||||
server.createWallet(opts, function(err) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -315,46 +306,63 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#joinWallet', function() {
|
describe('#joinWallet', function() {
|
||||||
var server;
|
var server, walletId;
|
||||||
beforeEach(function() {
|
beforeEach(function(done) {
|
||||||
server = new CopayServer();
|
server = new CopayServer();
|
||||||
});
|
|
||||||
|
|
||||||
it('should join existing wallet', function(done) {
|
|
||||||
var walletOpts = {
|
var walletOpts = {
|
||||||
name: 'my wallet',
|
name: 'my wallet',
|
||||||
m: 2,
|
m: 2,
|
||||||
n: 3,
|
n: 3,
|
||||||
pubKey: keyPair.pub,
|
pubKey: keyPair.pub,
|
||||||
};
|
};
|
||||||
|
server.createWallet(walletOpts, function(err, wId) {
|
||||||
server.createWallet(walletOpts, function(err, walletId) {
|
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var copayerOpts = {
|
should.exist.walletId;
|
||||||
walletId: walletId,
|
walletId = wId;
|
||||||
name: 'me',
|
done();
|
||||||
xPubKey: aXPubKey,
|
});
|
||||||
xPubKeySignature: aXPubKeySignature,
|
});
|
||||||
};
|
|
||||||
server.joinWallet(copayerOpts, function(err, copayerId) {
|
it('should join existing wallet', function(done) {
|
||||||
should.not.exist(err);
|
var copayerOpts = {
|
||||||
helpers.getAuthServer(copayerId, function(server) {
|
walletId: walletId,
|
||||||
server.getWallet({}, function(err, wallet) {
|
name: 'me',
|
||||||
wallet.id.should.equal(walletId);
|
xPubKey: aXPubKey,
|
||||||
wallet.copayers.length.should.equal(1);
|
xPubKeySignature: aXPubKeySignature,
|
||||||
var copayer = wallet.copayers[0];
|
};
|
||||||
copayer.name.should.equal('me');
|
server.joinWallet(copayerOpts, function(err, copayerId) {
|
||||||
copayer.id.should.equal(copayerId);
|
should.not.exist(err);
|
||||||
done();
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail to join with no name', function(done) {
|
||||||
|
var copayerOpts = {
|
||||||
|
walletId: walletId,
|
||||||
|
name: '',
|
||||||
|
xPubKey: someXPubKeys[0],
|
||||||
|
xPubKeySignature: someXPubKeysSignatures[0],
|
||||||
|
};
|
||||||
|
server.joinWallet(copayerOpts, function(err, copayerId) {
|
||||||
|
should.not.exist(copayerId);
|
||||||
|
err.should.exist;
|
||||||
|
err.message.should.contain('name');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail to join non-existent wallet', function(done) {
|
it('should fail to join non-existent wallet', function(done) {
|
||||||
var copayerOpts = {
|
var copayerOpts = {
|
||||||
walletId: '234',
|
walletId: '123',
|
||||||
name: 'me',
|
name: 'me',
|
||||||
xPubKey: 'dummy',
|
xPubKey: 'dummy',
|
||||||
xPubKeySignature: 'dummy',
|
xPubKeySignature: 'dummy',
|
||||||
|
@ -366,143 +374,77 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail to join full wallet', function(done) {
|
it('should fail to join full wallet', function(done) {
|
||||||
var walletOpts = {
|
helpers.createAndJoinWallet(1, 1, function(s, wallet) {
|
||||||
name: 'my wallet',
|
var copayerOpts = {
|
||||||
m: 1,
|
walletId: wallet.id,
|
||||||
n: 1,
|
|
||||||
pubKey: keyPair.pub,
|
|
||||||
};
|
|
||||||
server.createWallet(walletOpts, function(err,walletId) {
|
|
||||||
should.not.exist(err);
|
|
||||||
var copayer1Opts = {
|
|
||||||
walletId: walletId,
|
|
||||||
id: '111',
|
|
||||||
name: 'me',
|
name: 'me',
|
||||||
xPubKey: someXPubKeys[0],
|
|
||||||
xPubKeySignature: someXPubKeysSignatures[0],
|
|
||||||
};
|
|
||||||
var copayer2Opts = {
|
|
||||||
walletId: walletId,
|
|
||||||
id: '222',
|
|
||||||
name: 'me 2',
|
|
||||||
xPubKey: someXPubKeys[1],
|
xPubKey: someXPubKeys[1],
|
||||||
xPubKeySignature: someXPubKeysSignatures[1],
|
xPubKeySignature: someXPubKeysSignatures[1],
|
||||||
};
|
};
|
||||||
server.joinWallet(copayer1Opts, function(err, copayer1Id) {
|
server.joinWallet(copayerOpts, function(err) {
|
||||||
should.not.exist(err);
|
should.exist(err);
|
||||||
helpers.getAuthServer(copayer1Id, function(server) {
|
err.code.should.equal('WFULL');
|
||||||
server.getWallet({}, function(err, wallet) {
|
err.message.should.equal('Wallet full');
|
||||||
wallet.status.should.equal('complete');
|
done();
|
||||||
server.joinWallet(copayer2Opts, function(err, copayer2Id) {
|
|
||||||
should.exist(err);
|
|
||||||
err.code.should.equal('WFULL');
|
|
||||||
err.message.should.equal('Wallet full');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail to re-join wallet', function(done) {
|
it('should fail to re-join wallet', function(done) {
|
||||||
var walletOpts = {
|
var copayerOpts = {
|
||||||
name: 'my wallet',
|
walletId: walletId,
|
||||||
m: 1,
|
name: 'me',
|
||||||
n: 1,
|
xPubKey: someXPubKeys[0],
|
||||||
pubKey: keyPair.pub,
|
xPubKeySignature: someXPubKeysSignatures[0],
|
||||||
};
|
};
|
||||||
server.createWallet(walletOpts, function(err, walletId) {
|
server.joinWallet(copayerOpts, function(err) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
var copayerOpts = {
|
|
||||||
walletId: walletId,
|
|
||||||
id: '111',
|
|
||||||
name: 'me',
|
|
||||||
xPubKey: someXPubKeys[0],
|
|
||||||
xPubKeySignature: someXPubKeysSignatures[0],
|
|
||||||
};
|
|
||||||
server.joinWallet(copayerOpts, function(err) {
|
server.joinWallet(copayerOpts, function(err) {
|
||||||
should.not.exist(err);
|
should.exist(err);
|
||||||
server.joinWallet(copayerOpts, function(err) {
|
err.code.should.equal('CINWALLET');
|
||||||
should.exist(err);
|
err.message.should.equal('Copayer already in wallet');
|
||||||
err.code.should.equal('CINWALLET');
|
done();
|
||||||
err.message.should.equal('Copayer already in wallet');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should fail to join with bad formated signature', function(done) {
|
it('should fail to join with bad formated signature', function(done) {
|
||||||
var walletOpts = {
|
var copayerOpts = {
|
||||||
id: '123',
|
walletId: walletId,
|
||||||
name: 'my wallet',
|
name: 'me',
|
||||||
m: 1,
|
xPubKey: someXPubKeys[0],
|
||||||
n: 1,
|
xPubKeySignature: 'bad sign',
|
||||||
pubKey: aPubKey,
|
|
||||||
};
|
};
|
||||||
server.createWallet(walletOpts, function(err, walletId) {
|
server.joinWallet(copayerOpts, function(err) {
|
||||||
should.not.exist(err);
|
err.message.should.equal('Bad request');
|
||||||
var copayerOpts = {
|
done();
|
||||||
walletId: walletId,
|
|
||||||
name: 'me',
|
|
||||||
xPubKey: someXPubKeys[0],
|
|
||||||
xPubKeySignature: 'bad sign',
|
|
||||||
};
|
|
||||||
server.joinWallet(copayerOpts, function(err) {
|
|
||||||
err.message.should.equal('Bad request');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should fail to join with null signature', function(done) {
|
it('should fail to join with null signature', function(done) {
|
||||||
var walletOpts = {
|
var copayerOpts = {
|
||||||
id: '123',
|
walletId: walletId,
|
||||||
name: 'my wallet',
|
name: 'me',
|
||||||
m: 1,
|
xPubKey: someXPubKeys[0],
|
||||||
n: 1,
|
|
||||||
pubKey: aPubKey,
|
|
||||||
};
|
};
|
||||||
server.createWallet(walletOpts, function(err) {
|
try {
|
||||||
should.not.exist(err);
|
server.joinWallet(copayerOpts, function(err) {});
|
||||||
var copayerOpts = {
|
} catch (e) {
|
||||||
walletId: '123',
|
e.should.contain('xPubKeySignature');
|
||||||
id: '111',
|
done();
|
||||||
name: 'me',
|
}
|
||||||
xPubKey: someXPubKeys[0],
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
server.joinWallet(copayerOpts, function(err) {});
|
|
||||||
} catch (e) {
|
|
||||||
e.should.contain('xPubKeySignature');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail to join with wrong signature', function(done) {
|
it('should fail to join with wrong signature', function(done) {
|
||||||
var walletOpts = {
|
var copayerOpts = {
|
||||||
id: '123',
|
walletId: walletId,
|
||||||
name: 'my wallet',
|
name: 'me',
|
||||||
m: 1,
|
xPubKey: someXPubKeys[0],
|
||||||
n: 1,
|
xPubKeySignature: someXPubKeysSignatures[1],
|
||||||
pubKey: aPubKey,
|
|
||||||
};
|
};
|
||||||
server.createWallet(walletOpts, function(err, walletId) {
|
server.joinWallet(copayerOpts, function(err) {
|
||||||
should.not.exist(err);
|
err.message.should.equal('Bad request');
|
||||||
var copayerOpts = {
|
done();
|
||||||
walletId: walletId,
|
|
||||||
name: 'me',
|
|
||||||
xPubKey: someXPubKeys[0],
|
|
||||||
xPubKeySignature: someXPubKeysSignatures[0],
|
|
||||||
};
|
|
||||||
server.joinWallet(copayerOpts, function(err) {
|
|
||||||
err.message.should.equal('Bad request');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -566,10 +508,8 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create main address', function(done) {
|
it('should create address', function(done) {
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
address.should.exist;
|
||||||
address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR');
|
address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR');
|
||||||
|
@ -578,84 +518,66 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail to create address when wallet is not complete', function(done) {
|
||||||
it('should create change address', function(done) {
|
var server = new CopayServer();
|
||||||
server.createAddress({
|
var walletOpts = {
|
||||||
isChange: true,
|
name: 'my wallet',
|
||||||
}, function(err, address) {
|
m: 2,
|
||||||
|
n: 3,
|
||||||
|
pubKey: keyPair.pub,
|
||||||
|
};
|
||||||
|
server.createWallet(walletOpts, function(err, walletId) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
var copayerOpts = {
|
||||||
address.address.should.equal('3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH');
|
walletId: walletId,
|
||||||
address.path.should.equal('m/2147483647/1/0');
|
name: 'me',
|
||||||
done();
|
xPubKey: aXPubKey,
|
||||||
});
|
xPubKeySignature: aXPubKeySignature,
|
||||||
});
|
};
|
||||||
|
server.joinWallet(copayerOpts, function(err, copayerId) {
|
||||||
it.skip('should fail to create address when wallet is not complete', function(done) {});
|
should.not.exist(err);
|
||||||
|
helpers.getAuthServer(copayerId, function(server) {
|
||||||
it('should create many addresses on simultaneous requests', function(done) {
|
server.createAddress({}, function(err, address) {
|
||||||
async.map(_.range(10), function(i, cb) {
|
should.not.exist(address);
|
||||||
server.createAddress({
|
err.should.exist;
|
||||||
isChange: false,
|
err.message.should.contain('not complete');
|
||||||
}, cb);
|
done();
|
||||||
}, function(err, addresses) {
|
});
|
||||||
addresses.length.should.equal(10);
|
|
||||||
addresses[0].path.should.equal('m/2147483647/0/0');
|
|
||||||
addresses[9].path.should.equal('m/2147483647/0/9');
|
|
||||||
// No two identical addresses
|
|
||||||
_.keys(_.groupBy(addresses, 'address')).length.should.equal(10);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not create address if unable to store wallet', function(done) {
|
|
||||||
var storeWalletStub = sinon.stub(server.storage, 'storeWallet');
|
|
||||||
storeWalletStub.yields('dummy error');
|
|
||||||
|
|
||||||
server.createAddress({
|
|
||||||
isChange: true,
|
|
||||||
}, function(err, address) {
|
|
||||||
err.should.exist;
|
|
||||||
should.not.exist(address);
|
|
||||||
|
|
||||||
server.getAddresses({}, function(err, addresses) {
|
|
||||||
addresses.length.should.equal(0);
|
|
||||||
|
|
||||||
server.storage.storeWallet.restore();
|
|
||||||
server.createAddress({
|
|
||||||
isChange: true,
|
|
||||||
}, function(err, address) {
|
|
||||||
should.not.exist(err);
|
|
||||||
address.should.exist;
|
|
||||||
address.address.should.equal('3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH');
|
|
||||||
address.path.should.equal('m/2147483647/1/0');
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not create address if unable to store addresses', function(done) {
|
it('should create many addresses on simultaneous requests', function(done) {
|
||||||
var storeAddressStub = sinon.stub(server.storage, 'storeAddress');
|
var N = 5;
|
||||||
storeAddressStub.yields('dummy error');
|
async.map(_.range(N), function(i, cb) {
|
||||||
|
server.createAddress({}, cb);
|
||||||
|
}, function(err, addresses) {
|
||||||
|
addresses.length.should.equal(N);
|
||||||
|
_.each(_.range(N), function(i) {
|
||||||
|
addresses[i].path.should.equal('m/2147483647/0/' + i);
|
||||||
|
});
|
||||||
|
// No two identical addresses
|
||||||
|
_.uniq(_.pluck(addresses, 'address')).length.should.equal(N);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
server.createAddress({
|
it('should not create address if unable to store it', function(done) {
|
||||||
isChange: true,
|
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
|
||||||
}, function(err, address) {
|
server.createAddress({}, function(err, address) {
|
||||||
err.should.exist;
|
err.should.exist;
|
||||||
should.not.exist(address);
|
should.not.exist(address);
|
||||||
|
|
||||||
server.getAddresses({}, function(err, addresses) {
|
server.getAddresses({}, function(err, addresses) {
|
||||||
addresses.length.should.equal(0);
|
addresses.length.should.equal(0);
|
||||||
|
|
||||||
server.storage.storeAddress.restore();
|
server.storage.storeAddressAndWallet.restore();
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: true,
|
|
||||||
}, function(err, address) {
|
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
address.should.exist;
|
||||||
address.address.should.equal('3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH');
|
address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR');
|
||||||
address.path.should.equal('m/2147483647/1/0');
|
address.path.should.equal('m/2147483647/0/0');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -669,29 +591,25 @@ describe('Copay server', function() {
|
||||||
helpers.createAndJoinWallet(2, 2, function(s, w) {
|
helpers.createAndJoinWallet(2, 2, function(s, w) {
|
||||||
server = s;
|
server = s;
|
||||||
wallet = w;
|
wallet = w;
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a tx', function(done) {
|
it('should create a tx', function(done) {
|
||||||
|
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
||||||
helpers.stubBlockExplorer(server, utxos);
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(80),
|
amount: helpers.toSatoshi(80),
|
||||||
message: 'some message',
|
message: 'some message',
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
tx.should.exist;
|
tx.should.exist;
|
||||||
|
tx.message.should.equal('some message');
|
||||||
tx.isAccepted().should.equal.false;
|
tx.isAccepted().should.equal.false;
|
||||||
tx.isRejected().should.equal.false;
|
tx.isRejected().should.equal.false;
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
|
@ -708,7 +626,75 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('should fail to create tx when wallet is not complete', function(done) {});
|
it('should fail to create tx when wallet is not complete', function(done) {
|
||||||
|
var server = new CopayServer();
|
||||||
|
var walletOpts = {
|
||||||
|
name: 'my wallet',
|
||||||
|
m: 2,
|
||||||
|
n: 3,
|
||||||
|
pubKey: keyPair.pub,
|
||||||
|
};
|
||||||
|
server.createWallet(walletOpts, function(err, walletId) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var copayerOpts = {
|
||||||
|
walletId: walletId,
|
||||||
|
name: 'me',
|
||||||
|
xPubKey: aXPubKey,
|
||||||
|
xPubKeySignature: aXPubKeySignature,
|
||||||
|
};
|
||||||
|
server.joinWallet(copayerOpts, function(err, copayerId) {
|
||||||
|
should.not.exist(err);
|
||||||
|
helpers.getAuthServer(copayerId, function(server, wallet) {
|
||||||
|
var txOpts = {
|
||||||
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
|
amount: helpers.toSatoshi(80),
|
||||||
|
};
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(tx);
|
||||||
|
err.should.exist;
|
||||||
|
err.message.should.contain('not complete');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to create tx for address invalid address', function(done) {
|
||||||
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
var txOpts = {
|
||||||
|
toAddress: 'invalid address',
|
||||||
|
amount: helpers.toSatoshi(80),
|
||||||
|
};
|
||||||
|
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(tx);
|
||||||
|
err.should.exist;
|
||||||
|
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) {
|
||||||
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
var txOpts = {
|
||||||
|
toAddress: 'myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', // testnet
|
||||||
|
amount: helpers.toSatoshi(80),
|
||||||
|
};
|
||||||
|
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(tx);
|
||||||
|
err.should.exist;
|
||||||
|
err.code.should.equal('INVALIDADDRESS');
|
||||||
|
err.message.should.equal('Incorrect address network');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should fail to create tx when insufficient funds', function(done) {
|
it('should fail to create tx when insufficient funds', function(done) {
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) {
|
||||||
|
@ -716,9 +702,6 @@ describe('Copay server', function() {
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(120),
|
amount: helpers.toSatoshi(120),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
@ -738,15 +721,16 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.skip('should fail to create tx when insufficient funds for fee', function(done) {});
|
||||||
|
|
||||||
|
it.skip('should fail to create tx for dust amount', function(done) {});
|
||||||
|
|
||||||
it('should create tx when there is a pending tx and enough UTXOs', function(done) {
|
it('should create tx when there is a pending tx and enough UTXOs', function(done) {
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) {
|
||||||
helpers.stubBlockExplorer(server, utxos);
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(12),
|
amount: helpers.toSatoshi(12),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -755,9 +739,6 @@ describe('Copay server', function() {
|
||||||
var txOpts2 = {
|
var txOpts2 = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: 8,
|
amount: 8,
|
||||||
message: 'some message 2',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts2, function(err, tx) {
|
server.createTx(txOpts2, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -783,9 +764,6 @@ describe('Copay server', function() {
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(12),
|
amount: helpers.toSatoshi(12),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -794,9 +772,6 @@ describe('Copay server', function() {
|
||||||
var txOpts2 = {
|
var txOpts2 = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(24),
|
amount: helpers.toSatoshi(24),
|
||||||
message: 'some message 2',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts2, function(err, tx) {
|
server.createTx(txOpts2, function(err, tx) {
|
||||||
err.code.should.equal('INSUFFICIENTFUNDS');
|
err.code.should.equal('INSUFFICIENTFUNDS');
|
||||||
|
@ -815,7 +790,42 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create tx using different UTXOs for simultaneous requests', function(done) {
|
||||||
|
var N = 5;
|
||||||
|
helpers.createUtxos(server, wallet, helpers.toSatoshi(_.times(N, function() {
|
||||||
|
return 100;
|
||||||
|
})), function(utxos) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
server.getBalance({}, function(err, balance) {
|
||||||
|
should.not.exist(err);
|
||||||
|
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
|
||||||
|
balance.lockedAmount.should.equal(helpers.toSatoshi(0));
|
||||||
|
|
||||||
|
var txOpts = {
|
||||||
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
|
amount: helpers.toSatoshi(80),
|
||||||
|
};
|
||||||
|
async.map(_.range(N), function(i, cb) {
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
cb(err, tx);
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txs.length.should.equal(N);
|
||||||
|
_.uniq(_.pluck(txs, 'changeAddress')).length.should.equal(N);
|
||||||
|
server.getBalance({}, function(err, balance) {
|
||||||
|
should.not.exist(err);
|
||||||
|
balance.totalAmount.should.equal(helpers.toSatoshi(N * 100));
|
||||||
|
balance.lockedAmount.should.equal(balance.totalAmount);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -826,17 +836,12 @@ describe('Copay server', function() {
|
||||||
helpers.createAndJoinWallet(2, 2, function(s, w) {
|
helpers.createAndJoinWallet(2, 2, function(s, w) {
|
||||||
server = s;
|
server = s;
|
||||||
wallet = w;
|
wallet = w;
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) {
|
||||||
helpers.stubBlockExplorer(server, utxos);
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(10),
|
amount: helpers.toSatoshi(10),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -897,20 +902,16 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('#signTx and broadcast', function() {
|
describe('#signTx and broadcast', function() {
|
||||||
var server, wallet, utxos;
|
var server, wallet, utxos;
|
||||||
|
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
helpers.createAndJoinWallet(1, 1, function(s, w) {
|
helpers.createAndJoinWallet(1, 1, function(s, w) {
|
||||||
server = s;
|
server = s;
|
||||||
wallet = w;
|
wallet = w;
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) {
|
||||||
utxos = inutxos;
|
utxos = inutxos;
|
||||||
done();
|
done();
|
||||||
|
@ -924,9 +925,6 @@ describe('Copay server', function() {
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(10),
|
amount: helpers.toSatoshi(10),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -952,14 +950,10 @@ describe('Copay server', function() {
|
||||||
|
|
||||||
|
|
||||||
it('should keep tx as *accepted* if unable to broadcast it', function(done) {
|
it('should keep tx as *accepted* if unable to broadcast it', function(done) {
|
||||||
|
|
||||||
helpers.stubBlockExplorer(server, utxos);
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
amount: helpers.toSatoshi(10),
|
amount: helpers.toSatoshi(10),
|
||||||
message: 'some message',
|
|
||||||
otToken: 'dummy',
|
|
||||||
requestSignature: 'dummy',
|
|
||||||
};
|
};
|
||||||
server.createTx(txOpts, function(err, txp) {
|
server.createTx(txOpts, function(err, txp) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
@ -978,14 +972,11 @@ describe('Copay server', function() {
|
||||||
|
|
||||||
server.getPendingTxs({}, function(err, txps) {
|
server.getPendingTxs({}, function(err, txps) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txps.length.should.equal(0);
|
txps.length.should.equal(1);
|
||||||
server.getTx({
|
var txp = txps[0];
|
||||||
id: txpid
|
txp.status.should.equal('accepted');
|
||||||
}, function(err, txp) {
|
should.not.exist(txp.txid);
|
||||||
txp.status.should.equal('accepted');
|
done();
|
||||||
should.not.exist(txp.txid);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -993,10 +984,46 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Multisignature wallet', function() {
|
describe('Tx proposal workflow', function() {
|
||||||
it.skip('all copayers should see pending proposal created by one copayer', function(done) {});
|
var server, wallet, utxos;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
helpers.createAndJoinWallet(2, 3, function(s, w) {
|
||||||
|
server = s;
|
||||||
|
wallet = w;
|
||||||
|
server.createAddress({}, function(err, address) {
|
||||||
|
helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) {
|
||||||
|
utxos = inutxos;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it.skip('tx proposals should not be broadcast until quorum is reached', function(done) {});
|
it('other copayers should see pending proposal created by one copayer', function(done) {
|
||||||
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
|
var txOpts = {
|
||||||
|
toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7',
|
||||||
|
amount: helpers.toSatoshi(10),
|
||||||
|
message: 'some message',
|
||||||
|
};
|
||||||
|
server.createTx(txOpts, function(err, txp) {
|
||||||
|
should.not.exist(err);
|
||||||
|
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);
|
||||||
|
txps[0].message.should.equal('some message');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('tx proposals should not be broadcast until quorum is reached', function(done) {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it.skip('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {});
|
it.skip('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {});
|
||||||
|
|
||||||
|
@ -1007,7 +1034,7 @@ describe('Copay server', function() {
|
||||||
var server, wallet, clock;
|
var server, wallet, clock;
|
||||||
|
|
||||||
beforeEach(function(done) {
|
beforeEach(function(done) {
|
||||||
if (server)
|
if (server)
|
||||||
return done();
|
return done();
|
||||||
|
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
|
@ -1016,9 +1043,7 @@ describe('Copay server', function() {
|
||||||
helpers.createAndJoinWallet(1, 1, function(s, w) {
|
helpers.createAndJoinWallet(1, 1, function(s, w) {
|
||||||
server = s;
|
server = s;
|
||||||
wallet = w;
|
wallet = w;
|
||||||
server.createAddress({
|
server.createAddress({}, function(err, address) {
|
||||||
isChange: false,
|
|
||||||
}, function(err, address) {
|
|
||||||
helpers.createUtxos(server, wallet, helpers.toSatoshi(_.range(10)), function(utxos) {
|
helpers.createUtxos(server, wallet, helpers.toSatoshi(_.range(10)), function(utxos) {
|
||||||
helpers.stubBlockExplorer(server, utxos);
|
helpers.stubBlockExplorer(server, utxos);
|
||||||
var txOpts = {
|
var txOpts = {
|
||||||
|
@ -1026,14 +1051,13 @@ describe('Copay server', function() {
|
||||||
amount: helpers.toSatoshi(0.1),
|
amount: helpers.toSatoshi(0.1),
|
||||||
};
|
};
|
||||||
async.eachSeries(_.range(10), function(i, next) {
|
async.eachSeries(_.range(10), function(i, next) {
|
||||||
clock.tick(10000);
|
clock.tick(10000);
|
||||||
server.createTx(txOpts, function(err, tx) {
|
server.createTx(txOpts, function(err, tx) {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe('Wallet', function() {
|
||||||
describe('#fromObj', function() {
|
describe('#fromObj', function() {
|
||||||
it('read a wallet', function() {
|
it('read a wallet', function() {
|
||||||
var w = Wallet.fromObj(testWallet);
|
var w = Wallet.fromObj(testWallet);
|
||||||
w.status.should.equal('complete');
|
w.isComplete().should.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#createAddress', function() {
|
describe('#createAddress', function() {
|
||||||
|
|
Loading…
Reference in New Issue