From 16dc489b082680842b74621277fa243c3e9806df Mon Sep 17 00:00:00 2001 From: David de Kloet Date: Sat, 9 May 2015 18:44:26 +0200 Subject: [PATCH] Make sure a specified transaction fee and outputs add up to the sum of the inputs. Don't ignore the fee when it's explicitly specified. --- lib/transaction/transaction.js | 28 +++++++++++++++++++++++----- test/transaction/transaction.js | 10 ++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 8749355..145f8b0 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -196,11 +196,15 @@ Transaction.prototype.getSerializationError = function(opts) { return new errors.Transaction.InvalidSatoshis(); } + var feeIsDifferent = this._isFeeDifferent(); var missingChange = this._missingChange(); var feeIsTooLarge = this._isFeeTooLarge(); var feeIsTooSmall = this._isFeeTooSmall(); var isFullySigned = this.isFullySigned(); + if (!opts.disableDifferentFees && feeIsDifferent) { + return new errors.Transaction.FeeError(feeIsDifferent); + } if (!opts.disableLargeFees && feeIsTooLarge) { if (missingChange) { return new errors.Transaction.ChangeAddressMissing('Fee is too large and no change address was provided'); @@ -221,8 +225,17 @@ Transaction.prototype.getSerializationError = function(opts) { } }; +Transaction.prototype._isFeeDifferent = function() { + var fee = this.getFee(); + var unspent = this._getUnspentValue(); + if (fee !== unspent) { + return 'Unspent value ' + unspent + ' is different from specified fee ' + + fee + ' and no change address is specified'; + } +}; + Transaction.prototype._isFeeTooLarge = function() { - var fee = this._getUnspentValue(); + var fee = this.getFee(); var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee()); if (fee > maximumFee) { return 'Fee is too large: expected less than ' + maximumFee + ' but got ' + fee; @@ -230,7 +243,7 @@ Transaction.prototype._isFeeTooLarge = function() { }; Transaction.prototype._isFeeTooSmall = function() { - var fee = this._getUnspentValue(); + var fee = this.getFee(); var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN); if (fee < minimumFee) { return 'Fee is too small: expected more than ' + minimumFee + ' but got ' + fee; @@ -766,6 +779,8 @@ Transaction.prototype._updateChangeOutput = function() { /** * Calculates the fee of the transaction. * + * If there's a fixed fee set, return that. + * * If there is no change output set, the fee is the * total value of the outputs minus inputs. Note that * a serialized transaction only specifies the value @@ -774,17 +789,20 @@ Transaction.prototype._updateChangeOutput = function() { * This method therefore raises a "MissingPreviousOutput" * error when called on a serialized transaction. * - * If there's a fixed fee set, return that. - * If there's no fee set, estimate it based on size. + * If there's no fee set and no change address, + * estimate the fee based on size. * * @return {Number} fee of this transaction in satoshis */ Transaction.prototype.getFee = function() { + if (!_.isUndefined(this._fee)) { + return this._fee; + } // if no change output is set, fees should equal all the unspent amount if (!this._changeScript) { return this._getUnspentValue(); } - return _.isUndefined(this._fee) ? this._estimateFee() : this._fee; + return this._estimateFee(); }; /** diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 7841f8a..f3ffbb3 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -364,6 +364,16 @@ describe('Transaction', function() { return transaction.serialize(); }).to.not.throw(errors.Transaction.DustOutputs); }); + it('fails when outputs and fee don\'t add to total input', function() { + var transaction = new Transaction() + .from(simpleUtxoWith1BTC) + .to(toAddress, 99900000) + .fee(99999) + .sign(privateKey); + expect(function() { + return transaction.serialize(); + }).to.throw(errors.Transaction.FeeError); + }); describe('skipping checks', function() { var buildSkipTest = function(builder, check) { return function() {