Better granularity on serialize() checks
This commit is contained in:
parent
0031ae8089
commit
a6df7a175e
@ -60,6 +60,9 @@ module.exports = [{
|
|||||||
}, {
|
}, {
|
||||||
name: 'NeedMoreInfo',
|
name: 'NeedMoreInfo',
|
||||||
message: '{0}'
|
message: '{0}'
|
||||||
|
}, {
|
||||||
|
name: 'MissingSignatures',
|
||||||
|
message: 'Some inputs have not been fully signed'
|
||||||
}, {
|
}, {
|
||||||
name: 'InvalidIndex',
|
name: 'InvalidIndex',
|
||||||
message: 'Invalid index: {0} is not between 0, {1}'
|
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
|
* Retrieve a hexa string that can be used with bitcoind's CLI interface
|
||||||
* (decoderawtransaction, sendrawtransaction)
|
* (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}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
Transaction.prototype.serialize = function(unsafe) {
|
Transaction.prototype.serialize = function(unsafe) {
|
||||||
if (unsafe) {
|
if (true === unsafe || unsafe && unsafe.disableAll) {
|
||||||
return this.uncheckedSerialize();
|
return this.uncheckedSerialize();
|
||||||
} else {
|
} else {
|
||||||
return this.checkedSerialize();
|
return this.checkedSerialize(unsafe);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,29 +131,60 @@ Transaction.prototype.uncheckedSerialize = Transaction.prototype.toString = func
|
|||||||
return this.toBuffer().toString('hex');
|
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} skipOptions 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(skipOptions) {
|
||||||
|
skipOptions = skipOptions || {};
|
||||||
var missingChange = this._missingChange();
|
var missingChange = this._missingChange();
|
||||||
if (feeError && missingChange) {
|
var feeIsTooLarge = this._isFeeTooLarge();
|
||||||
throw new errors.Transaction.ChangeAddressMissing();
|
var feeIsTooSmall = this._isFeeTooSmall();
|
||||||
|
var isFullySigned = this.isFullySigned();
|
||||||
|
var hasDustOutputs = this._hasDustOutputs();
|
||||||
|
|
||||||
|
if (!skipOptions.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) {
|
if (!skipOptions.disableSmallFees && feeIsTooSmall) {
|
||||||
throw new errors.Transaction.FeeError(feeError);
|
throw new errors.Transaction.FeeError(feeIsTooSmall);
|
||||||
}
|
}
|
||||||
if (this._hasDustOutputs()) {
|
if (!skipOptions.disableDustOutputs && this._hasDustOutputs()) {
|
||||||
throw new errors.Transaction.DustOutputs();
|
throw new errors.Transaction.DustOutputs();
|
||||||
}
|
}
|
||||||
|
if (!skipOptions.disableIsFullySigned && !isFullySigned) {
|
||||||
|
throw new errors.Transaction.MissingSignatures();
|
||||||
|
}
|
||||||
return this.uncheckedSerialize();
|
return this.uncheckedSerialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.FEE_SECURITY_MARGIN = 15;
|
Transaction.FEE_SECURITY_MARGIN = 15;
|
||||||
|
|
||||||
Transaction.prototype._validateFees = function() {
|
Transaction.prototype._isFeeTooLarge = function() {
|
||||||
if (this._getUnspentValue() > Transaction.FEE_SECURITY_MARGIN * this._estimateFee()) {
|
var fee = this._getUnspentValue();
|
||||||
return 'Fee is more than ' + Transaction.FEE_SECURITY_MARGIN + ' times the suggested amount';
|
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() {
|
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() {
|
it('constructor returns a shallow copy of another transaction', function() {
|
||||||
@ -338,6 +339,59 @@ describe('Transaction', function() {
|
|||||||
return transaction.serialize();
|
return transaction.serialize();
|
||||||
}).to.not.throw(errors.Transaction.DustOutputs);
|
}).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() {
|
describe('to and from JSON', function() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user