Merge pull request #1109 from eordano/serialize/checks

Better granularity on serialize() checks
This commit is contained in:
Manuel Aráoz 2015-02-24 13:16:41 -03:00
commit a194c6ded1
3 changed files with 112 additions and 16 deletions

View File

@ -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}'

View File

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

View File

@ -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() {