Skip to content

Commit 39c42c4

Browse files
committed
Improve heuristic hex transaction decoding
Whenever both encodings are permitted, try both, and if only one succeeds, return that one. Otherwise prefer the one for which the heuristic sanity check passes. If that is the case for neither or for both, return the extended-permitting deserialization.
1 parent e98d1d6 commit 39c42c4

File tree

1 file changed

+50
-9
lines changed

1 file changed

+50
-9
lines changed

src/core_read.cpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,31 +126,72 @@ static bool CheckTxScriptsSanity(const CMutableTransaction& tx)
126126

127127
static bool DecodeTx(CMutableTransaction& tx, const std::vector<unsigned char>& tx_data, bool try_no_witness, bool try_witness)
128128
{
129+
// General strategy:
130+
// - Decode both with extended serialization (which interprets the 0x0001 tag as a marker for
131+
// the presense of witnesses) and with legacy serialization (which interprets the tag as a
132+
// 0-input 1-output incomplete transaction).
133+
// - Restricted by try_no_witness (which disables legacy if false) and try_witness (which
134+
// disables extended if false).
135+
// - Ignore serializations that do not fully consume the hex string.
136+
// - If neither succeeds, fail.
137+
// - If only one succeeds, return that one.
138+
// - If both decode attempts succeed:
139+
// - If only one passes the CheckTxScriptsSanity check, return that one.
140+
// - If neither or both pass CheckTxScriptsSanity, return the extended one.
141+
142+
CMutableTransaction tx_extended, tx_legacy;
143+
bool ok_extended = false, ok_legacy = false;
144+
145+
// Try decoding with extended serialization support, and remember if the result successfully
146+
// consumes the entire input.
129147
if (try_witness) {
130148
CDataStream ssData(tx_data, SER_NETWORK, PROTOCOL_VERSION);
131149
try {
132-
ssData >> tx;
133-
// If transaction looks sane, we don't try other mode even if requested
134-
if (ssData.empty() && (!try_no_witness || CheckTxScriptsSanity(tx))) {
135-
return true;
136-
}
150+
ssData >> tx_extended;
151+
if (ssData.empty()) ok_extended = true;
137152
} catch (const std::exception&) {
138153
// Fall through.
139154
}
140155
}
141156

157+
// Optimization: if extended decoding succeeded and the result passes CheckTxScriptsSanity,
158+
// don't bother decoding the other way.
159+
if (ok_extended && CheckTxScriptsSanity(tx_extended)) {
160+
tx = std::move(tx_extended);
161+
return true;
162+
}
163+
164+
// Try decoding with legacy serialization, and remember if the result successfully consumes the entire input.
142165
if (try_no_witness) {
143166
CDataStream ssData(tx_data, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
144167
try {
145-
ssData >> tx;
146-
if (ssData.empty()) {
147-
return true;
148-
}
168+
ssData >> tx_legacy;
169+
if (ssData.empty()) ok_legacy = true;
149170
} catch (const std::exception&) {
150171
// Fall through.
151172
}
152173
}
153174

175+
// If legacy decoding succeeded and passes CheckTxScriptsSanity, that's our answer, as we know
176+
// at this point that extended decoding either failed or doesn't pass the sanity check.
177+
if (ok_legacy && CheckTxScriptsSanity(tx_legacy)) {
178+
tx = std::move(tx_legacy);
179+
return true;
180+
}
181+
182+
// If extended decoding succeeded, and neither decoding passes sanity, return the extended one.
183+
if (ok_extended) {
184+
tx = std::move(tx_extended);
185+
return true;
186+
}
187+
188+
// If legacy decoding succeeded and extended didn't, return the legacy one.
189+
if (ok_legacy) {
190+
tx = std::move(tx_legacy);
191+
return true;
192+
}
193+
194+
// If none succeeded, we failed.
154195
return false;
155196
}
156197

0 commit comments

Comments
 (0)