Skip to content

Commit 46904ee

Browse files
committed
Merge #8580: Make CTransaction actually immutable
81e3228 Make CTransaction actually immutable (Pieter Wuille) 42fd8de Make DecodeHexTx return a CMutableTransaction (Pieter Wuille) c3f5673 Make CWalletTx store a CTransactionRef instead of inheriting (Pieter Wuille) a188353 Switch GetTransaction to returning a CTransactionRef (Pieter Wuille)
2 parents 4d955fc + 81e3228 commit 46904ee

28 files changed

+287
-292
lines changed

src/bench/coin_selection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, vector<COutput
1919
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
2020
tx.vout.resize(nInput + 1);
2121
tx.vout[nInput].nValue = nValue;
22-
CWalletTx* wtx = new CWalletTx(&wallet, tx);
22+
CWalletTx* wtx = new CWalletTx(&wallet, MakeTransactionRef(std::move(tx)));
2323

2424
int nAge = 6 * 24;
2525
COutput output(wtx, nInput, nAge, true, true);

src/bitcoin-tx.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ static int CommandLineRawTx(int argc, char* argv[])
623623
argv++;
624624
}
625625

626-
CTransaction txDecodeTmp;
626+
CMutableTransaction tx;
627627
int startArg;
628628

629629
if (!fCreateBlank) {
@@ -636,15 +636,13 @@ static int CommandLineRawTx(int argc, char* argv[])
636636
if (strHexTx == "-") // "-" implies standard input
637637
strHexTx = readStdin();
638638

639-
if (!DecodeHexTx(txDecodeTmp, strHexTx, true))
639+
if (!DecodeHexTx(tx, strHexTx, true))
640640
throw std::runtime_error("invalid transaction encoding");
641641

642642
startArg = 2;
643643
} else
644644
startArg = 1;
645645

646-
CMutableTransaction tx(txDecodeTmp);
647-
648646
for (int i = startArg; i < argc; i++) {
649647
std::string arg = argv[i];
650648
std::string key, value;

src/core_io.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
class CBlock;
1212
class CScript;
1313
class CTransaction;
14+
class CMutableTransaction;
1415
class uint256;
1516
class UniValue;
1617

1718
// core_read.cpp
1819
CScript ParseScript(const std::string& s);
1920
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode = false);
20-
bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx, bool fTryNoWitness = false);
21+
bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx, bool fTryNoWitness = false);
2122
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
2223
uint256 ParseHashUV(const UniValue& v, const std::string& strName);
2324
uint256 ParseHashStr(const std::string&, const std::string& strName);

src/core_read.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ CScript ParseScript(const std::string& s)
9090
return result;
9191
}
9292

93-
bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx, bool fTryNoWitness)
93+
bool DecodeHexTx(CMutableTransaction& tx, const std::string& strHexTx, bool fTryNoWitness)
9494
{
9595
if (!IsHex(strHexTx))
9696
return false;

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/qt/coincontroldialog.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -470,21 +470,21 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
470470
nQuantity++;
471471

472472
// Amount
473-
nAmount += out.tx->vout[out.i].nValue;
473+
nAmount += out.tx->tx->vout[out.i].nValue;
474474

475475
// Priority
476-
dPriorityInputs += (double)out.tx->vout[out.i].nValue * (out.nDepth+1);
476+
dPriorityInputs += (double)out.tx->tx->vout[out.i].nValue * (out.nDepth+1);
477477

478478
// Bytes
479479
CTxDestination address;
480480
int witnessversion = 0;
481481
std::vector<unsigned char> witnessprogram;
482-
if (out.tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram))
482+
if (out.tx->tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram))
483483
{
484484
nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4);
485485
fWitness = true;
486486
}
487-
else if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address))
487+
else if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address))
488488
{
489489
CPubKey pubkey;
490490
CKeyID *keyid = boost::get<CKeyID>(&address);
@@ -677,7 +677,7 @@ void CoinControlDialog::updateView()
677677
CAmount nSum = 0;
678678
int nChildren = 0;
679679
BOOST_FOREACH(const COutput& out, coins.second) {
680-
nSum += out.tx->vout[out.i].nValue;
680+
nSum += out.tx->tx->vout[out.i].nValue;
681681
nChildren++;
682682

683683
CCoinControlWidgetItem *itemOutput;
@@ -689,7 +689,7 @@ void CoinControlDialog::updateView()
689689
// address
690690
CTxDestination outputAddress;
691691
QString sAddress = "";
692-
if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, outputAddress))
692+
if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress))
693693
{
694694
sAddress = QString::fromStdString(CBitcoinAddress(outputAddress).ToString());
695695

@@ -714,8 +714,8 @@ void CoinControlDialog::updateView()
714714
}
715715

716716
// amount
717-
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->vout[out.i].nValue));
718-
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.tx->vout[out.i].nValue)); // padding so that sorting works correctly
717+
itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->tx->vout[out.i].nValue));
718+
itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong)out.tx->tx->vout[out.i].nValue)); // padding so that sorting works correctly
719719

720720
// date
721721
itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime()));

0 commit comments

Comments
 (0)