Skip to content

Commit

Permalink
consensus: Store transaction nVersion as uin32_t
Browse files Browse the repository at this point in the history
Given that the use of a transaction's nVersion is always as an unsigned
int, it doesn't make sense to store it as signed and then cast it to
unsigned.
  • Loading branch information
achow101 committed Jan 25, 2024
1 parent 3672099 commit 0a76440
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 20 deletions.
3 changes: 1 addition & 2 deletions src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ std::pair<int, int64_t> CalculateSequenceLocks(const CTransaction &tx, int flags
// tx.nVersion is signed integer so requires cast to unsigned otherwise
// we would be doing a signed comparison and half the range of nVersion
// wouldn't support BIP 68.
bool fEnforceBIP68 = static_cast<uint32_t>(tx.nVersion) >= 2
&& flags & LOCKTIME_VERIFY_SEQUENCE;
bool fEnforceBIP68 = tx.nVersion >= 2 && flags & LOCKTIME_VERIFY_SEQUENCE;

// Do not enforce sequence numbers as a relative lock time
// unless we have been instructed to
Expand Down
2 changes: 1 addition & 1 deletion src/core_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
// 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<int64_t>(static_cast<uint32_t>(tx.nVersion)));
entry.pushKV("version", static_cast<int64_t>(tx.nVersion));
entry.pushKV("size", tx.GetTotalSize());
entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
entry.pushKV("weight", GetTransactionWeight(tx));
Expand Down
8 changes: 4 additions & 4 deletions src/primitives/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,13 @@ static constexpr TransactionSerParams TX_NO_WITNESS{.allow_witness = false};

/**
* Basic transaction serialization format:
* - int32_t nVersion
* - uint32_t nVersion
* - std::vector<CTxIn> vin
* - std::vector<CTxOut> vout
* - uint32_t nLockTime
*
* Extended transaction serialization format:
* - int32_t nVersion
* - uint32_t nVersion
* - unsigned char dummy = 0x00
* - unsigned char flags (!= 0)
* - std::vector<CTxIn> vin
Expand Down Expand Up @@ -305,7 +305,7 @@ class CTransaction
// structure, including the hash.
const std::vector<CTxIn> vin;
const std::vector<CTxOut> vout;
const int32_t nVersion;
const uint32_t nVersion;
const uint32_t nLockTime;

private:
Expand Down Expand Up @@ -378,7 +378,7 @@ struct CMutableTransaction
{
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
int32_t nVersion;
uint32_t nVersion;
uint32_t nLockTime;

explicit CMutableTransaction();
Expand Down
6 changes: 3 additions & 3 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1744,8 +1744,8 @@ static RPCHelpMan joinpsbts()
}
psbtxs.push_back(psbtx);
// Choose the highest version number
if (static_cast<uint32_t>(psbtx.tx->nVersion) > best_version) {
best_version = static_cast<uint32_t>(psbtx.tx->nVersion);
if (psbtx.tx->nVersion > best_version) {
best_version = psbtx.tx->nVersion;
}
// Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) {
Expand All @@ -1756,7 +1756,7 @@ static RPCHelpMan joinpsbts()
// Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->nVersion = static_cast<int32_t>(best_version);
merged_psbt.tx->nVersion = best_version;
merged_psbt.tx->nLockTime = best_locktime;

// Merge
Expand Down
2 changes: 1 addition & 1 deletion src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq

// Fail if the transaction's version number is not set high
// enough to trigger BIP 68 rules.
if (static_cast<uint32_t>(txTo->nVersion) < 2)
if (txTo->nVersion < 2)
return false;

// Sequence numbers with their most significant bit set are not
Expand Down
2 changes: 1 addition & 1 deletion test/functional/feature_taproot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ def test_spenders(self, node, spenders, input_counts):
while left:
# Construct CTransaction with random nVersion, nLocktime
tx = CTransaction()
tx.nVersion = random.choice([1, 2, random.randint(-0x80000000, 0x7fffffff)])
tx.nVersion = random.choice([1, 2, random.randrange(0xffffffff)])
min_sequence = (tx.nVersion != 1 and tx.nVersion != 0) * 0x80000000 # The minimum sequence number to disable relative locktime
if random.choice([True, False]):
tx.nLockTime = random.randrange(LOCKTIME_THRESHOLD, self.lastblocktime - 7200) # all absolute locktimes in the past
Expand Down
2 changes: 1 addition & 1 deletion test/functional/rpc_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def test_submitpackage(self):
peer = node.add_p2p_connection(P2PTxInvStore())
txs = self.wallet.create_self_transfer_chain(chain_length=2)
bad_child = tx_from_hex(txs[1]["hex"])
bad_child.nVersion = -1
bad_child.nVersion = 0xffffffff
hex_partial_acceptance = [txs[0]["hex"], bad_child.serialize().hex()]
res = node.submitpackage(hex_partial_acceptance)
assert_equal(res["package_msg"], "transaction failed")
Expand Down
18 changes: 16 additions & 2 deletions test/functional/rpc_rawtransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,9 @@ def transaction_version_number_tests(self):
self.log.info("Test transaction version numbers")

# 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.
# As transaction version is serialized unsigned, this should convert to its unsigned equivalent.
tx = CTransaction()
tx.nVersion = -0x80000000
tx.nVersion = 0x80000000
rawtx = tx.serialize().hex()
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0x80000000)
Expand All @@ -478,6 +478,20 @@ def transaction_version_number_tests(self):
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0x7fffffff)

# Test the minimum transaction version number that fits in an unsigned 32-bit integer.
tx = CTransaction()
tx.nVersion = 0
rawtx = tx.serialize().hex()
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0)

# Test the maximum transaction version number that fits in an unsigned 32-bit integer.
tx = CTransaction()
tx.nVersion = 0xffffffff
rawtx = tx.serialize().hex()
decrawtx = self.nodes[0].decoderawtransaction(rawtx)
assert_equal(decrawtx['version'], 0xffffffff)

def raw_multisig_transaction_legacy_tests(self):
self.log.info("Test raw multisig transactions (legacy)")
# The traditional multisig workflow does not work with descriptor wallets so these are legacy only.
Expand Down
6 changes: 3 additions & 3 deletions test/functional/test_framework/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ def __init__(self, tx=None):
self.wit = copy.deepcopy(tx.wit)

def deserialize(self, f):
self.nVersion = struct.unpack("<i", f.read(4))[0]
self.nVersion = struct.unpack("<I", f.read(4))[0]
self.vin = deser_vector(f, CTxIn)
flags = 0
if len(self.vin) == 0:
Expand All @@ -598,7 +598,7 @@ def deserialize(self, f):

def serialize_without_witness(self):
r = b""
r += struct.pack("<i", self.nVersion)
r += struct.pack("<I", self.nVersion)
r += ser_vector(self.vin)
r += ser_vector(self.vout)
r += struct.pack("<I", self.nLockTime)
Expand All @@ -610,7 +610,7 @@ def serialize_with_witness(self):
if not self.wit.is_null():
flags |= 1
r = b""
r += struct.pack("<i", self.nVersion)
r += struct.pack("<I", self.nVersion)
if flags:
dummy = []
r += ser_vector(dummy)
Expand Down
4 changes: 2 additions & 2 deletions test/functional/test_framework/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ def SegwitV0SignatureMsg(script, txTo, inIdx, hashtype, amount):
hashOutputs = uint256_from_str(hash256(serialize_outputs))

ss = bytes()
ss += struct.pack("<i", txTo.nVersion)
ss += struct.pack("<I", txTo.nVersion)
ss += ser_uint256(hashPrevouts)
ss += ser_uint256(hashSequence)
ss += txTo.vin[inIdx].prevout.serialize()
Expand Down Expand Up @@ -804,7 +804,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index = 0, scriptpat
in_type = hash_type & SIGHASH_ANYONECANPAY
spk = spent_utxos[input_index].scriptPubKey
ss = bytes([0, hash_type]) # epoch, hash_type
ss += struct.pack("<i", txTo.nVersion)
ss += struct.pack("<I", txTo.nVersion)
ss += struct.pack("<I", txTo.nLockTime)
if in_type != SIGHASH_ANYONECANPAY:
ss += BIP341_sha_prevouts(txTo)
Expand Down

0 comments on commit 0a76440

Please sign in to comment.