Skip to content

Commit

Permalink
Add CMutableTransaction and make CTransaction immutable.
Browse files Browse the repository at this point in the history
In addition, introduce a cached hash inside CTransaction, to prevent
recalculating it over and over again.
  • Loading branch information
sipa committed Jun 21, 2014
1 parent 8f59251 commit 4949004
Show file tree
Hide file tree
Showing 19 changed files with 181 additions and 106 deletions.
2 changes: 1 addition & 1 deletion src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class CMainParams : public CChainParams {
// CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B)
// vMerkleTree: 4a5e1e
const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks";
CTransaction txNew;
CMutableTransaction txNew;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
Expand Down
25 changes: 24 additions & 1 deletion src/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,34 @@ std::string CFeeRate::ToString() const
return result;
}

uint256 CTransaction::GetHash() const
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {}

uint256 CMutableTransaction::GetHash() const
{
return SerializeHash(*this);
}

void CTransaction::UpdateHash() const
{
*const_cast<uint256*>(&hash) = SerializeHash(*this);
}

CTransaction::CTransaction() : hash(0), nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) { }

CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {
UpdateHash();
}

CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<int*>(&nVersion) = tx.nVersion;
*const_cast<std::vector<CTxIn>*>(&vin) = tx.vin;
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
*const_cast<uint256*>(&hash) = tx.hash;
return *this;
}

int64_t CTransaction::GetValueOut() const
{
int64_t nValueOut = 0;
Expand Down
93 changes: 62 additions & 31 deletions src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,49 +203,59 @@ class CTxOut
};


struct CMutableTransaction;

/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
*/
class CTransaction
{
private:
/** Memory only. */
const uint256 hash;
void UpdateHash() const;

public:
static CFeeRate minTxFee;
static CFeeRate minRelayTxFee;
static const int CURRENT_VERSION=1;
int nVersion;
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
unsigned int nLockTime;

CTransaction()
{
SetNull();
}
// The local variables are made const to prevent unintended modification
// without updating the cached hash value. However, CTransaction is not
// actually immutable; deserialization and assignment are implemented,
// and bypass the constness. This is safe, as they update the entire
// structure, including the hash.
const int nVersion;
const std::vector<CTxIn> vin;
const std::vector<CTxOut> vout;
const unsigned int nLockTime;

IMPLEMENT_SERIALIZE
(
READWRITE(this->nVersion);
/** Construct a CTransaction that qualifies as IsNull() */
CTransaction();

/** Convert a CMutableTransaction into a CTransaction. */
CTransaction(const CMutableTransaction &tx);

CTransaction& operator=(const CTransaction& tx);

IMPLEMENT_SERIALIZE(
READWRITE(*const_cast<int*>(&this->nVersion));
nVersion = this->nVersion;
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
READWRITE(*const_cast<unsigned int*>(&nLockTime));
if (fRead)
UpdateHash();
)

void SetNull()
{
nVersion = CTransaction::CURRENT_VERSION;
vin.clear();
vout.clear();
nLockTime = 0;
bool IsNull() const {
return vin.empty() && vout.empty();
}

bool IsNull() const
{
return (vin.empty() && vout.empty());
const uint256& GetHash() const {
return hash;
}

uint256 GetHash() const;

// Return sum of txouts.
int64_t GetValueOut() const;
// GetValueIn() is a method on CCoinsViewCache, because
Expand All @@ -261,22 +271,43 @@ class CTransaction

friend bool operator==(const CTransaction& a, const CTransaction& b)
{
return (a.nVersion == b.nVersion &&
a.vin == b.vin &&
a.vout == b.vout &&
a.nLockTime == b.nLockTime);
return a.hash == b.hash;
}

friend bool operator!=(const CTransaction& a, const CTransaction& b)
{
return !(a == b);
return a.hash != b.hash;
}


std::string ToString() const;
void print() const;
};

/** A mutable version of CTransaction. */
struct CMutableTransaction
{
int nVersion;
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
unsigned int nLockTime;

CMutableTransaction();
CMutableTransaction(const CTransaction& tx);

IMPLEMENT_SERIALIZE(
READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
)

/** Compute the hash of this CMutableTransaction. This is computed on the
* fly, as opposed to GetHash() in CTransaction, which uses a cached result.
*/
uint256 GetHash() const;
};

/** wrapper for CTxOut that provides a more compact serialization */
class CTxOutCompressor
{
Expand Down
18 changes: 11 additions & 7 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
CBlock *pblock = &pblocktemplate->block; // pointer for convenience

// Create coinbase tx
CTransaction txNew;
CMutableTransaction txNew;
txNew.vin.resize(1);
txNew.vin[0].prevout.SetNull();
txNew.vout.resize(1);
txNew.vout[0].scriptPubKey = scriptPubKeyIn;

// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
// Add dummy coinbase tx as first transaction
pblock->vtx.push_back(CTransaction());
pblocktemplate->vTxFees.push_back(-1); // updated at end
pblocktemplate->vTxSigOps.push_back(-1); // updated at end

Expand Down Expand Up @@ -294,15 +294,17 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
nLastBlockSize = nBlockSize;
LogPrintf("CreateNewBlock(): total size %u\n", nBlockSize);

pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees);
// Compute final coinbase transaction.
txNew.vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees);
txNew.vin[0].scriptSig = CScript() << OP_0 << OP_0;
pblock->vtx[0] = txNew;
pblocktemplate->vTxFees[0] = -nFees;

// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
UpdateTime(*pblock, pindexPrev);
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock);
pblock->nNonce = 0;
pblock->vtx[0].vin[0].scriptSig = CScript() << OP_0 << OP_0;
pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]);

CBlockIndex indexDummy(*pblock);
Expand All @@ -328,9 +330,11 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int&
}
++nExtraNonce;
unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
pblock->vtx[0].vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);
CMutableTransaction txCoinbase(pblock->vtx[0]);
txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
assert(txCoinbase.vin[0].scriptSig.size() <= 100);

pblock->vtx[0] = txCoinbase;
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
}

Expand Down
2 changes: 1 addition & 1 deletion src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
// nPayAmount
qint64 nPayAmount = 0;
bool fDust = false;
CTransaction txDummy;
CMutableTransaction txDummy;
foreach(const qint64 &amount, CoinControlDialog::payAmounts)
{
nPayAmount += amount;
Expand Down
10 changes: 5 additions & 5 deletions src/rpcrawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ Value createrawtransaction(const Array& params, bool fHelp)
Array inputs = params[0].get_array();
Object sendTo = params[1].get_obj();

CTransaction rawTx;
CMutableTransaction rawTx;

BOOST_FOREACH(const Value& input, inputs)
{
Expand Down Expand Up @@ -554,11 +554,11 @@ Value signrawtransaction(const Array& params, bool fHelp)

vector<unsigned char> txData(ParseHexV(params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
vector<CTransaction> txVariants;
vector<CMutableTransaction> txVariants;
while (!ssData.empty())
{
try {
CTransaction tx;
CMutableTransaction tx;
ssData >> tx;
txVariants.push_back(tx);
}
Expand All @@ -572,7 +572,7 @@ Value signrawtransaction(const Array& params, bool fHelp)

// mergedTx will end up with all the signatures; it
// starts as a clone of the rawtx:
CTransaction mergedTx(txVariants[0]);
CMutableTransaction mergedTx(txVariants[0]);
bool fComplete = true;

// Fetch previous transactions (inputs):
Expand Down Expand Up @@ -713,7 +713,7 @@ Value signrawtransaction(const Array& params, bool fHelp)
SignSignature(keystore, prevPubKey, mergedTx, i, nHashType);

// ... and merge in other signatures:
BOOST_FOREACH(const CTransaction& txv, txVariants)
BOOST_FOREACH(const CMutableTransaction& txv, txVariants)
{
txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig);
}
Expand Down
6 changes: 3 additions & 3 deletions src/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1636,7 +1636,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
}


bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType)
bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType)
{
assert(nIn < txTo.vin.size());
CTxIn& txin = txTo.vin[nIn];
Expand Down Expand Up @@ -1671,7 +1671,7 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CTransa
return VerifyScript(txin.scriptSig, fromPubKey, txTo, nIn, STANDARD_SCRIPT_VERIFY_FLAGS, 0);
}

bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType)
bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType)
{
assert(nIn < txTo.vin.size());
CTxIn& txin = txTo.vin[nIn];
Expand All @@ -1689,7 +1689,7 @@ static CScript PushAll(const vector<valtype>& values)
return result;
}

static CScript CombineMultisig(CScript scriptPubKey, const CTransaction& txTo, unsigned int nIn,
static CScript CombineMultisig(CScript scriptPubKey, const CMutableTransaction& txTo, unsigned int nIn,
const vector<valtype>& vSolutions,
vector<valtype>& sigs1, vector<valtype>& sigs2)
{
Expand Down
5 changes: 3 additions & 2 deletions src/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
class CCoins;
class CKeyStore;
class CTransaction;
class CMutableTransaction;

static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
static const unsigned int MAX_OP_RETURN_RELAY = 40; // bytes
Expand Down Expand Up @@ -805,8 +806,8 @@ bool IsMine(const CKeyStore& keystore, const CTxDestination &dest);
void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector<CKeyID> &vKeys);
bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet);
bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);
bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool SignSignature(const CKeyStore& keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL);
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CTransaction& txTo, unsigned int nIn, unsigned int flags, int nHashType);

// Given two sets of signatures for scriptPubKey, possibly with OP_0 placeholders,
Expand Down
12 changes: 6 additions & 6 deletions src/test/DoS_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
// 50 orphan transactions:
for (int i = 0; i < 50; i++)
{
CTransaction tx;
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].prevout.n = 0;
tx.vin[0].prevout.hash = GetRandHash();
Expand All @@ -184,7 +184,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
{
CTransaction txPrev = RandomOrphan();

CTransaction tx;
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].prevout.n = 0;
tx.vin[0].prevout.hash = txPrev.GetHash();
Expand All @@ -201,7 +201,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
{
CTransaction txPrev = RandomOrphan();

CTransaction tx;
CMutableTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
Expand Down Expand Up @@ -242,10 +242,10 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)

// 100 orphan transactions:
static const int NPREV=100;
CTransaction orphans[NPREV];
CMutableTransaction orphans[NPREV];
for (int i = 0; i < NPREV; i++)
{
CTransaction& tx = orphans[i];
CMutableTransaction& tx = orphans[i];
tx.vin.resize(1);
tx.vin[0].prevout.n = 0;
tx.vin[0].prevout.hash = GetRandHash();
Expand All @@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(DoS_checkSig)
}

// Create a transaction that depends on orphans:
CTransaction tx;
CMutableTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1*CENT;
tx.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
Expand Down
Loading

0 comments on commit 4949004

Please sign in to comment.