From ee7d3bad7fd00d3449705a642daf7bb84a243adf Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 10 Mar 2016 11:47:16 -0300 Subject: [PATCH] allow absolute fee when specifying inputs --- lib/model/txproposal.js | 6 ++++-- lib/server.js | 27 ++++++++++++++++++--------- test/integration/helpers.js | 1 + test/integration/server.js | 24 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 67a3707..f6dd382 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -33,7 +33,6 @@ TxProposal.create = function(opts) { x.message = opts.message; x.payProUrl = opts.payProUrl; x.changeAddress = opts.changeAddress; - x.setInputs(opts.inputs); x.outputs = _.map(opts.outputs, function(output) { return _.pick(output, ['amount', 'toAddress', 'message']); }); @@ -44,7 +43,6 @@ TxProposal.create = function(opts) { x.requiredRejections = Math.min(x.walletM, x.walletN - x.walletM + 1), x.status = 'temporary'; x.actions = []; - x.fee = null; x.feePerKb = opts.feePerKb; x.excludeUnconfirmedUtxos = opts.excludeUnconfirmedUtxos; @@ -59,6 +57,9 @@ TxProposal.create = function(opts) { } catch (ex) {} $.checkState(_.contains(_.values(Constants.NETWORKS), x.network)); + x.setInputs(opts.inputs); + x.fee = opts.fee; + return x; }; @@ -137,6 +138,7 @@ TxProposal.prototype._buildTx = function() { switch (self.addressType) { case Constants.SCRIPT_TYPES.P2SH: _.each(self.inputs, function(i) { + $.checkState(i.publicKeys, 'Inputs should include public keys'); t.from(i, i.publicKeys, self.requiredSignatures); }); break; diff --git a/lib/server.js b/lib/server.js index e20a1a7..d8026aa 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1270,9 +1270,10 @@ WalletService.prototype._checkTx = function(txp) { WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { var self = this; - //todo: check inputs are ours and has enough value - if (txp.inputs && txp.inputs.length > 0) { - self._estimateFee(txp); + //todo: check inputs are ours and have enough value + if (txp.inputs && !_.isEmpty(txp.inputs)) { + if (!_.isNumber(txp.fee)) + self._estimateFee(txp); return cb(self._checkTx(txp)); } @@ -1725,21 +1726,28 @@ WalletService.prototype.createTxLegacy = function(opts, cb) { * @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.feePerKb - The fee per kB to use for this TX. + * @param {number} opts.feePerKb - The fee per kB to use for this TX. * @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. + * @param {Array} opts.inputs - Optional. Inputs for this TX + * @param {number} opts.fee - Optional. The fee to use for this TX (used only when opts.inputs is specified). * @returns {TxProposal} Transaction proposal. */ WalletService.prototype.createTx = function(opts, cb) { var self = this; - if (!Utils.checkRequired(opts, ['outputs', 'feePerKb'])) + if (!Utils.checkRequired(opts, ['outputs'])) return cb(new ClientError('Required argument missing')); - if (opts.feePerKb < Defaults.MIN_FEE_PER_KB || opts.feePerKb > Defaults.MAX_FEE_PER_KB) - return cb(new ClientError('Invalid fee per KB')); + // feePerKb is required unless inputs & fee are specified + if (!_.isNumber(opts.feePerKb) && !(opts.inputs && _.isNumber(opts.fee))) + return cb(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')); + } self._runLocked(cb, function(cb) { self.getWallet({}, function(err, wallet) { @@ -1760,7 +1768,6 @@ WalletService.prototype.createTx = function(opts, cb) { var txOpts = { walletId: self.walletId, creatorId: self.copayerId, - inputs: opts.inputs, outputs: opts.outputs, message: opts.message, changeAddress: wallet.createAddress(true), @@ -1772,6 +1779,8 @@ WalletService.prototype.createTx = function(opts, cb) { validateOutputs: !opts.validateOutputs, addressType: wallet.addressType, customData: opts.customData, + inputs: opts.inputs, + fee: opts.inputs && !_.isNumber(opts.feePerKb) ? opts.fee : null, }; var txp = Model.TxProposal.create(txOpts); diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 88b9037..e53d38b 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -291,6 +291,7 @@ helpers.stubUtxos = function(server, wallet, amounts, opts, cb) { scriptPubKey: scriptPubKey.toBuffer().toString('hex'), address: address.address, confirmations: parsed.confirmations, + publicKeys: address.publicKeys, }; })); diff --git a/test/integration/server.js b/test/integration/server.js index 6736d92..45e2334 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -3089,6 +3089,30 @@ describe('Wallet service', function() { }); }); }); + + it('should be able to specify inputs & absolute fee', function(done) { + helpers.stubUtxos(server, wallet, [1, 2], function(utxos) { + var txOpts = { + outputs: [{ + toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', + amount: 0.8e8, + }], + inputs: utxos, + fee: 1000e2, + }; + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + should.exist(tx); + tx.amount.should.equal(helpers.toSatoshi(0.8)); + should.not.exist(tx.feePerKb); + tx.fee.should.equal(1000e2); + var t = tx.getBitcoreTx(); + t.getFee().should.equal(1000e2); + t.getChangeOutput().satoshis.should.equal(3e8 - 0.8e8 - 1000e2); + done(); + }); + }); + }); }); describe('Backoff time', function(done) {