New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transaction serialization errors: check the output amount before fee errors #1241
Conversation
…amount before checking fee errors. Added a test for it and also improved buildSkipTest by specifying which error to expect and using it for some tests where it wasn't used yet.
return function() { | ||
var transaction = new Transaction(); | ||
transaction.from(simpleUtxoWith1BTC); | ||
builder(transaction); | ||
|
||
var options = {}; | ||
var options = opts || {}; | ||
options[check] = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than Nevermind, the option is checked with values check
being an argument, couldn't we pass it more explicitly as part of the opts
argument?true
and false
.
When A few reasons why I don't think we need to have this option:
Edit: The only case I can see is if there are more inputs that will be expected to be added later. However in that case, perhaps |
return transaction | ||
.to(toAddress, 10000) | ||
.change(changeAddress); | ||
}, 'disableIsFullySigned', errors.Transaction.MissingSignatures)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
));
can go on the same column as it(
similar to more common });
Wouldn't there be more output than input with an anyone-can-pay transaction such as Lighthouse uses for assurance contracts? If not, I'm happy to remove the option to disable the check. |
If we do want to allow disabling the output amount check, it should automatically also disable all the fee errors as the concept of fee is meaningless when the unspent output is negative. |
Shall I remove the ability to disable the outputs check or shall I disable the fee errors also when the output check is disabled? |
@dskloet I wouldn't remove the ability to disable outputs>=intputs check... in some cases (CoinJoin or assurance contracts, for example), you'll send a totally invalid transaction (with regards to outputs>=inputs). I think your idea of disabling fee checks when that check is disabled is the right way to go |
…o disable the fee checks as the concept of a fee is meaningless when unspent output value is negative. This also allows for removing the opts from buildSkipTest again and simplifying the skip test for disableMoreOutputThanInput.
What about the case that |
I personally don't worry about it. As soon as output is allowed to be more than input the concept of fee is meaningless. But if you prefer I don't mind adding it. |
I think a better way to handle this is to either check the fees or that the output amount is greater than input: dskloet#1 |
…n already when the check for negative unspent output is disabled.
@@ -205,6 +205,10 @@ Transaction.prototype._isInvalidSatoshis = function() { | |||
}; | |||
|
|||
Transaction.prototype._hasFeeError = function(opts) { | |||
if (this._getUnspentValue() < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can simplify this check to only run once, as this._getUnspentValue() < 0
is the same as the _hasMoreOutputThanInput()
check: https://github.com/dskloet/bitcore/blob/fix/transaction-error-order/lib/transaction/transaction.js#L278
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you are suggesting. This check here looks very simple to me.
Is _getUnspentValue() expensive that we shouldn't call it more than once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably use benchmarking to help in that regard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it stands now _getUnspentValue()
is called around five times for the serialization check, would be interesting to see if this problematic with a transaction with many inputs/outputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could, but that doesn't answer my question. Is the cost of _getUnspentValue() what you were worried about or was it something else? Were you suggesting to merge _hasFeeError() and _hasMoreOutputThanInput() into one method?
I think that would make things more complicated rather than simpler. I just did the previous pull request in order to split all of that to reduce the cyclomatic complexity that lint was complaining about. I'd rather not undo that work.
If you are worried about calling _getUnspentValue() multiple times, I'd rather cache the result than merging several small methods into one big method. The fee checks also call _getUnspentValue() separately so it's not just here and in _hasMoreOutputThanInput().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I'm suggesting is something more along the lines of: braydonf@9c55b02
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Undoing my work from 26bd5a8 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say "simplify" do you mean "optimize"?
I have no idea why it says the coverage is decreased. Only one lines is not covered and it wasn't covered before either. |
Can we move forward with this pull request? It would also help if I had a better idea about how all of this works. Are you guys BitPay employees? Is Bitcore your full time job or a side project? Do you require extra time to discuss some pull requests on another channel that I can't see to make sure you agree about it? The whole process seems pretty opaque and contributing would be more fun if I could understand it better. Or maybe there isn't more too it than I can see but that would be good to know as well. |
Yes, we can move forward. Functionally it LGTM. Only a few thoughts on organization: Reducing the complexity and number of statements for individual functions can then make it tedious to read the operation as a whole, making it more complex. So I think there is a balance. I think these functions can be simplified together and still be under 15 statements:
These were separate methods from the beginning, however it could be good to take care of now while we're improving these methods. I think the addition of |
Can you maybe explain your criteria for simplicity? What is it you find tedious to read specifically? I honestly don't understand how the merging of several small methods into one big method could be considered a simplification. |
LGTM |
Example: Transaction.prototype.getSerializationError = function(opts) {
...
return this._isInvalidSatoshis() ||
...
}
Transaction.prototype._isInvalidSatoshis = function() {
if (this.invalidSatoshis()) {
return new errors.Transaction.InvalidSatoshis();
}
};
Transaction.prototype.invalidSatoshis = function() {
var invalid = false;
for (var i = 0; i < this.outputs.length; i++) {
if (this.outputs[i].invalidSatoshis()) {
invalid = true;
}
}
return invalid;
}; Would be more concise and with less function calls as: Transaction.prototype.getSerializationError = function(opts) {
...
if (this.invalidSatoshis()) {
return new errors.Transaction.InvalidSatoshis();
}
...
}
Transaction.prototype.invalidSatoshis = function() {
var invalid = false;
for (var i = 0; i < this.outputs.length; i++) {
if (this.outputs[i].invalidSatoshis()) {
invalid = true;
}
}
return invalid;
}; There are similar examples with the other methods mentioned, and related to handling of the method |
So you are really asking me to undo my work on #1226. If you didn't like one simple function per error, why didn't you say so on that pull request? I don't understand what this even has to do with this pull request but I'm not going to undo my own work. If that's what it takes to contribute my spare time to this project then it's not as much fun as I thought. |
With transaction serialization errors check the output amount before fee errors. Also added a test for it and also improved buildSkipTest by specifying which error to expect and using it for some tests where it wasn't used yet.