diff --git a/src/main.cpp b/src/main.cpp index 1779fa549214a..9f1b77bb32b2f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1001,7 +1001,18 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa if (!hasZcSpendInputs) dPriority = view.GetPriority(tx, chainHeight, inChainInputValue); - CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainHeight, pool.HasNoInputsOf(tx), inChainInputValue); + // Keep track of transactions that spend a coinbase, which we re-scan + // during reorgs to ensure COINBASE_MATURITY is still met. + bool fSpendsCoinbaseOrCoinstake = false; + for (const CTxIn &txin : tx.vin) { + const CCoins *coins = view.AccessCoins(txin.prevout.hash); + if (coins->IsCoinBase() || coins->IsCoinStake()) { + fSpendsCoinbaseOrCoinstake = true; + break; + } + } + + CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainHeight, pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbaseOrCoinstake); unsigned int nSize = entry.GetTxSize(); // Don't accept it if it can't get into a block @@ -1226,7 +1237,17 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact CAmount inChainInputValue; double dPriority = view.GetPriority(tx, chainHeight, inChainInputValue); - CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainHeight, mempool.HasNoInputsOf(tx), inChainInputValue); + // Keep track of transactions that spend a coinbase, which we re-scan + // during reorgs to ensure COINBASE_MATURITY is still met. + bool fSpendsCoinbaseOrCoinstake = false; + for (const CTxIn &txin : tx.vin) { + const CCoins *coins = view.AccessCoins(txin.prevout.hash); + if (coins->IsCoinBase() || coins->IsCoinStake()) { + fSpendsCoinbaseOrCoinstake = true; + break; + } + } + CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainHeight, mempool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbaseOrCoinstake); unsigned int nSize = entry.GetTxSize(); // Don't accept it if it can't get into a block diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index bb05a8e938f7e..e54f7fb7fadee 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -113,7 +113,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { tx.vout[0].nValue -= 1000000; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); @@ -133,7 +134,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { tx.vout[0].nValue -= 10000000; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); @@ -152,7 +154,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 4900000000LL; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin.resize(2); tx.vin[1].scriptSig = CScript() << OP_1; @@ -160,7 +162,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[1].prevout.n = 0; tx.vout[0].nValue = 5900000000LL; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); delete pblocktemplate; mempool.clear(); @@ -171,7 +173,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vout[0].nValue = 0; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(false).FromTx(tx)); BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); delete pblocktemplate; mempool.clear(); @@ -184,12 +186,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script)); hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin[0].scriptSig = CScript() << std::vector(script.begin(), script.end()); tx.vout[0].nValue -= 1000000; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(false).FromTx(tx)); BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); delete pblocktemplate; mempool.clear(); @@ -200,10 +202,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].nValue = 4900000000LL; tx.vout[0].scriptPubKey = CScript() << OP_1; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); delete pblocktemplate; mempool.clear(); @@ -229,7 +231,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = chainActive.Tip()->nHeight+1; hash = tx.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx)); BOOST_CHECK(!CheckFinalTx(tx, LOCKTIME_MEDIAN_TIME_PAST)); // time locked @@ -243,7 +245,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx2.vout[0].scriptPubKey = CScript() << OP_1; tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1; hash = tx2.GetHash(); - mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx2)); + mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbaseOrCoinstake(true).FromTx(tx2)); BOOST_CHECK(!CheckFinalTx(tx2, LOCKTIME_MEDIAN_TIME_PAST)); BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey, pwalletMain, false)); diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index eae4c0ef010c1..1c76737a5659a 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -75,7 +75,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(CMutableTransaction &tx, CTxMemPo CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0; return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight, - hasNoDependencies, inChainValue); + hasNoDependencies, inChainValue, spendsCoinbaseOrCoinstake); } [[noreturn]] void Shutdown(void* parg) diff --git a/src/test/test_pivx.h b/src/test/test_pivx.h index 1e222a1fbbebe..5a682f1569e12 100644 --- a/src/test/test_pivx.h +++ b/src/test/test_pivx.h @@ -63,10 +63,11 @@ struct TestMemPoolEntryHelper double dPriority; unsigned int nHeight; bool hadNoDependencies; + bool spendsCoinbaseOrCoinstake; TestMemPoolEntryHelper() : nFee(0), nTime(0), dPriority(0.0), nHeight(1), - hadNoDependencies(false) { } + hadNoDependencies(false), spendsCoinbaseOrCoinstake(false) { } CTxMemPoolEntry FromTx(CMutableTransaction &tx, CTxMemPool *pool = NULL); @@ -76,6 +77,7 @@ struct TestMemPoolEntryHelper TestMemPoolEntryHelper &Priority(double _priority) { dPriority = _priority; return *this; } TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; } TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; } + TestMemPoolEntryHelper &SpendsCoinbaseOrCoinstake(bool _flag) { spendsCoinbaseOrCoinstake = _flag; return *this; } }; #endif diff --git a/src/txmempool.cpp b/src/txmempool.cpp index de7e7adbda5d8..91eb97f8189d2 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -22,8 +22,9 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, int64_t _nTime, double _entryPriority, - unsigned int _entryHeight, bool poolHasNoInputsOf, CAmount _inChainInputValue) : - tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight), hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue) + unsigned int _entryHeight, bool poolHasNoInputsOf, CAmount _inChainInputValue, + bool _spendsCoinbaseOrCoinstake) : + tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight), hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue), spendsCoinbaseOrCoinstake(_spendsCoinbaseOrCoinstake) { nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); nModSize = tx.CalculateModifiedSize(nTxSize); @@ -472,7 +473,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMem const CTransaction& tx = it->GetTx(); if (!IsFinalTx(tx, nMemPoolHeight, GetAdjustedTime())) { transactionsToRemove.push_back(tx); - } else { + } else if (it->GetSpendsCoinbaseOrCoinstake()) { for (const CTxIn& txin : tx.vin) { indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); if (it2 != mapTx.end()) diff --git a/src/txmempool.h b/src/txmempool.h index 46f1d2f674723..c6c57ad39521c 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -70,6 +70,7 @@ class CTxMemPoolEntry unsigned int entryHeight; //! Chain height when entering the mempool bool hadNoDependencies; //! Not dependent on any other txs when it entered the mempool CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain + bool spendsCoinbaseOrCoinstake; //! keep track of transactions that spend a coinbase or a coinstake // Information about descendants of this transaction that are in the // mempool; if we remove this transaction we must remove all of these @@ -83,7 +84,7 @@ class CTxMemPoolEntry public: CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, int64_t _nTime, double _entryPriority, unsigned int _entryHeight, - bool poolHasNoInputsOf, CAmount _inChainInputValue); + bool poolHasNoInputsOf, CAmount _inChainInputValue, bool _spendsCoinbaseOrCoinstake); CTxMemPoolEntry(const CTxMemPoolEntry& other); const CTransaction& GetTx() const { return this->tx; } @@ -113,6 +114,8 @@ class CTxMemPoolEntry uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } CAmount GetFeesWithDescendants() const { return nFeesWithDescendants; } + + bool GetSpendsCoinbaseOrCoinstake() const { return spendsCoinbaseOrCoinstake; } }; // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index.