-
Notifications
You must be signed in to change notification settings - Fork 36.2k
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
DecodeHexTx: Try case where txn has inputs first #17775
Conversation
Yeah, I wanted to do the same, but there was no rationale on why no_witness is tried first, so I didn't touch the code. Concept ACK. |
This is incorrect I think. If you swap the order, the "!try_witness" needs to turn into a "!try_no_witness" on the other side. |
src/core_read.cpp
Outdated
try { | ||
ssData >> tx; | ||
if (ssData.empty()) { | ||
if (ssData.eof() && (!try_witness || CheckTxScriptsSanity(tx))) { |
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.
@sipa you mean here? Why would that make sense? We're inside a try_no_witness
block already
src/core_read.cpp
Outdated
try { | ||
ssData >> tx; | ||
if (ssData.empty()) { | ||
if (ssData.eof() && (!try_witness || CheckTxScriptsSanity(tx))) { |
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.
No, in the other clause. It's supposed to bypass the sanity check when only one of try_witness and try_no_witness is set.
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.
ah I see, fixed it up(within my understanding) and put a quick comment there for future readers' comprehension
3f33f02
to
6020ce3
Compare
While touching this file and When fuzzing the tx decoding part of this code it would be nice to not having to waste CPU cycles on hex encoding followed by immediate hex decoding for no reason at all :) |
The interaction between the parameters For the casual observer it is easy to think that:
Which is not the case :) |
65445e7
to
27fc6a3
Compare
@practicalswift trying to touch as little as possible for this change, but yes it's something that should certainly be made simpler |
Concept ACK; code looks right to me, and it fixes #18028. But in primitives/transaction.h SerializeTransaction we'll still do non-extended format for unfunded partial transactions with no inputs, which could still lead to us spitting out a hex representation that we misparse when reading it back in. So it might be sensible to also change: --- a/src/primitives/transaction.h
+++ b/src/primitives/transaction.h
@@ -222,7 +222,7 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) {
for (size_t i = 0; i < tx.vin.size(); i++) {
s >> tx.vin[i].scriptWitness.stack;
}
- if (!tx.HasWitness()) {
+ if (!tx.HasWitness() && !tx.vin.empty()) {
/* It's illegal to encode witnesses when all witness stacks are empty. */
throw std::ios_base::failure("Superfluous witness record");
}
@@ -244,10 +244,14 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) {
if (fAllowWitness) {
/* Check whether witnesses need to be serialized. */
if (tx.HasWitness()) {
flags |= 1;
}
+ /* If non-extended format might be ambiguous, use extended format */
+ if (tx.vin.empty()) {
+ flags |= 1;
+ }
}
if (flags) {
/* Use extended format in case witnesses are to be serialized. */
std::vector<CTxIn> vinDummy;
s << vinDummy; |
@ajtowns I'd rather not touch serialization code for this if possible since it's already hard enough to get review :) |
I tried writing a fuzzer to see if reasonable partial txs with one output and no inputs would be incorrectly decoded as a witness tx. There are a few, but they seem reasonably unlikely (requiring the only output to pay to a non-compressed, non-hashed pubkey, or an OP_RETURN). One more plausible example is:
In particular, the vout's nValue needs to decode to a plausible number of vins, the script pubkey has to decode as a plausible scriptSig, vout array and witness stack, and the scriptSig and any pubkeys in vout need to have valid ops to pass the sanity check. That seems reasonably unlikely, something lke 8 in 256**5 maybe, rather the 1 in 256ish chance seen in #18028. As the PR desc notes, for any cases where a partial tx is incorrectly decoded as a witness tx, using a PSBT instead will avoid the problem. [EDIT: dropped ACK, need to check what happens if valid witness tx that would have decoded as non-witness is included in block first] |
ACK 27fc6a3 A valid witness tx (on my regtest anyway) which decodes incorrectly as a 0-in 1-out non-witness tx is That tx was constructed by:
That's a 1-in-16M chance of occurring accidently, but requires only 66k sha256d operations to find. |
ACK 27fc6a3 If we dropped the |
27fc6a3 DecodeHexTx: Break out transaction decoding logic into own function (Gregory Sanders) 6020ce3 DecodeHexTx: Try case where txn has inputs first (Gregory Sanders) Pull request description: Alternative/complementary to bitcoin#17773 to avoid random `decoderawtransaction` failures. Most cases this is used now is on complete transactions, especially with the uptake of PSBT. ACKs for top commit: ajtowns: ACK 27fc6a3 achow101: ACK 27fc6a3 Tree-SHA512: 0a836d7c9951bf7d2764507788dbcc871d520f1ea9b77d6b22f051f4d6224ed779aba0e4f28c5c165040095ee0c70b67080c39164d82de61b19158f7ae6fddb2
Uncomment when bitcoin/bitcoin#17775 is merged
Alternative/complementary to #17773 to avoid random
decoderawtransaction
failures. Most cases this is used now is on complete transactions, especially with the uptake of PSBT.