diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -20,6 +20,13 @@ - Insufficient funds - Transaction has too long of a mempool chain +- Exposed transaction version numbers are now treated as unsigned 32-bit integers + instead of signed 32-bit integers. This matches their treatment in consensus + logic. Versions greater than 2 continue to be non-standard (matching previous + behavior of smaller than 1 or greater than 2 being non-standard). Note that + this includes the joinpsbt command, which combines partially-signed + transactions by selecting the highest version number. + ## Notification changes `-walletnotify` notifications are now sent for wallet transactions that are diff --git a/src/core_write.cpp b/src/core_write.cpp --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -216,7 +216,10 @@ bool include_hex, int serialize_flags) { entry.pushKV("txid", tx.GetId().GetHex()); entry.pushKV("hash", tx.GetHash().GetHex()); - entry.pushKV("version", tx.nVersion); + // Transaction version is actually unsigned in consensus checks, just + // signed in memory, so cast to unsigned before giving it to the user. + entry.pushKV("version", + static_cast(static_cast(tx.nVersion))); entry.pushKV("size", (int)::GetSerializeSize(tx, PROTOCOL_VERSION)); entry.pushKV("locktime", (int64_t)tx.nLockTime); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1934,7 +1934,7 @@ "At least two PSBTs are required to join PSBTs."); } - int32_t best_version = 1; + uint32_t best_version = 1; uint32_t best_locktime = 0xffffffff; for (size_t i = 0; i < txs.size(); ++i) { PartiallySignedTransaction psbtx; @@ -1945,8 +1945,8 @@ } psbtxs.push_back(psbtx); // Choose the highest version number - if (psbtx.tx->nVersion > best_version) { - best_version = psbtx.tx->nVersion; + if (static_cast(psbtx.tx->nVersion) > best_version) { + best_version = static_cast(psbtx.tx->nVersion); } // Choose the lowest lock time if (psbtx.tx->nLockTime < best_locktime) { @@ -1957,7 +1957,7 @@ // Create a blank psbt where everything will be added PartiallySignedTransaction merged_psbt; merged_psbt.tx = CMutableTransaction(); - merged_psbt.tx->nVersion = best_version; + merged_psbt.tx->nVersion = static_cast(best_version); merged_psbt.tx->nLockTime = best_locktime; // Merge diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -582,11 +582,13 @@ # Test the minimum transaction version number that fits in a signed # 32-bit integer. + # As transaction version is unsigned, this should convert to its + # unsigned equivalent. tx = CTransaction() tx.nVersion = -0x80000000 rawtx = ToHex(tx) decrawtx = self.nodes[0].decoderawtransaction(rawtx) - assert_equal(decrawtx['version'], -0x80000000) + assert_equal(decrawtx['version'], 0x80000000) # Test the maximum transaction version number that fits in a signed # 32-bit integer.