Merge pull request #1109 from eordano/serialize/checks
Better granularity on serialize() checks
This commit is contained in:
commit
a194c6ded1
|
@ -60,6 +60,9 @@ module.exports = [{
|
|||
}, {
|
||||
name: 'NeedMoreInfo',
|
||||
message: '{0}'
|
||||
}, {
|
||||
name: 'MissingSignatures',
|
||||
message: 'Some inputs have not been fully signed'
|
||||
}, {
|
||||
name: 'InvalidIndex',
|
||||
message: 'Invalid index: {0} is not between 0, {1}'
|
||||
|
|
|
@ -108,14 +108,22 @@ Transaction.prototype._getHash = function() {
|
|||
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
||||
* (decoderawtransaction, sendrawtransaction)
|
||||
*
|
||||
* @param {boolean=} unsafe if true, skip testing for fees that are too high
|
||||
* @param {Object|boolean=} unsafe if true, skip all tests. if it's an object,
|
||||
* it's expected to contain a set of flags to skip certain tests:
|
||||
* <ul>
|
||||
* <li><tt>disableAll</tt>: disable all checks</li>
|
||||
* <li><tt>disableSmallFees</tt>: disable checking for fees that are too small</li>
|
||||
* <li><tt>disableLargeFees</tt>: disable checking for fees that are too large</li>
|
||||
* <li><tt>disableNotFullySigned</tt>: disable checking if all inputs are fully signed</li>
|
||||
* <li><tt>disableDustOutputs</tt>: disable checking if there are no outputs that are dust amounts</li>
|
||||
* </ul>
|
||||
* @return {string}
|
||||
*/
|
||||
Transaction.prototype.serialize = function(unsafe) {
|
||||
if (unsafe) {
|
||||
if (true === unsafe || unsafe && unsafe.disableAll) {
|
||||
return this.uncheckedSerialize();
|
||||
} else {
|
||||
return this.checkedSerialize();
|
||||
return this.checkedSerialize(unsafe);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -123,29 +131,60 @@ Transaction.prototype.uncheckedSerialize = Transaction.prototype.toString = func
|
|||
return this.toBuffer().toString('hex');
|
||||
};
|
||||
|
||||
Transaction.prototype.checkedSerialize = function() {
|
||||
var feeError = this._validateFees();
|
||||
/**
|
||||
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
||||
* (decoderawtransaction, sendrawtransaction)
|
||||
*
|
||||
* @param {Object} opts allows to skip certain tests:
|
||||
* <ul>
|
||||
* <li><tt>disableSmallFees</tt>: disable checking for fees that are too small</li>
|
||||
* <li><tt>disableLargeFees</tt>: disable checking for fees that are too large</li>
|
||||
* <li><tt>disableIsFullySigned</tt>: disable checking if all inputs are fully signed</li>
|
||||
* <li><tt>disableDustOutputs</tt>: disable checking if there are no outputs that are dust amounts</li>
|
||||
* </ul>
|
||||
* @return {string}
|
||||
*/
|
||||
Transaction.prototype.checkedSerialize = function(opts) {
|
||||
opts = opts || {};
|
||||
var missingChange = this._missingChange();
|
||||
if (feeError && missingChange) {
|
||||
throw new errors.Transaction.ChangeAddressMissing();
|
||||
var feeIsTooLarge = this._isFeeTooLarge();
|
||||
var feeIsTooSmall = this._isFeeTooSmall();
|
||||
var isFullySigned = this.isFullySigned();
|
||||
var hasDustOutputs = this._hasDustOutputs();
|
||||
|
||||
if (!opts.disableLargeFees && feeIsTooLarge) {
|
||||
if (missingChange) {
|
||||
throw new errors.Transaction.ChangeAddressMissing('Fee is too large and no change address was provided');
|
||||
}
|
||||
throw new errors.Transaction.FeeError(feeIsTooLarge);
|
||||
}
|
||||
if (feeError && !missingChange) {
|
||||
throw new errors.Transaction.FeeError(feeError);
|
||||
if (!opts.disableSmallFees && feeIsTooSmall) {
|
||||
throw new errors.Transaction.FeeError(feeIsTooSmall);
|
||||
}
|
||||
if (this._hasDustOutputs()) {
|
||||
if (!opts.disableDustOutputs && this._hasDustOutputs()) {
|
||||
throw new errors.Transaction.DustOutputs();
|
||||
}
|
||||
if (!opts.disableIsFullySigned && !isFullySigned) {
|
||||
throw new errors.Transaction.MissingSignatures();
|
||||
}
|
||||
return this.uncheckedSerialize();
|
||||
};
|
||||
|
||||
Transaction.FEE_SECURITY_MARGIN = 15;
|
||||
|
||||
Transaction.prototype._validateFees = function() {
|
||||
if (this._getUnspentValue() > Transaction.FEE_SECURITY_MARGIN * this._estimateFee()) {
|
||||
return 'Fee is more than ' + Transaction.FEE_SECURITY_MARGIN + ' times the suggested amount';
|
||||
Transaction.prototype._isFeeTooLarge = function() {
|
||||
var fee = this._getUnspentValue();
|
||||
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;
|
||||
}
|
||||
if (this._getUnspentValue() < this._estimateFee() / Transaction.FEE_SECURITY_MARGIN) {
|
||||
return 'Fee is less than ' + Transaction.FEE_SECURITY_MARGIN + ' times the suggested amount';
|
||||
};
|
||||
|
||||
Transaction.prototype._isFeeTooSmall = function() {
|
||||
var fee = this._getUnspentValue();
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -59,7 +59,8 @@ describe('Transaction', function() {
|
|||
});
|
||||
|
||||
it('serialize to Object roundtrip', function() {
|
||||
new Transaction(testTransaction.toObject()).uncheckedSerialize().should.equal(testTransaction.serialize());
|
||||
new Transaction(testTransaction.toObject()).uncheckedSerialize()
|
||||
.should.equal(testTransaction.uncheckedSerialize());
|
||||
});
|
||||
|
||||
it('constructor returns a shallow copy of another transaction', function() {
|
||||
|
@ -338,6 +339,59 @@ describe('Transaction', function() {
|
|||
return transaction.serialize();
|
||||
}).to.not.throw(errors.Transaction.DustOutputs);
|
||||
});
|
||||
describe('skipping checks', function() {
|
||||
it('can skip the check for too much fee', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith1BTC)
|
||||
.fee(50000000)
|
||||
.change(changeAddress)
|
||||
.sign(privateKey);
|
||||
expect(function() {
|
||||
return transaction.serialize({disableLargeFees: true});
|
||||
}).to.not.throw();
|
||||
expect(function() {
|
||||
return transaction.serialize();
|
||||
}).to.throw();
|
||||
});
|
||||
it('can skip the check for a fee that is too small', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith1BTC)
|
||||
.fee(1)
|
||||
.change(changeAddress)
|
||||
.sign(privateKey);
|
||||
expect(function() {
|
||||
return transaction.serialize({disableSmallFees: true});
|
||||
}).to.not.throw();
|
||||
expect(function() {
|
||||
return transaction.serialize();
|
||||
}).to.throw();
|
||||
});
|
||||
it('can skip the check that prevents dust outputs', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith1BTC)
|
||||
.to(toAddress, 1000)
|
||||
.change(changeAddress)
|
||||
.sign(privateKey);
|
||||
expect(function() {
|
||||
return transaction.serialize({disableDustOutputs: true});
|
||||
}).to.not.throw();
|
||||
expect(function() {
|
||||
return transaction.serialize();
|
||||
}).to.throw();
|
||||
});
|
||||
it('can skip the check that prevents unsigned outputs', function() {
|
||||
var transaction = new Transaction()
|
||||
.from(simpleUtxoWith1BTC)
|
||||
.to(toAddress, 10000)
|
||||
.change(changeAddress);
|
||||
expect(function() {
|
||||
return transaction.serialize({disableIsFullySigned: true});
|
||||
}).to.not.throw();
|
||||
expect(function() {
|
||||
return transaction.serialize();
|
||||
}).to.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('to and from JSON', function() {
|
||||
|
|
Loading…
Reference in New Issue