diff --git a/lib/client/api.js b/lib/client/api.js index 3530582..f2674f6 100644 --- a/lib/client/api.js +++ b/lib/client/api.js @@ -44,6 +44,7 @@ function _parseError(body) { var code = body.code || 'ERROR'; var message = body.error || 'There was an unknown error processing the request'; log.error(code, message); + return {message: message, code: code}; }; function _signRequest(method, url, args, privKey) { @@ -81,7 +82,8 @@ API.prototype._tryToComplete = function(data, cb) { return cb('Wallet Incomplete'); if (!Verifier.checkCopayers(wallet.copayers, data.walletPrivKey, data.xPrivKey, data.n)) - return cb('Some copayers in the wallet could not be verified to have known the wallet secret'); + return cb(new ServerCompromisedError( + 'Copayers in the wallet could not be verified to have known the wallet secret')); data.publicKeyRing = _.pluck(wallet.copayers, 'xPubKey') @@ -149,9 +151,9 @@ API.prototype._doRequest = function(method, url, args, data, cb) { depth: 10 })); if (err) return cb(err); + if (res.statusCode != 200) { - _parseError(body); - return cb('Request error'); + return cb(_parseError(body)); } return cb(null, body); diff --git a/lib/server.js b/lib/server.js index 486e762..3a8822c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -358,7 +358,8 @@ CopayServer.prototype._getUtxos = function(cb) { bc.getUnspentUtxos(addressStrs, function(err, inutxos) { if (err) return cb(err); var utxos = _.map(inutxos, function(i) { - return i.toObject(); + var r = i.toObject(); + return _.pick(r,['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']); }); self.getPendingTxs({}, function(err, txps) { if (err) return cb(err); diff --git a/lib/walletutils.js b/lib/walletutils.js index 78f4c71..4ab8391 100644 --- a/lib/walletutils.js +++ b/lib/walletutils.js @@ -113,8 +113,8 @@ WalletUtils.UNITS = { }; WalletUtils.parseAmount = function(text) { - if (!_.isString(text)) - text = text.toString(); + if (!_.isString(text)) + text = text.toString(); var regex = '^(\\d*(\\.\\d{0,8})?)\\s*(' + _.keys(WalletUtils.UNITS).join('|') + ')?$'; var match = new RegExp(regex, 'i').exec(text.trim()); diff --git a/test/integration/clientApi.js b/test/integration/clientApi.js index 16c0288..1d4b53e 100644 --- a/test/integration/clientApi.js +++ b/test/integration/clientApi.js @@ -91,10 +91,11 @@ blockExplorerMock.utxos = []; blockExplorerMock.getUnspentUtxos = function(dummy, cb) { var ret = _.map(blockExplorerMock.utxos || [], function(x) { - x.toObject = function() { + var y = _.clone(x); + y.toObject = function() { return this; }; - return x; + return y; }); return cb(null, ret); }; @@ -184,7 +185,7 @@ describe('client API ', function() { should.not.exist(err); should.exist(w.secret); clients[4].joinWallet(w.secret, 'copayer', function(err, result) { - err.should.contain('Request error'); + err.code.should.contain('WFULL'); done(); }); }); @@ -192,7 +193,7 @@ describe('client API ', function() { it('should fail with a unknown secret', function(done) { var oldSecret = '3f8e5acb-ceeb-4aae-134f-692d934e3b1c:L2gohj8s2fLKqVU5cQutAVGciutUxczFxLxxXHFsjzLh71ZjkFQQ:T'; clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { - err.should.contain('Request error'); + err.code.should.contain('BADREQUEST'); done(); }); }); @@ -212,7 +213,7 @@ describe('client API ', function() { clients[1]._doGetRequest = sinon.stub().yields(null, x); clients[1].getBalance(function(err, x) { - err.should.contain('verified'); + err.code.should.contain('SERVERCOMPROMISED'); done(); }); }); @@ -236,7 +237,7 @@ describe('client API ', function() { clients[1]._doGetRequest = sinon.stub().yields(null, x); clients[1].getBalance(function(err, x) { - err.should.contain('verified'); + err.code.should.contain('SERVERCOMPROMISED'); done(); }); }); @@ -264,7 +265,7 @@ describe('client API ', function() { clients[1]._doGetRequest = sinon.stub().yields(null, x); clients[1].getBalance(function(err, x) { - err.should.contain('verified'); + err.code.should.contain('SERVERCOMPROMISED'); done(); }); }); @@ -361,7 +362,7 @@ describe('client API ', function() { }); - describe('Send Transactions', function() { + describe('Transactions Signatures and Rejection', function() { it('Send and broadcast in 1-1 wallet', function(done) { helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { clients[0].createAddress(function(err, x0) { @@ -489,9 +490,77 @@ describe('client API ', function() { }); }); - + it('Should not allow to reject or sign twice', function(done) { + helpers.createAndJoinWallet(clients, 2, 3, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 10, 1); + var opts = { + amount: 10000, + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + x.status.should.equal('pending'); + x.requiredRejections.should.equal(2); + x.requiredSignatures.should.equal(2); + clients[0].signTxProposal(x, function(err, tx) { + should.not.exist(err, err); + tx.status.should.equal('pending'); + clients[0].signTxProposal(x, function(err, tx) { + err.code.should.contain('CVOTED'); + clients[1].rejectTxProposal(x, 'xx', function(err, tx) { + should.not.exist(err); + clients[1].rejectTxProposal(x, 'xx', function(err, tx) { + err.code.should.contain('CVOTED'); + done(); + }); + }); + }); + }); + }); + }); + }); + }); }); + describe('Transaction Troposals and Locked funds', function() { + it('Should lock and release funds', function(done) { + helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { + clients[0].createAddress(function(err, x0) { + should.not.exist(err); + should.exist(x0.address); + blockExplorerMock.setUtxo(x0, 1, 2); + blockExplorerMock.setUtxo(x0, 1, 2); + var opts = { + amount: '1.2btc', + toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', + message: 'hola 1-1', + }; + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + + clients[0].sendTxProposal(opts, function(err, y) { + err.code.should.contain('INSUFFICIENTFUNDS'); + + clients[0].rejectTxProposal(x, 'no', function(err, z) { + should.not.exist(err); + z.status.should.equal('rejected'); + clients[0].sendTxProposal(opts, function(err, x) { + should.not.exist(err); + done(); + }); + }); + }); + }); + }); + }); + }); + }); + + /* describe('TODO', function(x) {