Skip to content

Commit 81e3228

Browse files
committed
Make CTransaction actually immutable
1 parent 42fd8de commit 81e3228

File tree

10 files changed

+131
-140
lines changed

10 files changed

+131
-140
lines changed

src/net_processing.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,8 +1594,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
15941594

15951595
deque<COutPoint> vWorkQueue;
15961596
vector<uint256> vEraseQueue;
1597-
CTransaction tx;
1598-
vRecv >> tx;
1597+
CTransaction tx(deserialize, vRecv);
15991598

16001599
CInv inv(MSG_TX, tx.GetHash());
16011600
pfrom->AddInventoryKnown(inv);

src/primitives/transaction.cpp

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,35 +62,20 @@ uint256 CMutableTransaction::GetHash() const
6262
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
6363
}
6464

65-
void CTransaction::UpdateHash() const
65+
uint256 CTransaction::ComputeHash() const
6666
{
67-
*const_cast<uint256*>(&hash) = SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
67+
return SerializeHash(*this, SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
6868
}
6969

7070
uint256 CTransaction::GetWitnessHash() const
7171
{
7272
return SerializeHash(*this, SER_GETHASH, 0);
7373
}
7474

75-
CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) { }
76-
77-
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime) {
78-
UpdateHash();
79-
}
80-
81-
CTransaction::CTransaction(CMutableTransaction &&tx) : nVersion(tx.nVersion), vin(std::move(tx.vin)), vout(std::move(tx.vout)), wit(std::move(tx.wit)), nLockTime(tx.nLockTime) {
82-
UpdateHash();
83-
}
84-
85-
CTransaction& CTransaction::operator=(const CTransaction &tx) {
86-
*const_cast<int*>(&nVersion) = tx.nVersion;
87-
*const_cast<std::vector<CTxIn>*>(&vin) = tx.vin;
88-
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
89-
*const_cast<CTxWitness*>(&wit) = tx.wit;
90-
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
91-
*const_cast<uint256*>(&hash) = tx.hash;
92-
return *this;
93-
}
75+
/* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */
76+
CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0), hash() {}
77+
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime), hash(ComputeHash()) {}
78+
CTransaction::CTransaction(CMutableTransaction &&tx) : nVersion(tx.nVersion), vin(std::move(tx.vin)), vout(std::move(tx.vout)), wit(std::move(tx.wit)), nLockTime(tx.nLockTime), hash(ComputeHash()) {}
9479

9580
CAmount CTransaction::GetValueOut() const
9681
{

src/primitives/transaction.h

Lines changed: 81 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -286,73 +286,81 @@ struct CMutableTransaction;
286286
* - CTxWitness wit;
287287
* - uint32_t nLockTime
288288
*/
289-
template<typename Stream, typename Operation, typename TxType>
290-
inline void SerializeTransaction(TxType& tx, Stream& s, Operation ser_action) {
289+
template<typename Stream, typename TxType>
290+
inline void UnserializeTransaction(TxType& tx, Stream& s) {
291291
const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS);
292292

293-
READWRITE(*const_cast<int32_t*>(&tx.nVersion));
293+
s >> tx.nVersion;
294294
unsigned char flags = 0;
295-
if (ser_action.ForRead()) {
296-
const_cast<std::vector<CTxIn>*>(&tx.vin)->clear();
297-
const_cast<std::vector<CTxOut>*>(&tx.vout)->clear();
298-
const_cast<CTxWitness*>(&tx.wit)->SetNull();
299-
/* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */
300-
READWRITE(*const_cast<std::vector<CTxIn>*>(&tx.vin));
301-
if (tx.vin.size() == 0 && fAllowWitness) {
302-
/* We read a dummy or an empty vin. */
303-
READWRITE(flags);
304-
if (flags != 0) {
305-
READWRITE(*const_cast<std::vector<CTxIn>*>(&tx.vin));
306-
READWRITE(*const_cast<std::vector<CTxOut>*>(&tx.vout));
307-
}
308-
} else {
309-
/* We read a non-empty vin. Assume a normal vout follows. */
310-
READWRITE(*const_cast<std::vector<CTxOut>*>(&tx.vout));
311-
}
312-
if ((flags & 1) && fAllowWitness) {
313-
/* The witness flag is present, and we support witnesses. */
314-
flags ^= 1;
315-
const_cast<CTxWitness*>(&tx.wit)->vtxinwit.resize(tx.vin.size());
316-
READWRITE(tx.wit);
317-
}
318-
if (flags) {
319-
/* Unknown flag in the serialization */
320-
throw std::ios_base::failure("Unknown transaction optional data");
295+
tx.vin.clear();
296+
tx.vout.clear();
297+
tx.wit.SetNull();
298+
/* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */
299+
s >> tx.vin;
300+
if (tx.vin.size() == 0 && fAllowWitness) {
301+
/* We read a dummy or an empty vin. */
302+
s >> flags;
303+
if (flags != 0) {
304+
s >> tx.vin;
305+
s >> tx.vout;
321306
}
322307
} else {
323-
// Consistency check
324-
assert(tx.wit.vtxinwit.size() <= tx.vin.size());
325-
if (fAllowWitness) {
326-
/* Check whether witnesses need to be serialized. */
327-
if (!tx.wit.IsNull()) {
328-
flags |= 1;
329-
}
330-
}
331-
if (flags) {
332-
/* Use extended format in case witnesses are to be serialized. */
333-
std::vector<CTxIn> vinDummy;
334-
READWRITE(vinDummy);
335-
READWRITE(flags);
308+
/* We read a non-empty vin. Assume a normal vout follows. */
309+
s >> tx.vout;
310+
}
311+
if ((flags & 1) && fAllowWitness) {
312+
/* The witness flag is present, and we support witnesses. */
313+
flags ^= 1;
314+
tx.wit.vtxinwit.resize(tx.vin.size());
315+
s >> tx.wit;
316+
}
317+
if (flags) {
318+
/* Unknown flag in the serialization */
319+
throw std::ios_base::failure("Unknown transaction optional data");
320+
}
321+
s >> tx.nLockTime;
322+
}
323+
324+
template<typename Stream, typename TxType>
325+
inline void SerializeTransaction(const TxType& tx, Stream& s) {
326+
const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS);
327+
328+
s << tx.nVersion;
329+
unsigned char flags = 0;
330+
// Consistency check
331+
assert(tx.wit.vtxinwit.size() <= tx.vin.size());
332+
if (fAllowWitness) {
333+
/* Check whether witnesses need to be serialized. */
334+
if (!tx.wit.IsNull()) {
335+
flags |= 1;
336336
}
337-
READWRITE(*const_cast<std::vector<CTxIn>*>(&tx.vin));
338-
READWRITE(*const_cast<std::vector<CTxOut>*>(&tx.vout));
339-
if (flags & 1) {
340-
const_cast<CTxWitness*>(&tx.wit)->vtxinwit.resize(tx.vin.size());
341-
READWRITE(tx.wit);
337+
}
338+
if (flags) {
339+
/* Use extended format in case witnesses are to be serialized. */
340+
std::vector<CTxIn> vinDummy;
341+
s << vinDummy;
342+
s << flags;
343+
}
344+
s << tx.vin;
345+
s << tx.vout;
346+
if (flags & 1) {
347+
for (size_t i = 0; i < tx.vin.size(); i++) {
348+
if (i < tx.wit.vtxinwit.size()) {
349+
s << tx.wit.vtxinwit[i];
350+
} else {
351+
s << CTxInWitness();
352+
}
342353
}
343354
}
344-
READWRITE(*const_cast<uint32_t*>(&tx.nLockTime));
355+
s << tx.nLockTime;
345356
}
346357

358+
347359
/** The basic transaction that is broadcasted on the network and contained in
348360
* blocks. A transaction can contain multiple inputs and outputs.
349361
*/
350362
class CTransaction
351363
{
352-
private:
353-
/** Memory only. */
354-
const uint256 hash;
355-
356364
public:
357365
// Default transaction version.
358366
static const int32_t CURRENT_VERSION=1;
@@ -374,25 +382,27 @@ class CTransaction
374382
CTxWitness wit; // Not const: can change without invalidating the txid cache
375383
const uint32_t nLockTime;
376384

385+
private:
386+
/** Memory only. */
387+
const uint256 hash;
388+
389+
uint256 ComputeHash() const;
390+
391+
public:
377392
/** Construct a CTransaction that qualifies as IsNull() */
378393
CTransaction();
379394

380395
/** Convert a CMutableTransaction into a CTransaction. */
381396
CTransaction(const CMutableTransaction &tx);
382397
CTransaction(CMutableTransaction &&tx);
383398

384-
CTransaction& operator=(const CTransaction& tx);
385-
386-
ADD_SERIALIZE_METHODS;
387-
388-
template <typename Stream, typename Operation>
389-
inline void SerializationOp(Stream& s, Operation ser_action) {
390-
SerializeTransaction(*this, s, ser_action);
391-
if (ser_action.ForRead()) {
392-
UpdateHash();
393-
}
399+
template <typename Stream>
400+
inline void Serialize(Stream& s) const {
401+
SerializeTransaction(*this, s);
394402
}
395403

404+
/** This deserializing constructor is provided instead of an Unserialize method.
405+
* Unserialize is not possible, since it would require overwriting const fields. */
396406
template <typename Stream>
397407
CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
398408

@@ -417,7 +427,7 @@ class CTransaction
417427

418428
// Compute modified tx size for priority calculation (optionally given tx size)
419429
unsigned int CalculateModifiedSize(unsigned int nTxSize=0) const;
420-
430+
421431
/**
422432
* Get the total transaction size in bytes, including witness data.
423433
* "Total Size" defined in BIP141 and BIP144.
@@ -441,8 +451,6 @@ class CTransaction
441451
}
442452

443453
std::string ToString() const;
444-
445-
void UpdateHash() const;
446454
};
447455

448456
/** A mutable version of CTransaction. */
@@ -457,11 +465,15 @@ struct CMutableTransaction
457465
CMutableTransaction();
458466
CMutableTransaction(const CTransaction& tx);
459467

460-
ADD_SERIALIZE_METHODS;
468+
template <typename Stream>
469+
inline void Serialize(Stream& s) const {
470+
SerializeTransaction(*this, s);
471+
}
461472

462-
template <typename Stream, typename Operation>
463-
inline void SerializationOp(Stream& s, Operation ser_action) {
464-
SerializeTransaction(*this, s, ser_action);
473+
474+
template <typename Stream>
475+
inline void Unserialize(Stream& s) {
476+
UnserializeTransaction(*this, s);
465477
}
466478

467479
template <typename Stream>

src/script/bitcoinconsensus.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
8585
}
8686
try {
8787
TxInputStream stream(SER_NETWORK, PROTOCOL_VERSION, txTo, txToLen);
88-
CTransaction tx;
89-
stream >> tx;
88+
CTransaction tx(deserialize, stream);
9089
if (nIn >= tx.vin.size())
9190
return set_error(err, bitcoinconsensus_ERR_TX_INDEX);
9291
if (GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) != txToLen)

src/test/bloom_tests.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,14 @@ BOOST_AUTO_TEST_CASE(bloom_create_insert_key)
114114
BOOST_AUTO_TEST_CASE(bloom_match)
115115
{
116116
// Random real transaction (b4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b)
117-
CTransaction tx;
118117
CDataStream stream(ParseHex("01000000010b26e9b7735eb6aabdf358bab62f9816a21ba9ebdb719d5299e88607d722c190000000008b4830450220070aca44506c5cef3a16ed519d7c3c39f8aab192c4e1c90d065f37b8a4af6141022100a8e160b856c2d43d27d8fba71e5aef6405b8643ac4cb7cb3c462aced7f14711a0141046d11fee51b0e60666d5049a9101a72741df480b96ee26488a4d3466b95c9a40ac5eeef87e10a5cd336c19a84565f80fa6c547957b7700ff4dfbdefe76036c339ffffffff021bff3d11000000001976a91404943fdd508053c75000106d3bc6e2754dbcff1988ac2f15de00000000001976a914a266436d2965547608b9e15d9032a7b9d64fa43188ac00000000"), SER_DISK, CLIENT_VERSION);
119-
stream >> tx;
118+
CTransaction tx(deserialize, stream);
120119

121120
// and one which spends it (e2769b09e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436)
122121
unsigned char ch[] = {0x01, 0x00, 0x00, 0x00, 0x01, 0x6b, 0xff, 0x7f, 0xcd, 0x4f, 0x85, 0x65, 0xef, 0x40, 0x6d, 0xd5, 0xd6, 0x3d, 0x4f, 0xf9, 0x4f, 0x31, 0x8f, 0xe8, 0x20, 0x27, 0xfd, 0x4d, 0xc4, 0x51, 0xb0, 0x44, 0x74, 0x01, 0x9f, 0x74, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xda, 0x0d, 0xc6, 0xae, 0xce, 0xfe, 0x1e, 0x06, 0xef, 0xdf, 0x05, 0x77, 0x37, 0x57, 0xde, 0xb1, 0x68, 0x82, 0x09, 0x30, 0xe3, 0xb0, 0xd0, 0x3f, 0x46, 0xf5, 0xfc, 0xf1, 0x50, 0xbf, 0x99, 0x0c, 0x02, 0x21, 0x00, 0xd2, 0x5b, 0x5c, 0x87, 0x04, 0x00, 0x76, 0xe4, 0xf2, 0x53, 0xf8, 0x26, 0x2e, 0x76, 0x3e, 0x2d, 0xd5, 0x1e, 0x7f, 0xf0, 0xbe, 0x15, 0x77, 0x27, 0xc4, 0xbc, 0x42, 0x80, 0x7f, 0x17, 0xbd, 0x39, 0x01, 0x41, 0x04, 0xe6, 0xc2, 0x6e, 0xf6, 0x7d, 0xc6, 0x10, 0xd2, 0xcd, 0x19, 0x24, 0x84, 0x78, 0x9a, 0x6c, 0xf9, 0xae, 0xa9, 0x93, 0x0b, 0x94, 0x4b, 0x7e, 0x2d, 0xb5, 0x34, 0x2b, 0x9d, 0x9e, 0x5b, 0x9f, 0xf7, 0x9a, 0xff, 0x9a, 0x2e, 0xe1, 0x97, 0x8d, 0xd7, 0xfd, 0x01, 0xdf, 0xc5, 0x22, 0xee, 0x02, 0x28, 0x3d, 0x3b, 0x06, 0xa9, 0xd0, 0x3a, 0xcf, 0x80, 0x96, 0x96, 0x8d, 0x7d, 0xbb, 0x0f, 0x91, 0x78, 0xff, 0xff, 0xff, 0xff, 0x02, 0x8b, 0xa7, 0x94, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xba, 0xde, 0xec, 0xfd, 0xef, 0x05, 0x07, 0x24, 0x7f, 0xc8, 0xf7, 0x42, 0x41, 0xd7, 0x3b, 0xc0, 0x39, 0x97, 0x2d, 0x7b, 0x88, 0xac, 0x40, 0x94, 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xc1, 0x09, 0x32, 0x48, 0x3f, 0xec, 0x93, 0xed, 0x51, 0xf5, 0xfe, 0x95, 0xe7, 0x25, 0x59, 0xf2, 0xcc, 0x70, 0x43, 0xf9, 0x88, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00};
123122
vector<unsigned char> vch(ch, ch + sizeof(ch) -1);
124123
CDataStream spendStream(vch, SER_DISK, CLIENT_VERSION);
125-
CTransaction spendingTx;
126-
spendStream >> spendingTx;
124+
CTransaction spendingTx(deserialize, spendStream);
127125

128126
CBloomFilter filter(10, 0.000001, 0, BLOOM_UPDATE_ALL);
129127
filter.insert(uint256S("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b"));

src/test/script_tests.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ class TestBuilder
276276
//! The Witness embedded script
277277
CScript witscript;
278278
CScriptWitness scriptWitness;
279-
CTransaction creditTx;
279+
CTransactionRef creditTx;
280280
CMutableTransaction spendTx;
281281
bool havePush;
282282
std::vector<unsigned char> push;
@@ -319,8 +319,8 @@ class TestBuilder
319319
redeemscript = scriptPubKey;
320320
scriptPubKey = CScript() << OP_HASH160 << ToByteVector(CScriptID(redeemscript)) << OP_EQUAL;
321321
}
322-
creditTx = BuildCreditingTransaction(scriptPubKey, nValue);
323-
spendTx = BuildSpendingTransaction(CScript(), CScriptWitness(), creditTx);
322+
creditTx = MakeTransactionRef(BuildCreditingTransaction(scriptPubKey, nValue));
323+
spendTx = BuildSpendingTransaction(CScript(), CScriptWitness(), *creditTx);
324324
}
325325

326326
TestBuilder& ScriptError(ScriptError_t err)
@@ -421,7 +421,7 @@ class TestBuilder
421421
{
422422
TestBuilder copy = *this; // Make a copy so we can rollback the push.
423423
DoPush();
424-
DoTest(creditTx.vout[0].scriptPubKey, spendTx.vin[0].scriptSig, scriptWitness, flags, comment, scriptError, nValue);
424+
DoTest(creditTx->vout[0].scriptPubKey, spendTx.vin[0].scriptSig, scriptWitness, flags, comment, scriptError, nValue);
425425
*this = copy;
426426
return *this;
427427
}
@@ -447,7 +447,7 @@ class TestBuilder
447447
array.push_back(wit);
448448
}
449449
array.push_back(FormatScript(spendTx.vin[0].scriptSig));
450-
array.push_back(FormatScript(creditTx.vout[0].scriptPubKey));
450+
array.push_back(FormatScript(creditTx->vout[0].scriptPubKey));
451451
array.push_back(FormatScriptFlags(flags));
452452
array.push_back(FormatScriptError((ScriptError_t)scriptError));
453453
array.push_back(comment);
@@ -461,7 +461,7 @@ class TestBuilder
461461

462462
const CScript& GetScriptPubKey()
463463
{
464-
return creditTx.vout[0].scriptPubKey;
464+
return creditTx->vout[0].scriptPubKey;
465465
}
466466
};
467467

src/test/serialize_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ class CSerializeMethodsTestSingle
2121
bool boolval;
2222
std::string stringval;
2323
const char* charstrval;
24-
CTransaction txval;
24+
CTransactionRef txval;
2525
public:
2626
CSerializeMethodsTestSingle() = default;
27-
CSerializeMethodsTestSingle(int intvalin, bool boolvalin, std::string stringvalin, const char* charstrvalin, CTransaction txvalin) : intval(intvalin), boolval(boolvalin), stringval(std::move(stringvalin)), charstrval(charstrvalin), txval(txvalin){}
27+
CSerializeMethodsTestSingle(int intvalin, bool boolvalin, std::string stringvalin, const char* charstrvalin, CTransaction txvalin) : intval(intvalin), boolval(boolvalin), stringval(std::move(stringvalin)), charstrval(charstrvalin), txval(MakeTransactionRef(txvalin)){}
2828
ADD_SERIALIZE_METHODS;
2929

3030
template <typename Stream, typename Operation>
@@ -42,7 +42,7 @@ class CSerializeMethodsTestSingle
4242
boolval == rhs.boolval && \
4343
stringval == rhs.stringval && \
4444
strcmp(charstrval, rhs.charstrval) == 0 && \
45-
txval == rhs.txval;
45+
*txval == *rhs.txval;
4646
}
4747
};
4848

src/test/sighash_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
184184
std::string raw_tx, raw_script, sigHashHex;
185185
int nIn, nHashType;
186186
uint256 sh;
187-
CTransaction tx;
187+
CTransactionRef tx;
188188
CScript scriptCode = CScript();
189189

190190
try {
@@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
199199
stream >> tx;
200200

201201
CValidationState state;
202-
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest);
202+
BOOST_CHECK_MESSAGE(CheckTransaction(*tx, state), strTest);
203203
BOOST_CHECK(state.IsValid());
204204

205205
std::vector<unsigned char> raw = ParseHex(raw_script);
@@ -209,7 +209,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
209209
continue;
210210
}
211211

212-
sh = SignatureHash(scriptCode, tx, nIn, nHashType, 0, SIGVERSION_BASE);
212+
sh = SignatureHash(scriptCode, *tx, nIn, nHashType, 0, SIGVERSION_BASE);
213213
BOOST_CHECK_MESSAGE(sh.GetHex() == sigHashHex, strTest);
214214
}
215215
}

0 commit comments

Comments
 (0)