From dee4a83ded3e2bc6b5e25ead57f3d564a9bcbf94 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Jun 2015 11:03:11 -0300 Subject: [PATCH 1/5] refactor proposal hash --- lib/model/txproposal.js | 37 +++++++------ lib/server.js | 14 +++-- test/integration/server.js | 36 +++++++++---- test/models/txproposal.js | 104 ++++++++++++++++++++----------------- 4 files changed, 110 insertions(+), 81 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 0ec513f..e25331c 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -18,7 +18,7 @@ TxProposal.Types = { }; TxProposal.isTypeSupported = function(type) { - return !type || _.contains(_.values(TxProposal.Types), type); + return _.contains(_.values(TxProposal.Types), type); }; TxProposal._create = {}; @@ -27,7 +27,9 @@ TxProposal._create.simple = function(txp, opts) { txp.toAddress = opts.toAddress; txp.amount = opts.amount; txp.outputOrder = _.shuffle(_.range(2)); - txp.network = Bitcore.Address(txp.toAddress).toObject().network; + try { + txp.network = Bitcore.Address(txp.toAddress).toObject().network; + } catch (ex) {} }; TxProposal._create.undefined = TxProposal._create.simple; @@ -35,7 +37,9 @@ TxProposal._create.undefined = TxProposal._create.simple; TxProposal._create.multiple_outputs = function(txp, opts) { txp.outputs = opts.outputs; txp.outputOrder = _.shuffle(_.range(txp.outputs.length + 1)); - txp.network = Bitcore.Address(txp.outputs[0].toAddress).toObject().network; + try { + txp.network = Bitcore.Address(txp.outputs[0].toAddress).toObject().network; + } catch (ex) {} }; TxProposal.create = function(opts) { @@ -43,10 +47,8 @@ TxProposal.create = function(opts) { var x = new TxProposal(); - if (opts.version == '1.0.0' && !opts.type) { - opts.type = TxProposal.Types.SIMPLE; - } - x.type = opts.type; + x.type = opts.type || TxProposal.Types.SIMPLE; + var now = Date.now(); x.createdOn = Math.floor(now / 1000); x.id = _.padLeft(now, 14, '0') + Uuid.v4(); @@ -65,7 +67,7 @@ TxProposal.create = function(opts) { x.fee = null; x.feePerKb = opts.feePerKb; - if (typeof TxProposal._create[x.type] == 'function') { + if (_.isFunction(TxProposal._create[x.type])) { TxProposal._create[x.type](x, opts); } @@ -75,10 +77,11 @@ TxProposal.create = function(opts) { TxProposal.fromObj = function(obj) { var x = new TxProposal(); - if (obj.version == '1.0.0' && !obj.type) { - obj.type = TxProposal.Types.SIMPLE; + if (obj.version == '1.0.0') { + x.type = TxProposal.Types.SIMPLE; + } else { + x.type = obj.type; } - x.type = obj.type; x.version = obj.version; x.createdOn = obj.createdOn; x.id = obj.id; @@ -168,13 +171,13 @@ TxProposal.prototype.getRawTx = function() { */ TxProposal.prototype.getHeader = function() { if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) { - return [ { + return [{ outputs: this.outputs, message: this.message, payProUrl: this.payProUrl - } ]; + }]; } else { - return [ this.toAddress, this.amount, this.message, this.payProUrl ]; + return [this.toAddress, this.amount, this.message, this.payProUrl]; } }; @@ -185,8 +188,10 @@ TxProposal.prototype.getHeader = function() { */ TxProposal.prototype.getTotalAmount = function() { if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) { - return _.map(this.outputs, function(o) { return o.amount }) - .reduce(function(total, n) { return total + n; }); + return _.pluck(this.outputs, 'amount') + .reduce(function(total, n) { + return total + n; + }); } else { return this.amount; } diff --git a/lib/server.js b/lib/server.js index a87ac98..bdf7428 100644 --- a/lib/server.js +++ b/lib/server.js @@ -830,6 +830,7 @@ WalletService.prototype.createTx = function(opts, cb) { if (!Utils.checkRequired(opts, ['proposalSignature'])) return cb(new ClientError('Required argument missing')); + opts.type = opts.type || Model.TxProposal.Types.SIMPLE; if (!Model.TxProposal.isTypeSupported(opts.type)) return cb(new ClientError('Invalid proposal type')); @@ -853,7 +854,9 @@ WalletService.prototype.createTx = function(opts, cb) { if (!o.valid) return false; }); if (_.any(opts.outputs, 'valid', false)) return; - _.each(opts.outputs, function(o) { delete o.valid; }); + _.each(opts.outputs, function(o) { + delete o.valid; + }); } else { if (!Utils.checkRequired(opts, ['toAddress', 'amount'])) @@ -877,13 +880,14 @@ WalletService.prototype.createTx = function(opts, cb) { var copayer = wallet.getCopayer(self.copayerId); var proposalHeader = Model.TxProposal.create(opts).getHeader(); - var hash = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); + var hash = WalletUtils.getProposalHash(proposalHeader); if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) return cb(new ClientError('Invalid proposal signature')); - var outputs = (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) - ? opts.outputs - : [ { toAddress: opts.toAddress, amount: opts.amount } ]; + var outputs = (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) ? opts.outputs : [{ + toAddress: opts.toAddress, + amount: opts.amount + }]; _.each(outputs, function(output) { output.valid = false; var toAddress = {}; diff --git a/test/integration/server.js b/test/integration/server.js index 13b45de..4a17ccb 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -193,7 +193,10 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey, fe }; if (feePerKb) opts.feePerKb = feePerKb; - var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); + var txp = Model.TxProposal.create(opts); + var proposalHeader = txp.getHeader(); + var hash = WalletUtils.getProposalHash(proposalHeader); + try { opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); } catch (ex) {} @@ -221,7 +224,7 @@ helpers.createProposalOptsByType = function(type, outputs, message, signingKey, var txp = Model.TxProposal.create(opts); var proposalHeader = txp.getHeader(); - var hash = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); + var hash = WalletUtils.getProposalHash(proposalHeader); try { opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); @@ -1399,8 +1402,8 @@ describe('Wallet service', function() { var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { - should.not.exist(tx); should.exist(err); + should.not.exist(tx); // may fail due to Non-base58 character, or Checksum mismatch, or other done(); }); @@ -1664,10 +1667,15 @@ describe('Wallet service', function() { it('should create tx for type multiple_outputs', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var outputs = [ - { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 75, message: 'message #1' }, - { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 75, message: 'message #2' } - ]; + var outputs = [{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 75, + message: 'message #1' + }, { + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 75, + message: 'message #2' + }]; var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -1679,10 +1687,16 @@ describe('Wallet service', function() { it('should fail to create tx for type multiple_outputs with invalid output argument', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var outputs = [ - { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 80, message: 'message #1', foo: 'bar' }, - { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 90, message: 'message #2' } - ]; + var outputs = [{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 80, + message: 'message #1', + foo: 'bar' + }, { + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 90, + message: 'message #2' + }]; var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); diff --git a/test/models/txproposal.js b/test/models/txproposal.js index 700e402..32f6935 100644 --- a/test/models/txproposal.js +++ b/test/models/txproposal.js @@ -4,21 +4,21 @@ var _ = require('lodash'); var chai = require('chai'); var sinon = require('sinon'); var should = chai.should(); -var TXP = require('../../lib/model/txproposal'); +var TxProposal = require('../../lib/model/txproposal'); var Bitcore = require('bitcore-wallet-utils').Bitcore; var WalletUtils = require('bitcore-wallet-utils'); describe('TXProposal', function() { describe('#create', function() { - it('should create a TXP', function() { - var txp = TXP.create(aTxpOpts()); + it('should create a TxProposal', function() { + var txp = TxProposal.create(aTxpOpts()); should.exist(txp); should.exist(txp.toAddress); should.not.exist(txp.outputs); }); - it('should create a multiple-outputs TXP', function() { - var txp = TXP.create(aTxpOpts(TXP.Types.MULTIPLEOUTPUTS)); + it('should create a multiple-outputs TxProposal', function() { + var txp = TxProposal.create(aTxpOpts(TxProposal.Types.MULTIPLEOUTPUTS)); should.exist(txp); should.not.exist(txp.toAddress); should.exist(txp.outputs); @@ -26,26 +26,26 @@ describe('TXProposal', function() { }); describe('#fromObj', function() { - it('should copy a TXP', function() { - var txp = TXP.fromObj(aTXP()); + it('should copy a TxProposal', function() { + var txp = TxProposal.fromObj(aTXP()); should.exist(txp); txp.toAddress.should.equal(aTXP().toAddress); }); - it('should copy a multiple-outputs TXP', function() { - var txp = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); + it('should copy a multiple-outputs TxProposal', function() { + var txp = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); should.exist(txp); - txp.outputs.should.deep.equal(aTXP(TXP.Types.MULTIPLEOUTPUTS).outputs); + txp.outputs.should.deep.equal(aTXP(TxProposal.Types.MULTIPLEOUTPUTS).outputs); }); }); describe('#getBitcoreTx', function() { it('should create a valid bitcore TX', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); var t = txp.getBitcoreTx(); should.exist(t); }); it('should order outputs as specified by outputOrder', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); txp.outputOrder = [0, 1]; var t = txp.getBitcoreTx(); @@ -56,7 +56,7 @@ describe('TXProposal', function() { t.getChangeOutput().should.deep.equal(t.outputs[0]); }); it('should create a bitcore TX with multiple outputs', function() { - var txp = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); + var txp = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); txp.outputOrder = [0, 1, 2]; var t = txp.getBitcoreTx(); t.getChangeOutput().should.deep.equal(t.outputs[2]); @@ -65,14 +65,14 @@ describe('TXProposal', function() { describe('#getHeader', function() { it('should be compatible with simple proposal legacy header', function() { - var x = TXP.fromObj(aTXP()); + var x = TxProposal.fromObj(aTXP()); var proposalHeader = x.getHeader(); var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); var uH = WalletUtils.getProposalHash(x.toAddress, x.amount, x.message, x.payProUrl); pH.should.equal(uH); }); it('should handle multiple-outputs', function() { - var x = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); + var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); var proposalHeader = x.getHeader(); should.exist(proposalHeader); var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); @@ -82,21 +82,23 @@ describe('TXProposal', function() { describe('#getTotalAmount', function() { it('should be compatible with simple proposal legacy amount', function() { - var x = TXP.fromObj(aTXP()); + var x = TxProposal.fromObj(aTXP()); var total = x.getTotalAmount(); total.should.equal(x.amount); }); it('should handle multiple-outputs', function() { - var x = TXP.fromObj(aTXP(TXP.Types.MULTIPLEOUTPUTS)); + var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); var totalOutput = 0; - _.each(x.outputs, function(o) { totalOutput += o.amount }); + _.each(x.outputs, function(o) { + totalOutput += o.amount + }); x.getTotalAmount().should.equal(totalOutput); }); }); describe('#sign', function() { it('should sign 2-2', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); txp.sign('1', theSignatures, theXPub); txp.isAccepted().should.equal(false); txp.isRejected().should.equal(false); @@ -108,7 +110,7 @@ describe('TXProposal', function() { describe('#getRawTx', function() { it('should generate correct raw transaction for signed 2-2', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); txp.sign('1', theSignatures, theXPub); txp.getRawTx().should.equal('0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e080000009200483045022100896aeb8db75fec22fddb5facf791927a996eb3aee23ee6deaa15471ea46047de02204c0c33f42a9d3ff93d62738712a8c8a5ecd21b45393fdd144e7b01b5a186f1f9014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0280f0fa02000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70c9fa020000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000'); }); @@ -118,7 +120,7 @@ describe('TXProposal', function() { describe('#reject', function() { it('should reject 2-2', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); txp.reject('1'); txp.isAccepted().should.equal(false); txp.isRejected().should.equal(true); @@ -128,7 +130,7 @@ describe('TXProposal', function() { describe('#reject & #sign', function() { it('should finally reject', function() { - var txp = TXP.fromObj(aTXP()); + var txp = TxProposal.fromObj(aTXP()); txp.sign('1', theSignatures); txp.isAccepted().should.equal(false); txp.isRejected().should.equal(false); @@ -151,19 +153,16 @@ var aTxpOpts = function(type) { amount: 50000000, message: 'some message' }; - if (type == TXP.Types.MULTIPLEOUTPUTS) { - opts.outputs = [ - { - toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", - amount: 10000000, - message: "first message" - }, - { - toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", - amount: 20000000, - message: "second message" - }, - ]; + if (type == TxProposal.Types.MULTIPLEOUTPUTS) { + opts.outputs = [{ + toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", + amount: 10000000, + message: "first message" + }, { + toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", + amount: 20000000, + message: "second message" + }, ]; delete opts.toAddress; delete opts.amount; } @@ -171,8 +170,18 @@ var aTxpOpts = function(type) { }; var aTXP = function(type) { + var version; + switch (type) { + case TxProposal.Types.MULTIPLEOUTPUTS: + version = '1.0.1'; + break; + default: + version = '1.0.0'; + break + } + var txp = { - "version": "1.0.0", + "version": version, "type": type, "createdOn": 1423146231, "id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e", @@ -209,19 +218,16 @@ var aTXP = function(type) { "actions": [], "outputOrder": [0, 1], }; - if (type == TXP.Types.MULTIPLEOUTPUTS) { - txp.outputs = [ - { - toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", - amount: 10000000, - message: "first message" - }, - { - toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", - amount: 20000000, - message: "second message" - }, - ]; + if (type == TxProposal.Types.MULTIPLEOUTPUTS) { + txp.outputs = [{ + toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", + amount: 10000000, + message: "first message" + }, { + toAddress: "18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7", + amount: 20000000, + message: "second message" + }, ]; txp.outputOrder = [0, 1, 2]; delete txp.toAddress; delete txp.amount; From 3851428a5752aac7352712d0aea6f4cb47a670f4 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Jun 2015 11:03:19 -0300 Subject: [PATCH 2/5] bwu v0.0.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1881518..764af55 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "dependencies": { "async": "^0.9.0", "bitcore": "^0.12.9", - "bitcore-wallet-utils": "^0.0.17", + "bitcore-wallet-utils": "^0.0.19", "body-parser": "^1.11.0", "coveralls": "^2.11.2", "email-validator": "^1.0.1", From 48e07096075fc416707dfe971a6e3efe1fc50773 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Jun 2015 11:43:47 -0300 Subject: [PATCH 3/5] simplify validations on createTx --- lib/model/txproposal.js | 4 ++- lib/server.js | 74 +++++++++++++++----------------------- test/integration/server.js | 10 +++--- test/models/txproposal.js | 9 +---- 4 files changed, 36 insertions(+), 61 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index e25331c..8935e8c 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -35,7 +35,9 @@ TxProposal._create.simple = function(txp, opts) { TxProposal._create.undefined = TxProposal._create.simple; TxProposal._create.multiple_outputs = function(txp, opts) { - txp.outputs = opts.outputs; + txp.outputs = _.map(opts.outputs, function(output) { + return _.pick(output, ['amount', 'toAddress', 'message']); + }); txp.outputOrder = _.shuffle(_.range(txp.outputs.length + 1)); try { txp.network = Bitcore.Address(txp.outputs[0].toAddress).toObject().network; diff --git a/lib/server.js b/lib/server.js index bdf7428..210e335 100644 --- a/lib/server.js +++ b/lib/server.js @@ -827,41 +827,28 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) { WalletService.prototype.createTx = function(opts, cb) { var self = this; - if (!Utils.checkRequired(opts, ['proposalSignature'])) + if (!opts.outputs) { + opts.outputs = _.pick(opts, ['amount', 'toAddress', 'message']); + } + opts.outputs = [].concat(opts.outputs); + + if (!Utils.checkRequired(opts, ['outputs', 'proposalSignature'])) return cb(new ClientError('Required argument missing')); opts.type = opts.type || Model.TxProposal.Types.SIMPLE; if (!Model.TxProposal.isTypeSupported(opts.type)) return cb(new ClientError('Invalid proposal type')); - if (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) { - if (!Utils.checkRequired(opts, ['outputs'])) - return cb(new ClientError('Required argument missing')); - _.each(opts.outputs, function(o) { - o.valid = true; - if (!Utils.checkRequired(o, ['toAddress', 'amount'])) { - o.valid = false; - cb(new ClientError('Required outputs argument missing')); - return false; - } - _.each(_.keys(o), function(key) { - if (!_.contains(['toAddress', 'amount', 'message', 'valid'], key)) { - o.valid = false; - cb(new ClientError('Invalid outputs argument found')); - return false; - } - }); - if (!o.valid) return false; - }); - if (_.any(opts.outputs, 'valid', false)) return; - _.each(opts.outputs, function(o) { - delete o.valid; - }); - - } else { - if (!Utils.checkRequired(opts, ['toAddress', 'amount'])) - return cb(new ClientError('Required argument missing')); - } + _.each(opts.outputs, function(output) { + if (!Utils.checkRequired(output, ['toAddress', 'amount'])) { + output.valid = false; + cb(new ClientError('Required outputs argument missing')); + return false; + } + }); + if (_.any(opts.outputs, { + valid: false + })) return; var feePerKb = opts.feePerKb || 10000; if (feePerKb < WalletUtils.MIN_FEE_PER_KB || feePerKb > WalletUtils.MAX_FEE_PER_KB) @@ -878,17 +865,7 @@ WalletService.prototype.createTx = function(opts, cb) { if (!canCreate) return cb(new ClientError('NOTALLOWEDTOCREATETX', 'Cannot create TX proposal during backoff time')); - var copayer = wallet.getCopayer(self.copayerId); - var proposalHeader = Model.TxProposal.create(opts).getHeader(); - var hash = WalletUtils.getProposalHash(proposalHeader); - if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) - return cb(new ClientError('Invalid proposal signature')); - - var outputs = (opts.type == Model.TxProposal.Types.MULTIPLEOUTPUTS) ? opts.outputs : [{ - toAddress: opts.toAddress, - amount: opts.amount - }]; - _.each(outputs, function(output) { + _.each(opts.outputs, function(output) { output.valid = false; var toAddress = {}; try { @@ -901,7 +878,7 @@ WalletService.prototype.createTx = function(opts, cb) { cb(new ClientError('INVALIDADDRESS', 'Incorrect address network')); return false; } - if (output.amount <= 0) { + if (!_.isNumber(output.amount) || _.isNaN(output.amount) || output.amount <= 0) { cb(new ClientError('Invalid amount')); return false; } @@ -911,9 +888,9 @@ WalletService.prototype.createTx = function(opts, cb) { } output.valid = true; }); - if (_.any(outputs, 'valid', false)) return; - - var changeAddress = wallet.createAddress(true); + if (_.any(opts.outputs, { + valid: false + })) return; var txp = Model.TxProposal.create({ type: opts.type, @@ -926,18 +903,23 @@ WalletService.prototype.createTx = function(opts, cb) { proposalSignature: opts.proposalSignature, feePerKb: feePerKb, payProUrl: opts.payProUrl, - changeAddress: changeAddress, requiredSignatures: wallet.m, requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), }); + var copayer = wallet.getCopayer(self.copayerId); + var hash = WalletUtils.getProposalHash(txp.getHeader()); + if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) + return cb(new ClientError('Invalid proposal signature')); + + txp.changeAddress = wallet.createAddress(true); self._selectTxInputs(txp, function(err) { if (err) return cb(err); $.checkState(txp.inputs); - self.storage.storeAddressAndWallet(wallet, changeAddress, function(err) { + self.storage.storeAddressAndWallet(wallet, txp.changeAddress, function(err) { if (err) return cb(err); self.storage.storeTx(wallet.id, txp, function(err) { diff --git a/test/integration/server.js b/test/integration/server.js index 4a17ccb..e65d6d0 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -137,7 +137,7 @@ helpers.toSatoshi = function(btc) { helpers.stubUtxos = function(server, wallet, amounts, cb) { var amounts = [].concat(amounts); - async.map(_.range(1, Math.ceil(amounts.length / 2) + 1), function(i, next) { + async.mapSeries(_.range(1, Math.ceil(amounts.length / 2) + 1), function(i, next) { server.createAddress({}, function(err, address) { next(err, address); }); @@ -248,7 +248,7 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var storage, blockchainExplorer; -var useMongo = false; +var useMongo = true; function initStorage(cb) { function getDb(cb) { @@ -1685,13 +1685,11 @@ describe('Wallet service', function() { }); }); - it('should fail to create tx for type multiple_outputs with invalid output argument', function(done) { + it('should fail to create tx for type multiple_outputs with missing output argument', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { var outputs = [{ - toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 80, message: 'message #1', - foo: 'bar' }, { toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: 90, @@ -1700,7 +1698,7 @@ describe('Wallet service', function() { var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); - err.message.should.contain('Invalid outputs argument'); + err.message.should.contain('outputs argument missing'); done(); }); }); diff --git a/test/models/txproposal.js b/test/models/txproposal.js index 32f6935..49e7430 100644 --- a/test/models/txproposal.js +++ b/test/models/txproposal.js @@ -64,18 +64,11 @@ describe('TXProposal', function() { }); describe('#getHeader', function() { - it('should be compatible with simple proposal legacy header', function() { - var x = TxProposal.fromObj(aTXP()); - var proposalHeader = x.getHeader(); - var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); - var uH = WalletUtils.getProposalHash(x.toAddress, x.amount, x.message, x.payProUrl); - pH.should.equal(uH); - }); it('should handle multiple-outputs', function() { var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); var proposalHeader = x.getHeader(); should.exist(proposalHeader); - var pH = WalletUtils.getProposalHash.apply(WalletUtils, proposalHeader); + var pH = WalletUtils.getProposalHash(proposalHeader); should.exist(pH); }); }); From 713984770945a525a7c4d0605b33e743371e8306 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Jun 2015 12:53:53 -0300 Subject: [PATCH 4/5] remove TxProposal#getHeader --- lib/model/txproposal.js | 16 ---- lib/server.js | 34 +++++--- test/integration/server.js | 174 +++++++++++++++++++++---------------- test/models/txproposal.js | 10 --- 4 files changed, 122 insertions(+), 112 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 8935e8c..8fdf320 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -166,22 +166,6 @@ TxProposal.prototype.getRawTx = function() { return t.uncheckedSerialize(); }; -/** - * getHeader - * - * @return {Array} arguments for getProposalHash wallet utility method - */ -TxProposal.prototype.getHeader = function() { - if (this.type == TxProposal.Types.MULTIPLEOUTPUTS) { - return [{ - outputs: this.outputs, - message: this.message, - payProUrl: this.payProUrl - }]; - } else { - return [this.toAddress, this.amount, this.message, this.payProUrl]; - } -}; /** * getTotalAmount diff --git a/lib/server.js b/lib/server.js index 210e335..5c137c5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -828,15 +828,15 @@ WalletService.prototype.createTx = function(opts, cb) { var self = this; if (!opts.outputs) { - opts.outputs = _.pick(opts, ['amount', 'toAddress', 'message']); + opts.outputs = _.pick(opts, ['amount', 'toAddress']); } opts.outputs = [].concat(opts.outputs); if (!Utils.checkRequired(opts, ['outputs', 'proposalSignature'])) return cb(new ClientError('Required argument missing')); - opts.type = opts.type || Model.TxProposal.Types.SIMPLE; - if (!Model.TxProposal.isTypeSupported(opts.type)) + var type = opts.type || Model.TxProposal.Types.SIMPLE; + if (!Model.TxProposal.isTypeSupported(type)) return cb(new ClientError('Invalid proposal type')); _.each(opts.outputs, function(output) { @@ -860,6 +860,24 @@ WalletService.prototype.createTx = function(opts, cb) { if (!wallet.isComplete()) return cb(new ClientError('Wallet is not complete')); + var copayer = wallet.getCopayer(self.copayerId); + var hash; + if (!opts.type) { + hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message, opts.payProUrl); + } else { + var header = { + outputs: _.map(opts.outputs, function(output) { + return _.pick(output, ['toAddress', 'amount', 'message']); + }), + message: opts.message, + payProUrl: opts.payProUrl + }; + hash = WalletUtils.getProposalHash(header) + } + + if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) + return cb(new ClientError('Invalid proposal signature')); + self._canCreateTx(self.copayerId, function(err, canCreate) { if (err) return cb(err); if (!canCreate) @@ -893,7 +911,7 @@ WalletService.prototype.createTx = function(opts, cb) { })) return; var txp = Model.TxProposal.create({ - type: opts.type, + type: type, walletId: self.walletId, creatorId: self.copayerId, outputs: opts.outputs, @@ -901,19 +919,13 @@ WalletService.prototype.createTx = function(opts, cb) { amount: opts.amount, message: opts.message, proposalSignature: opts.proposalSignature, + changeAddress: wallet.createAddress(true), feePerKb: feePerKb, payProUrl: opts.payProUrl, requiredSignatures: wallet.m, requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), }); - var copayer = wallet.getCopayer(self.copayerId); - var hash = WalletUtils.getProposalHash(txp.getHeader()); - if (!self._verifySignature(hash, opts.proposalSignature, copayer.requestPubKey)) - return cb(new ClientError('Invalid proposal signature')); - - txp.changeAddress = wallet.createAddress(true); - self._selectTxInputs(txp, function(err) { if (err) return cb(err); diff --git a/test/integration/server.js b/test/integration/server.js index e65d6d0..7b15344 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -184,7 +184,7 @@ helpers.stubAddressActivity = function(activeAddresses) { helpers.clientSign = WalletUtils.signTxp; -helpers.createProposalOpts = function(toAddress, amount, message, signingKey, feePerKb) { +helpers.createProposalOptsLegacy = function(toAddress, amount, message, signingKey, feePerKb) { var opts = { toAddress: toAddress, amount: helpers.toSatoshi(amount), @@ -193,9 +193,7 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey, fe }; if (feePerKb) opts.feePerKb = feePerKb; - var txp = Model.TxProposal.create(opts); - var proposalHeader = txp.getHeader(); - var hash = WalletUtils.getProposalHash(proposalHeader); + var hash = WalletUtils.getProposalHash(toAddress, opts.amount, message); try { opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); @@ -204,27 +202,41 @@ helpers.createProposalOpts = function(toAddress, amount, message, signingKey, fe return opts; }; -helpers.createProposalOptsByType = function(type, outputs, message, signingKey, feePerKb) { +helpers.createSimpleProposalOpts = function(toAddress, amount, message, signingKey, feePerKb) { + var outputs = [{ + toAddress: toAddress, + amount: amount, + }]; + return helpers.createProposalOpts(Model.TxProposal.Types.SIMPLE, outputs, message, signingKey, feePerKb); +}; + +helpers.createProposalOpts = function(type, outputs, message, signingKey, feePerKb) { + _.each(outputs, function(output) { + output.amount = helpers.toSatoshi(output.amount); + }); var opts = { type: type, message: message, proposalSignature: null, }; - if (type == Model.TxProposal.Types.MULTIPLEOUTPUTS) { - opts.outputs = []; - _.each(outputs, function(o) { - opts.outputs.push(o); - o.amount = helpers.toSatoshi(o.amount); - }); - } else { - opts.toAddress = outputs[0].toAddress; - opts.amount = helpers.toSatoshi(outputs[0].amount); - } if (feePerKb) opts.feePerKb = feePerKb; - var txp = Model.TxProposal.create(opts); - var proposalHeader = txp.getHeader(); - var hash = WalletUtils.getProposalHash(proposalHeader); + switch (type) { + case Model.TxProposal.Types.SIMPLE: + opts.toAddress = outputs[0].toAddress; + opts.amount = outputs[0].amount; + break; + case Model.TxProposal.Types.MULTIPLEOUTPUTS: + opts.outputs = outputs; + break; + } + + var header = { + outputs: outputs, + message: opts.message, + payProUrl: opts.payProUrl + }; + var hash = WalletUtils.getProposalHash(header); try { opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); @@ -338,7 +350,7 @@ describe('Wallet service', function() { it('should notify copayers a new tx proposal has been created', function(done) { helpers.stubUtxos(server, wallet, [1, 1], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); setTimeout(function() { @@ -365,7 +377,7 @@ describe('Wallet service', function() { it('should notify copayers a new outgoing tx has been created', function(done) { helpers.stubUtxos(server, wallet, [1, 1], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); var txpId; async.waterfall([ @@ -418,7 +430,7 @@ describe('Wallet service', function() { it('should notify copayers a tx has been finally rejected', function(done) { helpers.stubUtxos(server, wallet, 1, function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); var txpId; async.waterfall([ @@ -544,7 +556,7 @@ describe('Wallet service', function() { }, }, function(err) { helpers.stubUtxos(server, wallet, 1, function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.8, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); setTimeout(function() { @@ -1278,7 +1290,7 @@ describe('Wallet service', function() { server.joinWallet(copayerOpts, function(err, result) { should.not.exist(err); helpers.getAuthServer(result.copayerId, function(server, wallet) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); should.exist(err); @@ -1303,7 +1315,7 @@ describe('Wallet service', function() { it('should create a tx', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1338,9 +1350,20 @@ describe('Wallet service', function() { }); }); + it('should create a tx with legacy signature', function(done) { + helpers.stubUtxos(server, wallet, [100, 200], function() { + var txOpts = helpers.createProposalOptsLegacy('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + should.exist(tx); + done(); + }); + }); + }); + it('should create a tx using the uxtos with minimum amount first', function(done) { helpers.stubUtxos(server, wallet, [100, 200, 300], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 150, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 150, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1362,7 +1385,7 @@ describe('Wallet service', function() { blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, 'dummy error'); server.createAddress({}, function(err, address) { should.not.exist(err); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('BLOCKCHAINERROR'); @@ -1373,7 +1396,7 @@ describe('Wallet service', function() { it('should fail to create tx with invalid proposal signature', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, 'dummy'); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, 'dummy'); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -1386,7 +1409,7 @@ describe('Wallet service', function() { it('should fail to create tx with proposal signed by another copayer', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[1].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[1].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -1399,7 +1422,7 @@ describe('Wallet service', function() { it('should fail to create tx for invalid address', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('invalid address', 80, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); @@ -1412,7 +1435,7 @@ describe('Wallet service', function() { it('should fail to create tx for address of different network', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('myE38JHdxmQcTJGP1ZiX4BiGhDxMJDvLJD', 80, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); @@ -1425,7 +1448,7 @@ describe('Wallet service', function() { }); it('should fail to create tx for invalid amount', function(done) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(tx); should.exist(err); @@ -1436,7 +1459,7 @@ describe('Wallet service', function() { it('should fail to create tx when insufficient funds', function(done) { helpers.stubUtxos(server, wallet, [100], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 120, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('INSUFFICIENTFUNDS'); @@ -1457,7 +1480,7 @@ describe('Wallet service', function() { it('should fail to create tx when insufficient funds for fee', function(done) { helpers.stubUtxos(server, wallet, 0.048222, function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.048200, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.048200, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('INSUFFICIENTFUNDS'); @@ -1469,7 +1492,7 @@ describe('Wallet service', function() { it('should scale fees according to tx size', function(done) { helpers.stubUtxos(server, wallet, [1, 1, 1, 1], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.getBitcoreTx()._estimateSize().should.be.within(1001, 1999); @@ -1481,11 +1504,11 @@ describe('Wallet service', function() { it('should be possible to use a smaller fee', function(done) { helpers.stubUtxos(server, wallet, 1, function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('INSUFFICIENTFUNDS'); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0, 5000); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0, 5000); server.createTx(txOpts, function(err, tx) { should.not.exist(err); tx.fee.should.equal(5000); @@ -1505,7 +1528,7 @@ describe('Wallet service', function() { it('should fail to create tx for dust amount', function(done) { helpers.stubUtxos(server, wallet, [1], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('DUSTAMOUNT'); @@ -1521,7 +1544,7 @@ describe('Wallet service', function() { var change = 0.00000001; var amount = 1 - fee - change; - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('DUSTAMOUNT'); @@ -1533,14 +1556,14 @@ describe('Wallet service', function() { it('should fail with different error for insufficient funds and locked funds', function(done) { helpers.stubUtxos(server, wallet, [10, 10], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 11, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 11, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); server.getBalance({}, function(err, balance) { should.not.exist(err); balance.totalAmount.should.equal(helpers.toSatoshi(20)); balance.lockedAmount.should.equal(helpers.toSatoshi(20)); - txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0); + txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('LOCKEDFUNDS'); @@ -1557,7 +1580,7 @@ describe('Wallet service', function() { var fee = Bitcore.Transaction.FEE_PER_KB / 1e8; var amount = 1 - fee; - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1576,7 +1599,7 @@ describe('Wallet service', function() { name: 'dummy', message: 'dummy exception' }); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 2, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.message.should.equal('dummy exception'); @@ -1588,11 +1611,11 @@ describe('Wallet service', function() { it('should create tx when there is a pending tx and enough UTXOs', function(done) { helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); - var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0); + var txOpts2 = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 8, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts2, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1613,11 +1636,11 @@ describe('Wallet service', function() { it('should fail to create tx when there is a pending tx and not enough UTXOs', function(done) { helpers.stubUtxos(server, wallet, [10.1, 10.2, 10.3], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 12, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); - var txOpts2 = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey_1H_0); + var txOpts2 = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 24, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts2, function(err, tx) { err.code.should.equal('LOCKEDFUNDS'); should.not.exist(tx); @@ -1643,7 +1666,7 @@ describe('Wallet service', function() { should.not.exist(err); balance.totalAmount.should.equal(helpers.toSatoshi(N * 100)); balance.lockedAmount.should.equal(helpers.toSatoshi(0)); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, TestData.copayers[0].privKey_1H_0); async.map(_.range(N), function(i, cb) { server.createTx(txOpts, function(err, tx) { cb(err, tx); @@ -1676,7 +1699,7 @@ describe('Wallet service', function() { amount: 75, message: 'message #2' }]; - var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createProposalOpts(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1695,7 +1718,7 @@ describe('Wallet service', function() { amount: 90, message: 'message #2' }]; - var txOpts = helpers.createProposalOptsByType(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createProposalOpts(Model.TxProposal.Types.MULTIPLEOUTPUTS, outputs, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.message.should.contain('outputs argument missing'); @@ -1706,7 +1729,7 @@ describe('Wallet service', function() { it('should fail to create tx for unsupported proposal type', function(done) { helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); txOpts.type = 'bogus'; server.createTx(txOpts, function(err, tx) { should.exist(err); @@ -1724,7 +1747,7 @@ describe('Wallet service', function() { balance.lockedAmount.should.equal(0); balance.totalKbToSendMax.should.equal(3); var max = (balance.totalAmount - balance.lockedAmount) - (balance.totalKbToSendMax * 10000); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1741,7 +1764,7 @@ describe('Wallet service', function() { }); it('should be able to send max non-locked amount', function(done) { helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); server.getBalance({}, function(err, balance) { @@ -1750,7 +1773,7 @@ describe('Wallet service', function() { balance.lockedAmount.should.equal(helpers.toSatoshi(4)); balance.totalKbToSendMax.should.equal(2); var max = (balance.totalAmount - balance.lockedAmount) - (balance.totalKbToSendMax * 2000); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0, 2000); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', max / 1e8, null, TestData.copayers[0].privKey_1H_0, 2000); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1786,7 +1809,7 @@ describe('Wallet service', function() { function(next) { async.each(_.range(3), function(i, next) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); server.rejectTx({ @@ -1799,7 +1822,7 @@ describe('Wallet service', function() { }, function(next) { // Allow a 4th tx - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { server.rejectTx({ txProposalId: tx.id, @@ -1809,7 +1832,7 @@ describe('Wallet service', function() { }, function(next) { // Do not allow before backoff time - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.exist(err); err.code.should.equal('NOTALLOWEDTOCREATETX'); @@ -1818,7 +1841,7 @@ describe('Wallet service', function() { }, function(next) { var clock = sinon.useFakeTimers(Date.now() + (WalletService.backoffTimeMinutes + 2) * 60 * 1000, 'Date'); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { clock.restore(); server.rejectTx({ @@ -1830,7 +1853,7 @@ describe('Wallet service', function() { function(next) { // Do not allow a 5th tx before backoff time var clock = sinon.useFakeTimers(Date.now() + (WalletService.backoffTimeMinutes + 2) * 60 * 1000 + 1, 'Date'); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 1, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { clock.restore(); should.exist(err); @@ -1853,7 +1876,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -1943,7 +1966,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, _.range(1, 9), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, null, TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); @@ -2148,7 +2171,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, [10, 10], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { should.not.exist(err); should.exist(txp); @@ -2207,7 +2230,7 @@ describe('Wallet service', function() { it('should fail to brodcast a not yet accepted tx', function(done) { helpers.stubBroadcast('999'); - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some other message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some other message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { should.not.exist(err); should.exist(txp); @@ -2300,7 +2323,7 @@ describe('Wallet service', function() { }); it('other copayers should see pending proposal created by one copayer', function(done) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { should.not.exist(err); should.exist(txp); @@ -2321,7 +2344,7 @@ describe('Wallet service', function() { async.waterfall([ function(next) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { txpId = txp.id; should.not.exist(err); @@ -2409,7 +2432,7 @@ describe('Wallet service', function() { async.waterfall([ function(next) { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { txpId = txp.id; should.not.exist(err); @@ -2491,7 +2514,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, 10, function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 9, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, txp) { should.not.exist(err); should.exist(txp); @@ -2547,11 +2570,12 @@ describe('Wallet service', function() { helpers.createAndJoinWallet(1, 1, function(s, w) { server = s; wallet = w; - helpers.stubUtxos(server, wallet, _.range(10), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey_1H_0); + helpers.stubUtxos(server, wallet, _.range(1, 11), function() { + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, null, TestData.copayers[0].privKey_1H_0); async.eachSeries(_.range(10), function(i, next) { clock.tick(10 * 1000); server.createTx(txOpts, function(err, tx) { + should.not.exist(err); next(); }); }, function(err) { @@ -2572,7 +2596,7 @@ describe('Wallet service', function() { }, function(err, txps) { should.not.exist(err); var times = _.pluck(txps, 'createdOn'); - times.should.deep.equal([90, 80, 70, 60]); + times.should.deep.equal([100, 90, 80, 70, 60]); done(); }); }); @@ -2595,7 +2619,7 @@ describe('Wallet service', function() { }, function(err, txps) { should.not.exist(err); var times = _.pluck(txps, 'createdOn'); - times.should.deep.equal([90, 80, 70, 60]); + times.should.deep.equal([100, 90, 80, 70]); done(); }); }); @@ -2604,7 +2628,7 @@ describe('Wallet service', function() { server.getTxs({}, function(err, txps) { should.not.exist(err); var times = _.pluck(txps, 'createdOn'); - times.should.deep.equal([90, 80, 70, 60, 50, 40, 30, 20, 10]); + times.should.deep.equal([100, 90, 80, 70, 60, 50, 40, 30, 20, 10]); done(); }); }); @@ -2632,7 +2656,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, _.range(4), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey_1H_0); async.eachSeries(_.range(3), function(i, next) { server.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -2844,7 +2868,7 @@ describe('Wallet service', function() { wallet2 = w; helpers.stubUtxos(server2, wallet2, _.range(1, 3), function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, 'some message', TestData.copayers[1].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.1, 'some message', TestData.copayers[1].privKey_1H_0); async.eachSeries(_.range(2), function(i, next) { server2.createTx(txOpts, function(err, tx) { should.not.exist(err); @@ -2910,7 +2934,7 @@ describe('Wallet service', function() { server = s; wallet = w; helpers.stubUtxos(server, wallet, [100, 200], function() { - var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { server.getPendingTxs({}, function(err, txs) { txp = txs[0]; @@ -3222,7 +3246,7 @@ describe('Wallet service', function() { server._normalizeTxHistory = sinon.stub().returnsArg(0); helpers.stubUtxos(server, wallet, [100, 200], function(utxos) { - var txOpts = helpers.createProposalOpts(mainAddresses[0].address, 80, 'some message', TestData.copayers[0].privKey_1H_0); + var txOpts = helpers.createSimpleProposalOpts(mainAddresses[0].address, 80, 'some message', TestData.copayers[0].privKey_1H_0); server.createTx(txOpts, function(err, tx) { should.not.exist(err); should.exist(tx); diff --git a/test/models/txproposal.js b/test/models/txproposal.js index 49e7430..4d46a97 100644 --- a/test/models/txproposal.js +++ b/test/models/txproposal.js @@ -63,16 +63,6 @@ describe('TXProposal', function() { }); }); - describe('#getHeader', function() { - it('should handle multiple-outputs', function() { - var x = TxProposal.fromObj(aTXP(TxProposal.Types.MULTIPLEOUTPUTS)); - var proposalHeader = x.getHeader(); - should.exist(proposalHeader); - var pH = WalletUtils.getProposalHash(proposalHeader); - should.exist(pH); - }); - }); - describe('#getTotalAmount', function() { it('should be compatible with simple proposal legacy amount', function() { var x = TxProposal.fromObj(aTXP()); From 539da089ef344e67608976d17642ac9238873a94 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Jun 2015 12:56:14 -0300 Subject: [PATCH 5/5] v0.0.37 --- package.json | 2 +- test/integration/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 764af55..777f859 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-service", "description": "A service for Mutisig HD Bitcoin Wallets", "author": "BitPay Inc", - "version": "0.0.36", + "version": "0.0.37", "keywords": [ "bitcoin", "copay", diff --git a/test/integration/server.js b/test/integration/server.js index 7b15344..3b93e23 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -260,7 +260,7 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var storage, blockchainExplorer; -var useMongo = true; +var useMongo = false; function initStorage(cb) { function getDb(cb) {