diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 319be2991..29fabafd8 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -157,7 +157,7 @@ static void RegisterLoad(const string& strInput) static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) { int64_t newVersion = atoi64(cmdVal); - if (newVersion < 1 || newVersion > CTransaction::CURRENT_VERSION) + if (newVersion < CTransaction::MIN_CURRENT_VERSION || newVersion > CTransaction::MAX_CURRENT_VERSION) throw runtime_error("Invalid TX version requested"); tx.nVersion = (int) newVersion; diff --git a/src/main.cpp b/src/main.cpp index 2b09a50d6..7a1336fb7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -564,7 +564,7 @@ bool AddOrphanTx(const CTransaction& tx, NodeId peer) // have been mined or received. // 10,000 orphans, each of which is at most 5,000 bytes big is // at most 500 megabytes of orphans: - unsigned int sz = tx.GetSerializeSize(SER_NETWORK, CTransaction::CURRENT_VERSION); + unsigned int sz = tx.GetSerializeSize(SER_NETWORK, tx.nVersion); if (sz > 5000) { LogPrint("mempool", "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); @@ -639,7 +639,7 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) bool IsStandardTx(const CTransaction& tx, string& reason) { - if (tx.nVersion > CTransaction::CURRENT_VERSION || tx.nVersion < 1) { + if (tx.nVersion > CTransaction::MAX_CURRENT_VERSION || tx.nVersion < CTransaction::MIN_CURRENT_VERSION) { reason = "version"; return false; } diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 5fc2a39ab..399cd665a 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -137,7 +137,7 @@ std::string CTxOut::ToString() const return strprintf("CTxOut(nValue=%d.%08d, scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,30)); } -CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {} +CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::MIN_CURRENT_VERSION), nLockTime(0) {} CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), vjoinsplit(tx.vjoinsplit), joinSplitPubKey(tx.joinSplitPubKey), joinSplitSig(tx.joinSplitSig) { @@ -154,7 +154,7 @@ void CTransaction::UpdateHash() const *const_cast(&hash) = SerializeHash(*this); } -CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0), vjoinsplit(), joinSplitPubKey(), joinSplitSig() { } +CTransaction::CTransaction() : nVersion(CTransaction::MIN_CURRENT_VERSION), vin(), vout(), nLockTime(0), vjoinsplit(), joinSplitPubKey(), joinSplitSig() { } CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), vjoinsplit(tx.vjoinsplit), joinSplitPubKey(tx.joinSplitPubKey), joinSplitSig(tx.joinSplitSig) diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 9b78436c5..f57ea240d 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -309,7 +309,9 @@ private: public: typedef boost::array joinsplit_sig_t; - static const int32_t CURRENT_VERSION=1; + // Transactions that include a list of JoinSplits are version 2. + static const int32_t MIN_CURRENT_VERSION = 1; + static const int32_t MAX_CURRENT_VERSION = 2; // The local variables are made const to prevent unintended modification // without updating the cached hash value. However, CTransaction is not diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 25efbd292..672a20d8a 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -590,4 +590,55 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK(!IsStandardTx(t, reason)); } +BOOST_AUTO_TEST_CASE(test_IsStandardV2) +{ + LOCK(cs_main); + CBasicKeyStore keystore; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + std::vector dummyTransactions = SetupDummyInputs(keystore, coins); + + CMutableTransaction t; + t.vin.resize(1); + t.vin[0].prevout.hash = dummyTransactions[0].GetHash(); + t.vin[0].prevout.n = 1; + t.vin[0].scriptSig << std::vector(65, 0); + t.vout.resize(1); + t.vout[0].nValue = 90*CENT; + CKey key; + key.MakeNewKey(true); + t.vout[0].scriptPubKey = GetScriptForDestination(key.GetPubKey().GetID()); + + string reason; + // A v2 transaction with no JoinSplits is still standard. + t.nVersion = 2; + BOOST_CHECK(IsStandardTx(t, reason)); + + // ... and with one JoinSplit. + t.vjoinsplit.push_back(JSDescription()); + BOOST_CHECK(IsStandardTx(t, reason)); + + // ... and when that JoinSplit takes from a transparent input. + JSDescription *jsdesc = &t.vjoinsplit[0]; + jsdesc->vpub_old = 10*CENT; + t.vout[0].nValue -= 10*CENT; + BOOST_CHECK(IsStandardTx(t, reason)); + + // A v2 transaction with JoinSplits but no transparent inputs is standard. + jsdesc->vpub_old = 0; + jsdesc->vpub_new = 100*CENT; + t.vout[0].nValue = 90*CENT; + t.vin.resize(0); + BOOST_CHECK(IsStandardTx(t, reason)); + + // v2 transactions can still be non-standard for the same reasons as v1. + t.vout[0].nValue = 501; // dust + BOOST_CHECK(!IsStandardTx(t, reason)); + + // v3 is not standard. + t.nVersion = 3; + t.vout[0].nValue = 90*CENT; + BOOST_CHECK(!IsStandardTx(t, reason)); +} + BOOST_AUTO_TEST_SUITE_END()