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); }); });