From 7726a69ae598301d9ea236ee65fbc1cdce1027ed Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 11:20:22 -0300 Subject: [PATCH 01/14] fix txProposal#isPending --- lib/model/txproposal.js | 2 +- test/integration.js | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 3a9d277..5ea96c0 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -147,7 +147,7 @@ TxProposal.prototype.reject = function(copayerId) { }; TxProposal.prototype.isPending = function() { - return this.status === 'pending'; + return !_.any(['boradcasted', 'rejected'], this.status); }; TxProposal.prototype.isAccepted = function() { diff --git a/test/integration.js b/test/integration.js index 479fa93..c8664b7 100644 --- a/test/integration.js +++ b/test/integration.js @@ -978,14 +978,11 @@ describe('Copay server', function() { server.getPendingTxs({}, function(err, txps) { should.not.exist(err); - txps.length.should.equal(0); - server.getTx({ - id: txpid - }, function(err, txp) { - txp.status.should.equal('accepted'); - should.not.exist(txp.txid); - done(); - }); + txps.length.should.equal(1); + var txp = txps[0]; + txp.status.should.equal('accepted'); + should.not.exist(txp.txid); + done(); }); }); }); From 70475fa17c012cd6296f8f01dd6e2f5d09d884c9 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 11:36:15 -0300 Subject: [PATCH 02/14] remove recreate wallet test --- test/integration.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test/integration.js b/test/integration.js index c8664b7..249d911 100644 --- a/test/integration.js +++ b/test/integration.js @@ -252,29 +252,6 @@ describe('Copay server', function() { }); }); - // non sense with server generated UUIDs - it.skip('should fail to recreate existing wallet', function(done) { - var opts = { - id: '123', - name: 'my wallet', - m: 2, - n: 3, - pubKey: aPubKey, - }; - server.createWallet(opts, function(err) { - should.not.exist(err); - server.storage.fetchWallet('123', function(err, wallet) { - should.not.exist(err); - wallet.id.should.equal('123'); - wallet.name.should.equal('my wallet'); - server.createWallet(opts, function(err) { - should.exist(err); - done(); - }); - }); - }); - }); - it('should fail to create wallet with invalid copayer pairs', function(done) { var invalidPairs = [{ m: 0, From 1ba97a3883df98f2fc8fbe0096e7f39f20e17a63 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 11:47:04 -0300 Subject: [PATCH 03/14] fix address creation when wallet not complete --- lib/model/wallet.js | 9 +++++++- lib/server.js | 3 ++- test/integration.js | 51 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 8bf50d2..5191523 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var util = require('util'); +var $ = require('preconditions').singleton(); var Bitcore = require('bitcore'); var BitcoreAddress = Bitcore.Address; @@ -111,9 +112,15 @@ Wallet.prototype.getPublicKey = function(copayerId, path) { return copayer.getPublicKey(path); }; +Wallet.prototype.isComplete = function() { + return this.status == 'complete'; +}; + Wallet.prototype.createAddress = function(isChange) { + $.checkState(this.isComplete()); + var path = this.addressManager.getNewAddressPath(isChange); - + var publicKeys = _.map(this.copayers, function(copayer) { var xpub = new Bitcore.HDPublicKey(copayer.xPubKey); return xpub.derive(path).publicKey; diff --git a/lib/server.js b/lib/server.js index 5a1c49e..b58c288 100644 --- a/lib/server.js +++ b/lib/server.js @@ -113,7 +113,7 @@ CopayServer.prototype.createWallet = function(opts, cb) { }); self.storage.storeWallet(wallet, function(err) { - return cb(err,wallet.id); + return cb(err, wallet.id); }); }; @@ -200,6 +200,7 @@ CopayServer.prototype.createAddress = function(opts, cb) { Utils.runLocked(self.walletId, cb, function(cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); + if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); var address = wallet.createAddress(opts.isChange); diff --git a/test/integration.js b/test/integration.js index 249d911..ced4a41 100644 --- a/test/integration.js +++ b/test/integration.js @@ -349,7 +349,7 @@ describe('Copay server', function() { n: 1, pubKey: keyPair.pub, }; - server.createWallet(walletOpts, function(err,walletId) { + server.createWallet(walletOpts, function(err, walletId) { should.not.exist(err); var copayer1Opts = { walletId: walletId, @@ -568,7 +568,37 @@ describe('Copay server', function() { }); }); - it.skip('should fail to create address when wallet is not complete', function(done) {}); + it('should fail to create address 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) { + server.createAddress({ + isChange: false, + }, function(err, address) { + should.not.exist(address); + err.should.exist; + err.message.should.contain('not complete'); + done(); + }); + }); + }); + }); + }); it('should create many addresses on simultaneous requests', function(done) { async.map(_.range(10), function(i, cb) { @@ -981,7 +1011,7 @@ describe('Copay server', function() { var server, wallet, clock; beforeEach(function(done) { - if (server) + if (server) return done(); this.timeout(5000); @@ -1000,14 +1030,13 @@ describe('Copay server', function() { amount: helpers.toSatoshi(0.1), }; async.eachSeries(_.range(10), function(i, next) { - clock.tick(10000); - server.createTx(txOpts, function(err, tx) { - next(); - }); - }, function(err) { - return done(err); - } - ); + clock.tick(10000); + server.createTx(txOpts, function(err, tx) { + next(); + }); + }, function(err) { + return done(err); + }); }); }); }); From 00c56650da4bb0cfceede1a16d317551bc62f028 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 11:53:06 -0300 Subject: [PATCH 04/14] fix tx creation when wallet not complete --- lib/server.js | 1 + test/integration.js | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index b58c288..829bc2e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -407,6 +407,7 @@ CopayServer.prototype.createTx = function(opts, cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); + if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); self._getUtxos(function(err, utxos) { if (err) return cb(err); diff --git a/test/integration.js b/test/integration.js index ced4a41..7d4c57c 100644 --- a/test/integration.js +++ b/test/integration.js @@ -715,7 +715,42 @@ 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), + message: 'some message', + otToken: 'dummy', + requestSignature: 'dummy', + }; + 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 when insufficient funds', function(done) { helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) { From 516926c78a141202b17f575d53c7316d1f720a04 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 12:05:59 -0300 Subject: [PATCH 05/14] implement test to see tx proposal created by other copayers --- test/integration.js | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/integration.js b/test/integration.js index 7d4c57c..038eb81 100644 --- a/test/integration.js +++ b/test/integration.js @@ -68,6 +68,7 @@ helpers.getAuthServer = function(copayerId, cb) { message: 'dummy', signature: 'dummy', }, function(err, server) { + if (err || !server) throw new Error('Could not login as copayerId ' + copayerId); signatureStub.restore(); return cb(server); }); @@ -945,7 +946,6 @@ describe('Copay server', function() { describe('#signTx and broadcast', function() { var server, wallet, utxos; - beforeEach(function(done) { helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; @@ -994,7 +994,6 @@ describe('Copay server', function() { it('should keep tx as *accepted* if unable to broadcast it', function(done) { - helpers.stubBlockExplorer(server, utxos); var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', @@ -1033,7 +1032,44 @@ describe('Copay server', function() { }); describe('Multisignature wallet', 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({ + isChange: false, + }, function(err, address) { + helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) { + utxos = inutxos; + 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', + otToken: 'dummy', + requestSignature: 'dummy', + }; + 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); + done(); + }); + }); + }); + }); it.skip('tx proposals should not be broadcast until quorum is reached', function(done) {}); From c1a0ec6f5d047b94c6d120ece0f5d9766d76b20b Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 13:16:41 -0300 Subject: [PATCH 06/14] add tests --- lib/server.js | 1 + test/integration.js | 29 +++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/server.js b/lib/server.js index 829bc2e..98aa301 100644 --- a/lib/server.js +++ b/lib/server.js @@ -91,6 +91,7 @@ CopayServer.prototype.createWallet = function(opts, cb) { 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)) return cb(new ClientError('Invalid combination of required copayers / total copayers')); diff --git a/test/integration.js b/test/integration.js index 038eb81..bd06f35 100644 --- a/test/integration.js +++ b/test/integration.js @@ -253,6 +253,21 @@ describe('Copay server', function() { }); }); + it('should fail to create wallet with no name', function(done) { + var opts = { + name: '', + m: 2, + n: 3, + pubKey: aPubKey, + }; + server.createWallet(opts, function(err, walletId) { + should.not.exist(walletId); + err.should.exist; + err.message.should.contain('name'); + done(); + }); + }); + it('should fail to create wallet with invalid copayer pairs', function(done) { var invalidPairs = [{ m: 0, @@ -753,6 +768,8 @@ describe('Copay server', function() { }); }); + it.skip('should fail to create tx for address of different network', function(done) {}); + it('should fail to create tx when insufficient funds', function(done) { helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) { helpers.stubBlockExplorer(server, utxos); @@ -781,6 +798,10 @@ 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) { helpers.createUtxos(server, wallet, helpers.toSatoshi([10.1, 10.2, 10.3]), function(utxos) { helpers.stubBlockExplorer(server, utxos); @@ -858,7 +879,6 @@ describe('Copay server', function() { }); }); }); - }); }); @@ -940,7 +960,6 @@ describe('Copay server', function() { }); }); }); - }); @@ -1031,7 +1050,7 @@ describe('Copay server', function() { }); }); - describe('Multisignature wallet', function() { + describe('Tx proposal workflow', function() { var server, wallet, utxos; beforeEach(function(done) { helpers.createAndJoinWallet(2, 3, function(s, w) { @@ -1071,7 +1090,9 @@ describe('Copay server', function() { }); }); - it.skip('tx proposals should not be broadcast until quorum is reached', function(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) {}); From e665db210bf1722b2681f55372433169fe30af4a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 13:36:19 -0300 Subject: [PATCH 07/14] refactor join wallet tests --- lib/server.js | 2 + test/integration.js | 221 +++++++++++++++++--------------------------- 2 files changed, 88 insertions(+), 135 deletions(-) diff --git a/lib/server.js b/lib/server.js index 98aa301..ae9ff5e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -157,6 +157,8 @@ CopayServer.prototype.joinWallet = function(opts, cb) { 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) { self.storage.fetchWallet(opts.walletId, function(err, wallet) { if (err) return cb(err); diff --git a/test/integration.js b/test/integration.js index bd06f35..8876cd9 100644 --- a/test/integration.js +++ b/test/integration.js @@ -308,46 +308,63 @@ describe('Copay server', function() { }); describe('#joinWallet', function() { - var server; - beforeEach(function() { + var server, walletId; + beforeEach(function(done) { server = new CopayServer(); - }); - - it('should join existing wallet', function(done) { var walletOpts = { name: 'my wallet', m: 2, n: 3, pubKey: keyPair.pub, }; - - server.createWallet(walletOpts, function(err, walletId) { + server.createWallet(walletOpts, function(err, wId) { 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) { - 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(); - }); + should.exist.walletId; + walletId = wId; + done(); + }); + }); + + it('should join existing wallet', function(done) { + 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) { + 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) { var copayerOpts = { - walletId: '234', + walletId: '123', name: 'me', xPubKey: 'dummy', xPubKeySignature: 'dummy', @@ -359,143 +376,77 @@ describe('Copay server', function() { }); it('should fail to join full wallet', function(done) { - var walletOpts = { - name: 'my wallet', - m: 1, - n: 1, - pubKey: keyPair.pub, - }; - server.createWallet(walletOpts, function(err, walletId) { - should.not.exist(err); - var copayer1Opts = { - walletId: walletId, - id: '111', + helpers.createAndJoinWallet(1, 1, function(s, wallet) { + var copayerOpts = { + walletId: wallet.id, name: 'me', - xPubKey: someXPubKeys[0], - xPubKeySignature: someXPubKeysSignatures[0], - }; - var copayer2Opts = { - walletId: walletId, - id: '222', - name: 'me 2', xPubKey: someXPubKeys[1], xPubKeySignature: someXPubKeysSignatures[1], }; - server.joinWallet(copayer1Opts, function(err, copayer1Id) { - should.not.exist(err); - helpers.getAuthServer(copayer1Id, function(server) { - server.getWallet({}, function(err, wallet) { - wallet.status.should.equal('complete'); - server.joinWallet(copayer2Opts, function(err, copayer2Id) { - should.exist(err); - err.code.should.equal('WFULL'); - err.message.should.equal('Wallet full'); - done(); - }); - }); - }); + server.joinWallet(copayerOpts, function(err) { + should.exist(err); + err.code.should.equal('WFULL'); + err.message.should.equal('Wallet full'); + done(); }); }); }); it('should fail to re-join wallet', function(done) { - var walletOpts = { - name: 'my wallet', - m: 1, - n: 1, - pubKey: keyPair.pub, + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: someXPubKeys[0], + xPubKeySignature: someXPubKeysSignatures[0], }; - server.createWallet(walletOpts, function(err, walletId) { + server.joinWallet(copayerOpts, function(err) { should.not.exist(err); - var copayerOpts = { - walletId: walletId, - id: '111', - name: 'me', - xPubKey: someXPubKeys[0], - xPubKeySignature: someXPubKeysSignatures[0], - }; server.joinWallet(copayerOpts, function(err) { - should.not.exist(err); - server.joinWallet(copayerOpts, function(err) { - should.exist(err); - err.code.should.equal('CINWALLET'); - err.message.should.equal('Copayer already in wallet'); - done(); - }); + should.exist(err); + err.code.should.equal('CINWALLET'); + err.message.should.equal('Copayer already in wallet'); + done(); }); }); }); - it('should fail to join with bad formated signature', function(done) { - var walletOpts = { - id: '123', - name: 'my wallet', - m: 1, - n: 1, - pubKey: aPubKey, + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: someXPubKeys[0], + xPubKeySignature: 'bad sign', }; - server.createWallet(walletOpts, function(err, walletId) { - should.not.exist(err); - var copayerOpts = { - walletId: walletId, - name: 'me', - xPubKey: someXPubKeys[0], - xPubKeySignature: 'bad sign', - }; - server.joinWallet(copayerOpts, function(err) { - err.message.should.equal('Bad request'); - done(); - }); + server.joinWallet(copayerOpts, function(err) { + err.message.should.equal('Bad request'); + done(); }); }); - it('should fail to join with null signature', function(done) { - var walletOpts = { - id: '123', - name: 'my wallet', - m: 1, - n: 1, - pubKey: aPubKey, + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: someXPubKeys[0], }; - server.createWallet(walletOpts, function(err) { - should.not.exist(err); - var copayerOpts = { - walletId: '123', - id: '111', - name: 'me', - xPubKey: someXPubKeys[0], - }; - try { - server.joinWallet(copayerOpts, function(err) {}); - } catch (e) { - e.should.contain('xPubKeySignature'); - done(); - } - }); + try { + server.joinWallet(copayerOpts, function(err) {}); + } catch (e) { + e.should.contain('xPubKeySignature'); + done(); + } }); it('should fail to join with wrong signature', function(done) { - var walletOpts = { - id: '123', - name: 'my wallet', - m: 1, - n: 1, - pubKey: aPubKey, + var copayerOpts = { + walletId: walletId, + name: 'me', + xPubKey: someXPubKeys[0], + xPubKeySignature: someXPubKeysSignatures[1], }; - server.createWallet(walletOpts, function(err, walletId) { - should.not.exist(err); - var copayerOpts = { - walletId: walletId, - name: 'me', - xPubKey: someXPubKeys[0], - xPubKeySignature: someXPubKeysSignatures[0], - }; - server.joinWallet(copayerOpts, function(err) { - err.message.should.equal('Bad request'); - done(); - }); + server.joinWallet(copayerOpts, function(err) { + err.message.should.equal('Bad request'); + done(); }); }); From 53fa9fcacefa197d286eef5b5714461fc2450dbc Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 18:29:58 -0300 Subject: [PATCH 08/14] simultaneous tx creation --- lib/server.js | 71 +++++++++++++++++++++++++-------------------- test/integration.js | 50 +++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 43 deletions(-) diff --git a/lib/server.js b/lib/server.js index ae9ff5e..8924178 100644 --- a/lib/server.js +++ b/lib/server.js @@ -408,41 +408,50 @@ CopayServer.prototype.createTx = function(opts, cb) { // Check some parameters like: // amount > dust - self.getWallet({}, function(err, wallet) { - if (err) return cb(err); - if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); - - self._getUtxos(function(err, utxos) { + Utils.runLocked(self.walletId, cb, function(cb) { + self.getWallet({}, function(err, wallet) { if (err) return cb(err); + if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); - var changeAddress = wallet.createAddress(true).address; - - utxos = _.reject(utxos, { - locked: true - }); - - 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')); - } - - txp.inputPaths = _.pluck(txp.inputs, 'path'); - - // no need to do this now: // TODO remove this comment - //self._createRawTx(txp); - self.storage.storeTx(wallet.id, txp, function(err) { + self._getUtxos(function(err, utxos) { 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.storeWallet(wallet, function(err) { + if (err) return cb(err); + + self.storage.storeAddress(wallet.id, 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); + }); + }) + }); }); }); }); diff --git a/test/integration.js b/test/integration.js index 8876cd9..e4df629 100644 --- a/test/integration.js +++ b/test/integration.js @@ -652,14 +652,12 @@ describe('Copay server', function() { }); it('should create a tx', function(done) { - helpers.createUtxos(server, wallet, helpers.toSatoshi([100, 200]), function(utxos) { helpers.stubBlockExplorer(server, utxos); var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; @@ -705,7 +703,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -728,7 +725,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(120), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; @@ -760,7 +756,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -771,7 +766,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 8, message: 'some message 2', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { @@ -799,7 +793,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -810,7 +803,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(24), message: 'some message 2', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { @@ -831,6 +823,43 @@ describe('Copay server', function() { }); }); }); + + it('should create tx using different UTXOs for simultaneous requests', function(done) { + var ntxs = 3; + helpers.createUtxos(server, wallet, helpers.toSatoshi(_.times(3, function() { + return 100; + })), function(utxos) { + helpers.stubBlockExplorer(server, utxos); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(helpers.toSatoshi(ntxs * 100)); + balance.lockedAmount.should.equal(helpers.toSatoshi(0)); + + var txOpts = { + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: helpers.toSatoshi(80), + requestSignature: 'dummy', + }; + async.map(_.range(ntxs), 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(ntxs); + _.uniq(_.pluck(txs, 'changeAddress')).length.should.equal(ntxs); + server.getBalance({}, function(err, balance) { + should.not.exist(err); + balance.totalAmount.should.equal(helpers.toSatoshi(ntxs * 100)); + balance.lockedAmount.should.equal(balance.totalAmount); + done(); + }); + }); + }); + }); + }); + }); }); describe('#signTx', function() { @@ -849,7 +878,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -937,7 +965,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { @@ -969,7 +996,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { @@ -1024,7 +1050,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', - otToken: 'dummy', requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { @@ -1035,6 +1060,7 @@ describe('Copay server', function() { should.not.exist(err); txps.length.should.equal(1); txps[0].id.should.equal(txp.id); + txps[0].message.should.equal('some message'); done(); }); }); From 29e3b5a087702b08b7de85b2cadcb26e18df4b75 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 18:35:19 -0300 Subject: [PATCH 09/14] decreased running time on slow tests --- test/integration.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/integration.js b/test/integration.js index e4df629..a12751e 100644 --- a/test/integration.js +++ b/test/integration.js @@ -568,16 +568,18 @@ describe('Copay server', function() { }); it('should create many addresses on simultaneous requests', function(done) { - async.map(_.range(10), function(i, cb) { + var N = 5; + async.map(_.range(N), function(i, cb) { server.createAddress({ isChange: false, }, cb); }, 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'); + addresses.length.should.equal(N); + _.each(_.range(N), function(i) { + addresses[i].path.should.equal('m/2147483647/0/' + i); + }); // No two identical addresses - _.keys(_.groupBy(addresses, 'address')).length.should.equal(10); + _.uniq(_.pluck(addresses, 'address')).length.should.equal(N); done(); }); }); @@ -825,14 +827,14 @@ describe('Copay server', function() { }); it('should create tx using different UTXOs for simultaneous requests', function(done) { - var ntxs = 3; - helpers.createUtxos(server, wallet, helpers.toSatoshi(_.times(3, function() { + 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(ntxs * 100)); + balance.totalAmount.should.equal(helpers.toSatoshi(N * 100)); balance.lockedAmount.should.equal(helpers.toSatoshi(0)); var txOpts = { @@ -840,18 +842,18 @@ describe('Copay server', function() { amount: helpers.toSatoshi(80), requestSignature: 'dummy', }; - async.map(_.range(ntxs), function(i, cb) { + 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(ntxs); - _.uniq(_.pluck(txs, 'changeAddress')).length.should.equal(ntxs); + 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(ntxs * 100)); + balance.totalAmount.should.equal(helpers.toSatoshi(N * 100)); balance.lockedAmount.should.equal(balance.totalAmount); done(); }); From b9afbaf0de7f956412586862fa69c1382b0ea07f Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 18:45:13 -0300 Subject: [PATCH 10/14] remove isChange arg from #createAddress --- lib/server.js | 6 +--- test/integration.js | 75 +++++++++++---------------------------------- 2 files changed, 19 insertions(+), 62 deletions(-) diff --git a/lib/server.js b/lib/server.js index 8924178..5b1880e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -191,21 +191,17 @@ CopayServer.prototype.joinWallet = function(opts, cb) { /** * Creates a new address. * @param {Object} opts - * @param {truthy} opts.isChange - Indicates whether this is a regular address or a change address. * @returns {Address} address */ CopayServer.prototype.createAddress = function(opts, cb) { var self = this; - var isChange = opts.isChange || false; - - Utils.checkRequired(opts, ['isChange']); Utils.runLocked(self.walletId, cb, function(cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); - var address = wallet.createAddress(opts.isChange); + var address = wallet.createAddress(false); self.storage.storeAddress(wallet.id, address, function(err) { if (err) return cb(err); diff --git a/test/integration.js b/test/integration.js index a12751e..06bc599 100644 --- a/test/integration.js +++ b/test/integration.js @@ -130,9 +130,7 @@ helpers.createUtxos = function(server, wallet, amounts, cb) { var addresses = []; async.each(amounts, function(a, next) { - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { addresses.push(address); next(err); }); @@ -510,10 +508,8 @@ describe('Copay server', function() { }); }); - it('should create main address', function(done) { - server.createAddress({ - isChange: false, - }, function(err, address) { + it('should create address', function(done) { + server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR'); @@ -522,19 +518,6 @@ describe('Copay server', function() { }); }); - - it('should create change address', function(done) { - 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 fail to create address when wallet is not complete', function(done) { var server = new CopayServer(); var walletOpts = { @@ -554,9 +537,7 @@ describe('Copay server', function() { server.joinWallet(copayerOpts, function(err, copayerId) { should.not.exist(err); helpers.getAuthServer(copayerId, function(server) { - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { should.not.exist(address); err.should.exist; err.message.should.contain('not complete'); @@ -570,9 +551,7 @@ describe('Copay server', function() { it('should create many addresses on simultaneous requests', function(done) { var N = 5; async.map(_.range(N), function(i, cb) { - server.createAddress({ - isChange: false, - }, cb); + server.createAddress({}, cb); }, function(err, addresses) { addresses.length.should.equal(N); _.each(_.range(N), function(i) { @@ -588,9 +567,7 @@ describe('Copay server', function() { var storeWalletStub = sinon.stub(server.storage, 'storeWallet'); storeWalletStub.yields('dummy error'); - server.createAddress({ - isChange: true, - }, function(err, address) { + server.createAddress({}, function(err, address) { err.should.exist; should.not.exist(address); @@ -598,13 +575,11 @@ describe('Copay server', function() { addresses.length.should.equal(0); server.storage.storeWallet.restore(); - server.createAddress({ - isChange: true, - }, function(err, address) { + server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; - address.address.should.equal('3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH'); - address.path.should.equal('m/2147483647/1/0'); + address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR'); + address.path.should.equal('m/2147483647/0/0'); done(); }); }); @@ -615,9 +590,7 @@ describe('Copay server', function() { var storeAddressStub = sinon.stub(server.storage, 'storeAddress'); storeAddressStub.yields('dummy error'); - server.createAddress({ - isChange: true, - }, function(err, address) { + server.createAddress({}, function(err, address) { err.should.exist; should.not.exist(address); @@ -625,13 +598,11 @@ describe('Copay server', function() { addresses.length.should.equal(0); server.storage.storeAddress.restore(); - server.createAddress({ - isChange: true, - }, function(err, address) { + server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; - address.address.should.equal('3CauZ5JUFfmSAx2yANvCRoNXccZ3YSUjXH'); - address.path.should.equal('m/2147483647/1/0'); + address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR'); + address.path.should.equal('m/2147483647/0/0'); done(); }); }); @@ -645,9 +616,7 @@ describe('Copay server', function() { helpers.createAndJoinWallet(2, 2, function(s, w) { server = s; wallet = w; - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { done(); }); }); @@ -871,9 +840,7 @@ describe('Copay server', function() { helpers.createAndJoinWallet(2, 2, function(s, w) { server = s; wallet = w; - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(utxos) { helpers.stubBlockExplorer(server, utxos); var txOpts = { @@ -950,9 +917,7 @@ describe('Copay server', function() { helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) { utxos = inutxos; done(); @@ -1035,9 +1000,7 @@ describe('Copay server', function() { helpers.createAndJoinWallet(2, 3, function(s, w) { server = s; wallet = w; - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, helpers.toSatoshi([1, 2, 3, 4, 5, 6, 7, 8]), function(inutxos) { utxos = inutxos; done(); @@ -1091,9 +1054,7 @@ describe('Copay server', function() { helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - server.createAddress({ - isChange: false, - }, function(err, address) { + server.createAddress({}, function(err, address) { helpers.createUtxos(server, wallet, helpers.toSatoshi(_.range(10)), function(utxos) { helpers.stubBlockExplorer(server, utxos); var txOpts = { From 07332a15618b99057a24cdc581154d694137b5d7 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 19:10:06 -0300 Subject: [PATCH 11/14] test valid toAddress on createTx --- lib/server.js | 13 ++++++---- test/integration.js | 58 ++++++++++++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/lib/server.js b/lib/server.js index 5b1880e..5fc8872 100644 --- a/lib/server.js +++ b/lib/server.js @@ -399,16 +399,19 @@ CopayServer.prototype.createTx = function(opts, cb) { Utils.checkRequired(opts, ['toAddress', 'amount']); - - // TODO? - // Check some parameters like: - // amount > dust - Utils.runLocked(self.walletId, cb, function(cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); + var toAddress; + try { + toAddress = new Bitcore.Address(opts.toAddress); + } catch (ex) { + return cb(new ClientError('INVALIDADDRESS', 'Invalid address')); + } + if (toAddress.network != wallet.getNetworkName()) return cb(new ClientError('INVALIDADDRESS', 'Incorrect address network')); + self._getUtxos(function(err, utxos) { if (err) return cb(err); diff --git a/test/integration.js b/test/integration.js index 06bc599..44a8913 100644 --- a/test/integration.js +++ b/test/integration.js @@ -629,12 +629,12 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.should.exist; + tx.message.should.equal('some message'); tx.isAccepted().should.equal.false; tx.isRejected().should.equal.false; server.getPendingTxs({}, function(err, txs) { @@ -673,8 +673,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -687,7 +685,41 @@ describe('Copay server', function() { }); }); - it.skip('should fail to create tx for address of different network', function(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) { helpers.createUtxos(server, wallet, helpers.toSatoshi([100]), function(utxos) { @@ -695,8 +727,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(120), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { @@ -726,8 +756,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -736,8 +764,6 @@ describe('Copay server', function() { var txOpts2 = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 8, - message: 'some message 2', - requestSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { should.not.exist(err); @@ -763,8 +789,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(12), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -773,8 +797,6 @@ describe('Copay server', function() { var txOpts2 = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(24), - message: 'some message 2', - requestSignature: 'dummy', }; server.createTx(txOpts2, function(err, tx) { err.code.should.equal('INSUFFICIENTFUNDS'); @@ -809,7 +831,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(80), - requestSignature: 'dummy', }; async.map(_.range(N), function(i, cb) { server.createTx(txOpts, function(err, tx) { @@ -846,8 +867,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -931,8 +950,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { should.not.exist(err); @@ -962,8 +979,6 @@ describe('Copay server', function() { var txOpts = { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), - message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { should.not.exist(err); @@ -1015,7 +1030,6 @@ describe('Copay server', function() { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: helpers.toSatoshi(10), message: 'some message', - requestSignature: 'dummy', }; server.createTx(txOpts, function(err, txp) { should.not.exist(err); From 254b476fd5ab2242674bda0ed2c49141b0439f44 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 20:36:13 -0300 Subject: [PATCH 12/14] update TODO.txt --- TODO.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index 9600e23..7ec5061 100644 --- a/TODO.txt +++ b/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 not blank & length < 100 for both wallet.name & copayer.name +- Check length < 100 for both wallet.name & copayer.name - 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). - +- Payment protocol - check parameters for KEY at storage From 240b3322b02bd6f9662d446b5517bfd4019eac82 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Sun, 8 Feb 2015 20:46:02 -0300 Subject: [PATCH 13/14] made address and wallet storage atomic --- lib/server.js | 24 ++++++------------------ lib/storage.js | 15 ++++++++++++--- test/integration.js | 31 +++---------------------------- 3 files changed, 21 insertions(+), 49 deletions(-) diff --git a/lib/server.js b/lib/server.js index 5fc8872..60bba4d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -203,18 +203,10 @@ CopayServer.prototype.createAddress = function(opts, cb) { 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); - self.storage.storeWallet(wallet, function(err) { - if (err) { - self.storage.removeAddress(wallet.id, address, function() { - return cb(err); - }); - } else { - return cb(null, address); - } - }); + return cb(null, address); }); }); }); @@ -438,18 +430,14 @@ CopayServer.prototype.createTx = function(opts, cb) { txp.inputPaths = _.pluck(txp.inputs, 'path'); - self.storage.storeWallet(wallet, function(err) { + self.storage.storeAddressAndWallet(wallet, changeAddress, function(err) { if (err) return cb(err); - self.storage.storeAddress(wallet.id, changeAddress, function(err) { + self.storage.storeTx(wallet.id, txp, function(err) { if (err) return cb(err); - self.storage.storeTx(wallet.id, txp, function(err) { - if (err) return cb(err); - - return cb(null, txp); - }); - }) + return cb(null, txp); + }); }); }); }); diff --git a/lib/storage.js b/lib/storage.js index 275d4a6..ac99d15 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -25,7 +25,7 @@ var opKey = function(key) { var MAX_TS = '999999999999'; 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) { - this.db.put(KEY.ADDRESS(walletId, address.address), address, cb); +Storage.prototype.storeAddressAndWallet = function(wallet, 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) { diff --git a/test/integration.js b/test/integration.js index 44a8913..75f430b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -563,10 +563,8 @@ describe('Copay server', function() { }); }); - it('should not create address if unable to store wallet', function(done) { - var storeWalletStub = sinon.stub(server.storage, 'storeWallet'); - storeWalletStub.yields('dummy error'); - + 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) { err.should.exist; should.not.exist(address); @@ -574,30 +572,7 @@ describe('Copay server', function() { server.getAddresses({}, function(err, addresses) { addresses.length.should.equal(0); - server.storage.storeWallet.restore(); - server.createAddress({}, function(err, address) { - should.not.exist(err); - address.should.exist; - address.address.should.equal('36JdLEUDa6UwCfMhhkdZ2VFnDrGUoLedsR'); - address.path.should.equal('m/2147483647/0/0'); - done(); - }); - }); - }); - }); - - it('should not create address if unable to store addresses', function(done) { - var storeAddressStub = sinon.stub(server.storage, 'storeAddress'); - storeAddressStub.yields('dummy error'); - - server.createAddress({}, function(err, address) { - err.should.exist; - should.not.exist(address); - - server.getAddresses({}, function(err, addresses) { - addresses.length.should.equal(0); - - server.storage.storeAddress.restore(); + server.storage.storeAddressAndWallet.restore(); server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; From 8e105d731a18ef41d47d486a2c8d662038b052bf Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 9 Feb 2015 10:16:56 -0300 Subject: [PATCH 14/14] check wallet status using #isComplete() --- test/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/wallet.js b/test/wallet.js index ae0be20..c8b7620 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -12,7 +12,7 @@ describe('Wallet', function() { describe('#fromObj', function() { it('read a wallet', function() { var w = Wallet.fromObj(testWallet); - w.status.should.equal('complete'); + w.isComplete().should.be.true; }); }); describe('#createAddress', function() {