Merge pull request #1248 from braydonf/bug/misleading-error
Fixes bug with misleading error with getSerializationError. Closes #1236
This commit is contained in:
commit
4c1ba674c5
|
@ -191,57 +191,54 @@ Transaction.prototype.invalidSatoshis = function() {
|
||||||
Transaction.prototype.getSerializationError = function(opts) {
|
Transaction.prototype.getSerializationError = function(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
return this._isInvalidSatoshis() ||
|
|
||||||
this._hasFeeError(opts) ||
|
|
||||||
this._hasDustOutputs(opts) ||
|
|
||||||
this._isMissingSignatures(opts) ||
|
|
||||||
this._hasMoreOutputThanInput(opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype._isInvalidSatoshis = function() {
|
|
||||||
if (this.invalidSatoshis()) {
|
if (this.invalidSatoshis()) {
|
||||||
return new errors.Transaction.InvalidSatoshis();
|
return new errors.Transaction.InvalidSatoshis();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype._hasFeeError = function(opts) {
|
|
||||||
return this._isFeeDifferent() ||
|
|
||||||
this._isFeeTooLarge(opts) ||
|
|
||||||
this._isFeeTooSmall(opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype._isFeeDifferent = function() {
|
|
||||||
if (!_.isUndefined(this._fee)) {
|
|
||||||
var fee = this._fee;
|
|
||||||
var unspent = this._getUnspentValue();
|
var unspent = this._getUnspentValue();
|
||||||
if (fee !== unspent) {
|
var unspentError;
|
||||||
return new errors.Transaction.FeeError.Different('Unspent value is ' + unspent + ' but specified fee is ' + fee);
|
if (unspent < 0) {
|
||||||
|
if (!opts.disableMoreOutputThanInput) {
|
||||||
|
unspentError = new errors.Transaction.InvalidOutputAmountSum();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unspentError = this._hasFeeError(opts, unspent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return unspentError ||
|
||||||
|
this._hasDustOutputs(opts) ||
|
||||||
|
this._isMissingSignatures(opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._isFeeTooLarge = function(opts) {
|
Transaction.prototype._hasFeeError = function(opts, unspent) {
|
||||||
if (opts.disableLargeFees) {
|
|
||||||
return;
|
if (!_.isUndefined(this._fee) && this._fee !== unspent) {
|
||||||
|
return new errors.Transaction.FeeError.Different(
|
||||||
|
'Unspent value is ' + unspent + ' but specified fee is ' + this._fee
|
||||||
|
);
|
||||||
}
|
}
|
||||||
var fee = this._getUnspentValue();
|
|
||||||
|
if (!opts.disableLargeFees) {
|
||||||
var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee());
|
var maximumFee = Math.floor(Transaction.FEE_SECURITY_MARGIN * this._estimateFee());
|
||||||
if (fee > maximumFee) {
|
if (unspent > maximumFee) {
|
||||||
if (this._missingChange()) {
|
if (this._missingChange()) {
|
||||||
return new errors.Transaction.ChangeAddressMissing('Fee is too large and no change address was provided');
|
return new errors.Transaction.ChangeAddressMissing(
|
||||||
|
'Fee is too large and no change address was provided'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new errors.Transaction.FeeError.TooLarge(
|
||||||
|
'expected less than ' + maximumFee + ' but got ' + unspent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return new errors.Transaction.FeeError.TooLarge('expected less than ' + maximumFee + ' but got ' + fee);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype._isFeeTooSmall = function(opts) {
|
if (!opts.disableSmallFees) {
|
||||||
if (opts.disableSmallFees) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fee = this._getUnspentValue();
|
|
||||||
var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN);
|
var minimumFee = Math.ceil(this._estimateFee() / Transaction.FEE_SECURITY_MARGIN);
|
||||||
if (fee < minimumFee) {
|
if (unspent < minimumFee) {
|
||||||
return new errors.Transaction.FeeError.TooSmall('expected more than ' + minimumFee + ' but got ' + fee);
|
return new errors.Transaction.FeeError.TooSmall(
|
||||||
|
'expected more than ' + minimumFee + ' but got ' + unspent
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -271,15 +268,6 @@ Transaction.prototype._isMissingSignatures = function(opts) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype._hasMoreOutputThanInput = function(opts) {
|
|
||||||
if (opts.disableMoreOutputThanInput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._getUnspentValue() < 0) {
|
|
||||||
return new errors.Transaction.InvalidOutputAmountSum();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Transaction.prototype.inspect = function() {
|
Transaction.prototype.inspect = function() {
|
||||||
return '<Transaction: ' + this.uncheckedSerialize() + '>';
|
return '<Transaction: ' + this.uncheckedSerialize() + '>';
|
||||||
};
|
};
|
||||||
|
|
|
@ -385,8 +385,32 @@ describe('Transaction', function() {
|
||||||
return transaction.serialize();
|
return transaction.serialize();
|
||||||
}).to.throw(errors.Transaction.FeeError.Different);
|
}).to.throw(errors.Transaction.FeeError.Different);
|
||||||
});
|
});
|
||||||
|
it('checks output amount before fee errors', function() {
|
||||||
|
var transaction = new Transaction();
|
||||||
|
transaction.from(simpleUtxoWith1BTC);
|
||||||
|
transaction
|
||||||
|
.to(toAddress, 10000000000000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.fee(5);
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
return transaction.serialize();
|
||||||
|
}).to.throw(errors.Transaction.InvalidOutputAmountSum);
|
||||||
|
});
|
||||||
|
it('will throw fee error with disableMoreOutputThanInput enabled (but not triggered)', function() {
|
||||||
|
var transaction = new Transaction();
|
||||||
|
transaction.from(simpleUtxoWith1BTC);
|
||||||
|
transaction
|
||||||
|
.to(toAddress, 90000000)
|
||||||
|
.change(changeAddress)
|
||||||
|
.fee(10000000);
|
||||||
|
|
||||||
|
expect(function() {
|
||||||
|
return transaction.serialize({disableMoreOutputThanInput: true});
|
||||||
|
}).to.throw(errors.Transaction.FeeError.TooLarge);
|
||||||
|
});
|
||||||
describe('skipping checks', function() {
|
describe('skipping checks', function() {
|
||||||
var buildSkipTest = function(builder, check) {
|
var buildSkipTest = function(builder, check, expectedError) {
|
||||||
return function() {
|
return function() {
|
||||||
var transaction = new Transaction();
|
var transaction = new Transaction();
|
||||||
transaction.from(simpleUtxoWith1BTC);
|
transaction.from(simpleUtxoWith1BTC);
|
||||||
|
@ -400,7 +424,7 @@ describe('Transaction', function() {
|
||||||
}).not.to.throw();
|
}).not.to.throw();
|
||||||
expect(function() {
|
expect(function() {
|
||||||
return transaction.serialize();
|
return transaction.serialize();
|
||||||
}).to.throw();
|
}).to.throw(expectedError);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
it('can skip the check for too much fee', buildSkipTest(
|
it('can skip the check for too much fee', buildSkipTest(
|
||||||
|
@ -409,54 +433,39 @@ describe('Transaction', function() {
|
||||||
.fee(50000000)
|
.fee(50000000)
|
||||||
.change(changeAddress)
|
.change(changeAddress)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
}, 'disableLargeFees'));
|
}, 'disableLargeFees', errors.Transaction.FeeError.TooLarge
|
||||||
|
));
|
||||||
it('can skip the check for a fee that is too small', buildSkipTest(
|
it('can skip the check for a fee that is too small', buildSkipTest(
|
||||||
function(transaction) {
|
function(transaction) {
|
||||||
return transaction
|
return transaction
|
||||||
.fee(1)
|
.fee(1)
|
||||||
.change(changeAddress)
|
.change(changeAddress)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
}, 'disableSmallFees'));
|
}, 'disableSmallFees', errors.Transaction.FeeError.TooSmall
|
||||||
|
));
|
||||||
it('can skip the check that prevents dust outputs', buildSkipTest(
|
it('can skip the check that prevents dust outputs', buildSkipTest(
|
||||||
function(transaction) {
|
function(transaction) {
|
||||||
return transaction
|
return transaction
|
||||||
.to(toAddress, 100)
|
.to(toAddress, 100)
|
||||||
.change(changeAddress)
|
.change(changeAddress)
|
||||||
.sign(privateKey);
|
.sign(privateKey);
|
||||||
}, 'disableDustOutputs'));
|
}, 'disableDustOutputs', errors.Transaction.DustOutputs
|
||||||
it('can skip the check that prevents unsigned outputs', function() {
|
));
|
||||||
var transaction = new Transaction();
|
it('can skip the check that prevents unsigned outputs', buildSkipTest(
|
||||||
transaction.from(simpleUtxoWith1BTC);
|
function(transaction) {
|
||||||
transaction.to(toAddress, 10000);
|
return transaction
|
||||||
transaction.change(changeAddress);
|
.to(toAddress, 10000)
|
||||||
var options = {};
|
.change(changeAddress);
|
||||||
options.disableIsFullySigned = true;
|
}, 'disableIsFullySigned', errors.Transaction.MissingSignatures
|
||||||
expect(function() {
|
));
|
||||||
return transaction.serialize(options);
|
it('can skip the check that avoids spending more bitcoins than the inputs for a transaction', buildSkipTest(
|
||||||
}).not.to.throw(errors.Transaction.MissingSignatures);
|
function(transaction) {
|
||||||
expect(function() {
|
return transaction
|
||||||
return transaction.serialize();
|
.to(toAddress, 10000000000000)
|
||||||
}).to.throw(errors.Transaction.MissingSignatures);
|
.change(changeAddress)
|
||||||
});
|
.sign(privateKey);
|
||||||
it('can skip the check that avoids spending more bitcoins than the inputs for a transaction', function() {
|
}, 'disableMoreOutputThanInput', errors.Transaction.InvalidOutputAmountSum
|
||||||
var transaction = new Transaction();
|
));
|
||||||
transaction.from(simpleUtxoWith1BTC);
|
|
||||||
transaction.to(toAddress, 10000000000000);
|
|
||||||
transaction.change(changeAddress);
|
|
||||||
expect(function() {
|
|
||||||
return transaction.serialize({
|
|
||||||
disableSmallFees: true,
|
|
||||||
disableIsFullySigned: true,
|
|
||||||
disableMoreOutputThanInput: true
|
|
||||||
});
|
|
||||||
}).not.to.throw(errors.Transaction.InvalidOutputAmountSum);
|
|
||||||
expect(function() {
|
|
||||||
return transaction.serialize({
|
|
||||||
disableIsFullySigned: true,
|
|
||||||
disableSmallFees: true
|
|
||||||
});
|
|
||||||
}).to.throw(errors.Transaction.InvalidOutputAmountSum);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -511,6 +520,23 @@ describe('Transaction', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('not if has null input (and not coinbase)', function() {
|
||||||
|
|
||||||
|
var tx = new Transaction()
|
||||||
|
.from({
|
||||||
|
'txId': testPrevTx,
|
||||||
|
'outputIndex': 0,
|
||||||
|
'script': testScript,
|
||||||
|
'satoshis': testAmount
|
||||||
|
}).to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
||||||
|
|
||||||
|
tx.isCoinbase = sinon.stub().returns(false);
|
||||||
|
tx.inputs[0].isNull = sinon.stub().returns(true);
|
||||||
|
var verify = tx.verify();
|
||||||
|
verify.should.equal('transaction input 0 has null input');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('to and from JSON', function() {
|
describe('to and from JSON', function() {
|
||||||
|
|
Loading…
Reference in New Issue