From d23788100e0b1669ec40e2baad1ca9e99081093e Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 25 Feb 2016 14:23:01 -0300 Subject: [PATCH] allow for external use of getSendMaxInfo + refactor createTx --- lib/server.js | 138 ++++++++++++++++++++----------------- test/integration/server.js | 7 ++ 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/lib/server.js b/lib/server.js index 862c72f..e01e039 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1153,6 +1153,7 @@ WalletService.prototype.getBalance = function(opts, cb) { * @param {Object} opts * @param {string} opts.feePerKb - The fee per KB used to compute the TX. * @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs + * @param {string} opts.returnInputs[=false] - Optional. Return the list of UTXOs that would be included in the tx. * @returns {Object} sendMaxInfo */ WalletService.prototype.getSendMaxInfo = function(opts, cb) { @@ -1209,7 +1210,7 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) { info.size = txp.getEstimatedSize(); info.fee = txp.getEstimatedFee(); info.amount = _.sum(txp.inputs, 'satoshis') - info.fee; - info.inputs = txp.inputs; + if (opts.returnInputs) info.inputs = txp.inputs; return cb(null, info); }); @@ -1785,20 +1786,78 @@ WalletService.prototype.createTxLegacy = function(opts, cb) { }); }; -WalletService.prototype._doCreateTx = function(opts, cb) { +WalletService.prototype._validateAndSanitizeTxOpts = function(wallet, opts, cb) { var self = this; - if (!Utils.checkRequired(opts, ['outputs'])) - return cb(new ClientError('Required argument missing')); + async.series([ - // feePerKb is required unless inputs & fee are specified - if (!_.isNumber(opts.feePerKb) && !(opts.inputs && _.isNumber(opts.fee))) - return cb(new ClientError('Required argument missing')); + function(next) { + if (!Utils.checkRequired(opts, ['outputs'])) + return next(new ClientError('Required argument missing')); + next(); + }, + function(next) { + // feePerKb is required unless inputs & fee are specified + if (!_.isNumber(opts.feePerKb) && !(opts.inputs && _.isNumber(opts.fee))) + return next(new ClientError('Required argument missing')); - if (_.isNumber(opts.feePerKb)) { - if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB) - return cb(new ClientError('Invalid fee per KB')); - } + if (_.isNumber(opts.feePerKb)) { + if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB) + return next(new ClientError('Invalid fee per KB')); + } + next(); + }, + function(next) { + if (!opts.sendMax) return next(); + if (!_.isArray(opts.outputs) || opts.outputs.length > 1) { + return next(new ClientError('Only one output allowed when sendMax is specified')); + } + if (_.isNumber(opts.outputs[0].amount)) + return next(new ClientError('Amount is not allowed when sendMax is specified')); + if (_.isNumber(opts.fee)) + return next(new ClientError('Fee is not allowed when sendMax is specified (use feePerKb instead)')); + + self.getSendMaxInfo({ + feePerKb: opts.feePerKb || Defaults.DEFAULT_FEE_PER_KB, + excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos, + returnInputs: true, + }, function(err, info) { + if (err) return next(err); + opts.outputs[0].amount = info.amount; + opts.inputs = info.inputs; + return next(); + }); + }, + function(next) { + if (opts.validateOutputs === false) return next(); + var validationError = self._validateOutputs(opts, wallet); + if (validationError) { + return next(validationError); + } + next(); + }, + ], cb); +}; + +/** + * Creates a new transaction proposal. + * @param {Object} opts + * @param {Array} opts.outputs - List of outputs. + * @param {string} opts.outputs[].toAddress - Destination address. + * @param {number} opts.outputs[].amount - Amount to transfer in satoshi. + * @param {string} opts.outputs[].message - A message to attach to this output. + * @param {string} opts.message - A message to attach to this transaction. + * @param {Array} opts.inputs - Optional. Inputs for this TX + * @param {string} opts.fee - Optional. Use an alternative fee for this TX (mutually exclusive with feePerKb) + * @param {string} opts.feePerKb - Optional. Use an alternative fee per KB for this TX (mutually exclusive with fee) + * @param {string} opts.sendMax - Optional. Send maximum amount of funds that make sense under the specified fee/feePerKb conditions. (defaults to false). + * @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX + * @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs + * @param {string} opts.validateOutputs[=true] - Optional. Perform validation on outputs. + * @returns {TxProposal} Transaction proposal. + */ +WalletService.prototype.createTx = function(opts, cb) { + var self = this; self._runLocked(cb, function(cb) { @@ -1813,17 +1872,13 @@ WalletService.prototype._doCreateTx = function(opts, cb) { next(); }); }, + function(next) { + self._validateAndSanitizeTxOpts(wallet, opts, next); + }, function(next) { self._canCreateTx(function(err, canCreate) { if (err) return next(err); if (!canCreate) return next(Errors.TX_CANNOT_CREATE); - - if (opts.validateOutputs !== false) { - var validationError = self._validateOutputs(opts, wallet); - if (validationError) { - return next(validationError); - } - } next(); }); }, @@ -1869,53 +1924,6 @@ WalletService.prototype._doCreateTx = function(opts, cb) { }); }; -/** - * Creates a new transaction proposal. - * @param {Object} opts - * @param {Array} opts.outputs - List of outputs. - * @param {string} opts.outputs[].toAddress - Destination address. - * @param {number} opts.outputs[].amount - Amount to transfer in satoshi. - * @param {string} opts.outputs[].message - A message to attach to this output. - * @param {string} opts.message - A message to attach to this transaction. - * @param {Array} opts.inputs - Optional. Inputs for this TX - * @param {string} opts.fee - Optional. Use an alternative fee for this TX (mutually exclusive with feePerKb) - * @param {string} opts.feePerKb - Optional. Use an alternative fee per KB for this TX (mutually exclusive with fee) - * @param {string} opts.sendMax - Optional. Send maximum amount of funds that make sense under the specified fee/feePerKb conditions. (defaults to false). - * @param {string} opts.payProUrl - Optional. Paypro URL for peers to verify TX - * @param {string} opts.excludeUnconfirmedUtxos[=false] - Optional. Do not use UTXOs of unconfirmed transactions as inputs - * @param {string} opts.validateOutputs[=true] - Optional. Perform validation on outputs. - * @returns {TxProposal} Transaction proposal. - */ -WalletService.prototype.createTx = function(opts, cb) { - var self = this; - - opts = opts || []; - async.series([ - - function(next) { - if (!opts.sendMax) return next(); - if (!_.isArray(opts.outputs) || opts.outputs.length > 1) { - return next(new ClientError('Only one output allowed when sendMax is specified')); - } - if (_.isNumber(opts.outputs[0].amount)) - return next(new ClientError('Amount is not allowed when sendMax is specified')); - if (_.isNumber(opts.fee)) - return next(new ClientError('Fee is not allowed when sendMax is specified (use feePerKb instead)')); - - self.getSendMaxInfo(opts, function(err, info) { - if (err) return next(err); - opts.outputs[0].amount = info.amount; - opts.inputs = info.inputs; - return next(); - }); - }, - ], function(err) { - if (err) return cb(err); - self._doCreateTx(opts, cb); - }); - -}; - WalletService.prototype._verifyRequestPubKey = function(requestPubKey, signature, xPubKey) { var pub = (new Bitcore.HDPublicKey(xPubKey)).derive(Constants.PATHS.REQUEST_KEY_AUTH).publicKey; return Utils.verifyMessage(requestPubKey, signature, pub.toString()); diff --git a/test/integration/server.js b/test/integration/server.js index dfa68ac..f6f1497 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -3653,6 +3653,7 @@ describe('Wallet service', function() { it('should be able to get send max info on empty wallet', function(done) { server.getSendMaxInfo({ feePerKb: 10000, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3667,6 +3668,7 @@ describe('Wallet service', function() { helpers.stubUtxos(server, wallet, [0.1, 0.2, 0.3, 0.4], function() { server.getSendMaxInfo({ feePerKb: 10000, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3683,6 +3685,7 @@ describe('Wallet service', function() { server.getSendMaxInfo({ feePerKb: 10000, excludeUnconfirmedUtxos: true, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3707,6 +3710,7 @@ describe('Wallet service', function() { server.getSendMaxInfo({ feePerKb: 10000, excludeUnconfirmedUtxos: true, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3723,6 +3727,7 @@ describe('Wallet service', function() { helpers.stubUtxos(server, wallet, ['u0.1', 0.2, 0.3, 0.4, 0.000001, 0.0002, 0.0003], function() { server.getSendMaxInfo({ feePerKb: 0.001e8, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3732,6 +3737,7 @@ describe('Wallet service', function() { info.amount.should.equal(1e8 - info.fee); server.getSendMaxInfo({ feePerKb: 0.0001e8, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info); @@ -3750,6 +3756,7 @@ describe('Wallet service', function() { helpers.stubUtxos(server, wallet, _.range(1, 10, 0), function() { server.getSendMaxInfo({ feePerKb: 10000, + returnInputs: true, }, function(err, info) { should.not.exist(err); should.exist(info);