From 7b05e5ca060b52a195117e63e1895dc0373dae0f Mon Sep 17 00:00:00 2001 From: David Burkett Date: Sat, 29 Jan 2022 21:44:33 -0500 Subject: [PATCH] MWEB: Mempool --- src/test/mempool_tests.cpp | 11 +- src/test/policyestimator_tests.cpp | 24 ++-- src/txmempool.cpp | 187 ++++++++++++++++++++--------- src/txmempool.h | 45 ++++++- src/validation.cpp | 58 ++++++--- 5 files changed, 225 insertions(+), 100 deletions(-) diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index e10d558b1..a07077045 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include +#include #include #include #include @@ -389,9 +390,9 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) CheckSort(pool, sortedOrder); /* after tx6 is mined, tx7 should move up in the sort */ - std::vector vtx; - vtx.push_back(MakeTransactionRef(tx6)); - pool.removeForBlock(vtx, 1); + CBlock block; + block.vtx.push_back(MakeTransactionRef(tx6)); + pool.removeForBlock(block, 1, nullptr); sortedOrder.erase(sortedOrder.begin()+1); // Ties are broken by hash @@ -546,12 +547,12 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5)); pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7)); - std::vector vtx; + CBlock block; SetMockTime(42); SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000); // ... we should keep the same min fee until we get a block - pool.removeForBlock(vtx, 1); + pool.removeForBlock(block, 1, nullptr); SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/2.0)); // ... then feerate should drop 1/2 each halflife diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index fd5699ee7..365eb8810 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) CFeeRate baseRate(basefee, GetVirtualTransactionSize(CTransaction(tx)), 0); // Create a fake block - std::vector block; + CBlock block; int blocknum = 0; // Loop through 200 blocks @@ -70,12 +70,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) while (txHashes[9-h].size()) { CTransactionRef ptx = mpool.get(txHashes[9-h].back()); if (ptx) - block.push_back(ptx); + block.vtx.push_back(ptx); txHashes[9-h].pop_back(); } } - mpool.removeForBlock(block, ++blocknum); - block.clear(); + mpool.removeForBlock(block, ++blocknum, nullptr); + block.vtx.clear(); // Check after just a few txs that combining buckets works as expected if (blocknum == 3) { // At this point we should need to combine 3 buckets to get enough data points @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) // Mine 50 more blocks with no transactions happening, estimates shouldn't change // We haven't decayed the moving average enough so we still have enough data points in every bucket while (blocknum < 250) - mpool.removeForBlock(block, ++blocknum); + mpool.removeForBlock(block, ++blocknum, nullptr); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); for (int i = 2; i < 10;i++) { @@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) txHashes[j].push_back(hash); } } - mpool.removeForBlock(block, ++blocknum); + mpool.removeForBlock(block, ++blocknum, nullptr); } for (int i = 1; i < 10;i++) { @@ -146,12 +146,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) while(txHashes[j].size()) { CTransactionRef ptx = mpool.get(txHashes[j].back()); if (ptx) - block.push_back(ptx); + block.vtx.push_back(ptx); txHashes[j].pop_back(); } } - mpool.removeForBlock(block, 266); - block.clear(); + mpool.removeForBlock(block, 266, nullptr); + block.vtx.clear(); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); for (int i = 2; i < 10;i++) { BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); @@ -167,12 +167,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) mpool.addUnchecked(entry.Fee(feeV[j]).Time(GetTime()).Height(blocknum).FromTx(tx)); CTransactionRef ptx = mpool.get(hash); if (ptx) - block.push_back(ptx); + block.vtx.push_back(ptx); } } - mpool.removeForBlock(block, ++blocknum); - block.clear(); + mpool.removeForBlock(block, ++blocknum, nullptr); + block.vtx.clear(); } BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index bdff9be7b..f9cee41ba 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -131,21 +131,24 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector &vHashes if (it == mapTx.end()) { continue; } - auto iter = mapNextTx.lower_bound(COutPoint(hash, 0)); + // First calculate the children, and update CTxMemPool::m_children to // include them, and update their CTxMemPoolEntry::m_parents to include this tx. // we cache the in-mempool children to avoid duplicate updates { const auto epoch = GetFreshEpoch(); - for (; iter != mapNextTx.end() && iter->first->hash == hash; ++iter) { - const uint256 &childHash = iter->second->GetHash(); - txiter childIter = mapTx.find(childHash); - assert(childIter != mapTx.end()); - // We can skip updating entries we've encountered before or that - // are in the block (which are already accounted for). - if (!visited(childIter) && !setAlreadyIncluded.count(childHash)) { - UpdateChild(it, childIter, true); - UpdateParent(childIter, it, true); + for (const CTxOutput& output : it->GetTx().GetOutputs()) { + auto iter = mapNextTx.find(output.GetIndex()); + if (iter != mapNextTx.end()) { + const uint256& childHash = iter->second->GetHash(); + txiter childIter = mapTx.find(childHash); + assert(childIter != mapTx.end()); + // We can skip updating entries we've encountered before or that + // are in the block (which are already accounted for). + if (!visited(childIter) && !setAlreadyIncluded.count(childHash)) { + UpdateChild(it, childIter, true); + UpdateParent(childIter, it, true); + } } } } // release epoch guard for UpdateForDescendants @@ -322,7 +325,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifyMWEBWeight) { nSizeWithDescendants += modifySize; - assert(int64_t(nSizeWithDescendants) > 0); + assert(int64_t(nSizeWithDescendants) >= 0); nModFeesWithDescendants += modifyFee; nCountWithDescendants += modifyCount; assert(int64_t(nCountWithDescendants) > 0); @@ -354,7 +357,7 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) nCheckFrequency = 0; } -bool CTxMemPool::isSpent(const COutPoint& outpoint) const +bool CTxMemPool::isSpent(const OutputIndex& outpoint) const { LOCK(cs); return mapNextTx.count(outpoint); @@ -393,10 +396,24 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces const CTransaction& tx = newit->GetTx(); std::set setParentTransactions; - for (unsigned int i = 0; i < tx.vin.size(); i++) { - mapNextTx.insert(std::make_pair(&tx.vin[i].prevout, &tx)); - setParentTransactions.insert(tx.vin[i].prevout.hash); + for (const CTxInput& input : tx.GetInputs()) { + mapNextTx.insert(std::make_pair(input.GetIndex(), &tx)); + + if (input.IsMWEB()) { + auto parentIter = mapTxOutputs_MWEB.find(input.ToMWEB()); + if (parentIter != mapTxOutputs_MWEB.end()) { + setParentTransactions.insert(parentIter->second->GetHash()); + } + } else { + setParentTransactions.insert(input.GetTxIn().prevout.hash); + } } + + // MWEB: Add transaction to mapTxOutputs_MWEB for each output + for (const mw::Hash& output_id : tx.mweb_tx.GetOutputIDs()) { + mapTxOutputs_MWEB.insert(std::make_pair(output_id, &tx)); + } + // Don't bother worrying about child transactions of this one. // Normal case of a new transaction arriving is that there can't be any // children, because such children would be orphans. @@ -433,9 +450,24 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence); } - const uint256 hash = it->GetTx().GetHash(); - for (const CTxIn& txin : it->GetTx().vin) - mapNextTx.erase(txin.prevout); + CTransactionRef ptx = it->GetSharedTx(); + + const uint256 hash = ptx->GetHash(); + for (const CTxInput& txin : ptx->GetInputs()) + mapNextTx.erase(txin.GetIndex()); + + // MWEB: Remove transaction from mapTxOutputs_MWEB for each output + for (const mw::Hash& output_id : ptx->mweb_tx.GetOutputIDs()) { + mapTxOutputs_MWEB.erase(output_id); + } + + // MWEB: When removing MWEB transactions from the mempool after a block is connected, + // cache the original tx in recentTxsByKernel, in case we need to replay it during a reorg. + if (reason == MemPoolRemovalReason::BLOCK || reason == MemPoolRemovalReason::REORG) { + for (const mw::Hash& kernel_id : ptx->mweb_tx.GetKernelIDs()) { + recentTxsByKernel.Put(kernel_id, ptx); + } + } RemoveUnbroadcastTx(hash, true /* add logging because unchecked */ ); @@ -490,30 +522,31 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso { // Remove transaction from memory pool AssertLockHeld(cs); - setEntries txToRemove; - txiter origit = mapTx.find(origTx.GetHash()); - if (origit != mapTx.end()) { - txToRemove.insert(origit); - } else { - // When recursively removing but origTx isn't in the mempool - // be sure to remove any children that are in the pool. This can - // happen during chain re-orgs if origTx isn't re-accepted into - // the mempool for any reason. - for (unsigned int i = 0; i < origTx.vout.size(); i++) { - auto it = mapNextTx.find(COutPoint(origTx.GetHash(), i)); - if (it == mapNextTx.end()) - continue; - txiter nextit = mapTx.find(it->second->GetHash()); - assert(nextit != mapTx.end()); - txToRemove.insert(nextit); - } - } - setEntries setAllRemoves; - for (txiter it : txToRemove) { - CalculateDescendants(it, setAllRemoves); + setEntries txToRemove; + txiter origit = mapTx.find(origTx.GetHash()); + if (origit != mapTx.end()) { + txToRemove.insert(origit); + } else { + // When recursively removing but origTx isn't in the mempool + // be sure to remove any children that are in the pool. This can + // happen during chain re-orgs if origTx isn't re-accepted into + // the mempool for any reason. + for (const CTxOutput& output : origTx.GetOutputs()) { + auto it = mapNextTx.find(output.GetIndex()); + if (it == mapNextTx.end()) + continue; + txiter nextit = mapTx.find(it->second->GetHash()); + assert(nextit != mapTx.end()); + txToRemove.insert(nextit); } + } + setEntries setAllRemoves; + for (txiter it : txToRemove) { + CalculateDescendants(it, setAllRemoves); + } + + RemoveStaged(setAllRemoves, false, reason); - RemoveStaged(setAllRemoves, false, reason); } void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags) @@ -563,8 +596,8 @@ void CTxMemPool::removeConflicts(const CTransaction &tx) { // Remove transactions which depend on inputs of tx, recursively AssertLockHeld(cs); - for (const CTxIn &txin : tx.vin) { - auto it = mapNextTx.find(txin.prevout); + for (const CTxInput& input : tx.GetInputs()) { + auto it = mapNextTx.find(input.GetIndex()); if (it != mapNextTx.end()) { const CTransaction &txConflict = *it->second; if (txConflict != tx) @@ -579,21 +612,42 @@ void CTxMemPool::removeConflicts(const CTransaction &tx) /** * Called when a block is connected. Removes from mempool and updates the miner fee estimator. */ -void CTxMemPool::removeForBlock(const std::vector& vtx, unsigned int nBlockHeight) +void CTxMemPool::removeForBlock(const CBlock& block, unsigned int nBlockHeight, DisconnectedBlockTransactions* disconnectpool) { AssertLockHeld(cs); std::vector entries; - for (const auto& tx : vtx) + for (const auto& tx : block.vtx) { - uint256 hash = tx->GetHash(); - - indexed_transaction_set::iterator i = mapTx.find(hash); + indexed_transaction_set::iterator i = mapTx.find(tx->GetHash()); if (i != mapTx.end()) entries.push_back(&*i); } + + // MWEB: Check for transactions with kernels included in the block. + // If we add a map of txs by kernel hash in the future, this can be made more efficient. + std::vector txs = block.vtx; + if (!block.mweb_block.IsNull()) { + auto block_kernels = block.mweb_block.GetKernelIDs(); + for (txiter it = mapTx.begin(); it != mapTx.end(); ++it) { + CTransactionRef ptx = it->GetSharedTx(); + if (!ptx->HasMWEBTx()) continue; + + const auto& tx_kernels = ptx->mweb_tx.GetKernelIDs(); + bool remove_tx = std::any_of(tx_kernels.begin(), tx_kernels.end(), + [&block_kernels](const mw::Hash& kernel_id) { + return block_kernels.count(kernel_id) != 0; + } + ); + if (remove_tx) { + entries.push_back(&*it); + txs.push_back(ptx); + } + } + } + // Before the txs in the new block have been removed from the mempool, update policy estimates if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);} - for (const auto& tx : vtx) + for (const auto& tx : txs) { txiter it = mapTx.find(tx->GetHash()); if (it != mapTx.end()) { @@ -604,14 +658,20 @@ void CTxMemPool::removeForBlock(const std::vector& vtx, unsigne removeConflicts(*tx); ClearPrioritisation(tx->GetHash()); } + lastRollingFeeUpdate = GetTime(); blockSinceLastRollingFeeBump = true; + + if (disconnectpool) { + disconnectpool->removeForBlock(txs); + } } void CTxMemPool::_clear() { mapTx.clear(); mapNextTx.clear(); + mapTxOutputs_MWEB.clear(); totalTxSize = 0; cachedInnerUsage = 0; lastRollingFeeUpdate = GetTime(); @@ -675,7 +735,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const // Check whether its inputs are marked in mapNextTx. auto it3 = mapNextTx.find(txin.prevout); assert(it3 != mapNextTx.end()); - assert(it3->first == &txin.prevout); + assert(it3->first == OutputIndex(txin.prevout)); assert(it3->second == &tx); i++; } @@ -707,13 +767,15 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const // Check children against mapNextTx CTxMemPoolEntry::Children setChildrenCheck; - auto iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0)); uint64_t child_sizes = 0; - for (; iter != mapNextTx.end() && iter->first->hash == it->GetTx().GetHash(); ++iter) { - txiter childit = mapTx.find(iter->second->GetHash()); - assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions - if (setChildrenCheck.insert(*childit).second) { - child_sizes += childit->GetTxSize(); + for (const CTxOutput& output : it->GetTx().GetOutputs()) { + auto iter = mapNextTx.find(output.GetIndex()); + if (iter != mapNextTx.end()) { + txiter childit = mapTx.find(iter->second->GetHash()); + assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions + if (setChildrenCheck.insert(*childit).second) { + child_sizes += childit->GetTxSize(); + } } } assert(setChildrenCheck.size() == it->GetMemPoolChildrenConst().size()); @@ -895,7 +957,7 @@ void CTxMemPool::ClearPrioritisation(const uint256& hash) mapDeltas.erase(hash); } -const CTransaction* CTxMemPool::GetConflictTx(const COutPoint& prevout) const +const CTransaction* CTxMemPool::GetConflictTx(const OutputIndex& prevout) const { const auto it = mapNextTx.find(prevout); return it == mapNextTx.end() ? nullptr : it->second; @@ -920,9 +982,16 @@ CTxMemPool::setEntries CTxMemPool::GetIterSet(const std::set& hashes) c bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const { - for (unsigned int i = 0; i < tx.vin.size(); i++) - if (exists(tx.vin[i].prevout.hash)) + for (const CTxInput& input : tx.GetInputs()) { + if (input.IsMWEB()) { + if (mapTxOutputs_MWEB.find(input.ToMWEB()) != mapTxOutputs_MWEB.end()) { + return false; + } + } else if (exists(input.GetTxIn().prevout.hash)) { return false; + } + } + return true; } @@ -947,7 +1016,7 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { size_t CTxMemPool::DynamicMemoryUsage() const { LOCK(cs); // Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. - return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; + return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapTxOutputs_MWEB) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; } void CTxMemPool::RemoveUnbroadcastTx(const uint256& txid, const bool unchecked) { diff --git a/src/txmempool.h b/src/txmempool.h index a0e109028..0c9cac126 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -27,13 +27,19 @@ #include #include #include +#include +class CBlock; class CBlockIndex; +struct DisconnectedBlockTransactions; extern RecursiveMutex cs_main; /** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */ static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF; +/** Default size of CMemPool's recentTxsByKernel cache */ +static const unsigned int DEFAULT_MEMPOOL_MWEB_CACHE_SIZE = 1000; + struct LockPoints { // Will be set to the blockchain height and median time past @@ -608,7 +614,21 @@ private: std::set m_unbroadcast_txids GUARDED_BY(cs); public: - indirectmap mapNextTx GUARDED_BY(cs); + /** + * Maps outputs to mempool transactions that spend them. + */ + std::map mapNextTx GUARDED_BY(cs); + + /** + * Maps MWEB output IDs to mempool transactions that create them. + */ + std::map mapTxOutputs_MWEB GUARDED_BY(cs); + + /** + * FIFO cache of txs recently removed from the mempool keyed by kernel ID. + */ + FIFOCache recentTxsByKernel GUARDED_BY(cs){DEFAULT_MEMPOOL_MWEB_CACHE_SIZE}; + std::map mapDeltas; /** Create a new CTxMemPool. @@ -637,13 +657,13 @@ public: void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs); - void removeForBlock(const std::vector& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); + void removeForBlock(const CBlock& block, unsigned int nBlockHeight, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs); void clear(); void _clear() EXCLUSIVE_LOCKS_REQUIRED(cs); //lock free bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb, bool wtxid=false); void queryHashes(std::vector& vtxid) const; - bool isSpent(const COutPoint& outpoint) const; + bool isSpent(const OutputIndex& outpoint) const; unsigned int GetTransactionsUpdated() const; void AddTransactionsUpdated(unsigned int n); /** @@ -658,7 +678,7 @@ public: void ClearPrioritisation(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs); /** Get the transaction in the pool that spends the same prevout */ - const CTransaction* GetConflictTx(const COutPoint& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs); + const CTransaction* GetConflictTx(const OutputIndex& prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs); /** Returns an iterator to the given hash, if found */ Optional GetIter(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -754,6 +774,17 @@ public: } bool exists(const uint256& txid) const { return exists(GenTxid{false, txid}); } + bool GetCreatedTx(const mw::Hash& output_id, uint256& hash) const + { + LOCK(cs); + auto iter = mapTxOutputs_MWEB.find(output_id); + if (iter != mapTxOutputs_MWEB.end()) { + hash = iter->second->GetHash(); + return true; + } + return false; + } + CTransactionRef get(const uint256& hash) const; txiter get_iter_from_wtxid(const uint256& wtxid) const EXCLUSIVE_LOCKS_REQUIRED(cs) { @@ -968,8 +999,10 @@ struct DisconnectedBlockTransactions { void addTransaction(const CTransactionRef& tx) { - queuedTx.insert(tx); - cachedInnerUsage += RecursiveDynamicUsage(tx); + if (queuedTx.find(tx->GetHash()) == queuedTx.end()) { + queuedTx.insert(tx); + cachedInnerUsage += RecursiveDynamicUsage(tx); + } } // Remove entries based on txid_index, and update memory usage. diff --git a/src/validation.cpp b/src/validation.cpp index 4c2e302c0..4009043c9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -385,7 +385,7 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact while (it != disconnectpool.queuedTx.get().rend()) { // ignore validation errors in resurrected transactions TxValidationState stateDummy; - if (!fAddToMempool || (*it)->IsCoinBase() || + if (!fAddToMempool || (*it)->IsCoinBase() || (*it)->IsHogEx() || !AcceptToMemoryPool(mempool, stateDummy, *it, nullptr /* plTxnReplaced */, true /* bypass_limits */)) { // If the transaction doesn't make it in to the mempool, remove any @@ -473,7 +473,7 @@ public: * additions if the associated transaction ends up being rejected by * the mempool. */ - std::vector& m_coins_to_uncache; + std::vector& m_coins_to_uncache; const bool m_test_accept; CAmount* m_fee_out; }; @@ -560,7 +560,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) TxValidationState &state = args.m_state; const int64_t nAcceptTime = args.m_accept_time; const bool bypass_limits = args.m_bypass_limits; - std::vector& coins_to_uncache = args.m_coins_to_uncache; + std::vector& coins_to_uncache = args.m_coins_to_uncache; // Alias what we need out of ws std::set& setConflicts = ws.m_conflicts; @@ -576,10 +576,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return false; // state filled in by CheckTransaction } + // MWEB: Check MWEB tx + if (!MWEB::Node::CheckTransaction(tx, state)) { + return false; // state filled in by CheckTransaction + } + // Coinbase is only valid in a block, not as a loose transaction if (tx.IsCoinBase()) return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); + // HogEx is only valid in a block, not as a loose transaction + if (tx.IsHogEx()) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "hogex"); + // Rather not work on nonstandard transactions (unless -testnet/-regtest) std::string reason; if (fRequireStandard && !IsStandardTx(tx, reason)) @@ -606,9 +615,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) } // Check for conflicts with in-memory transactions - for (const CTxIn &txin : tx.vin) + for (const CTxInput& txin : tx.GetInputs()) { - const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout); + const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.GetIndex()); if (ptxConflicting) { if (!setConflicts.count(ptxConflicting->GetHash())) { @@ -651,19 +660,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip(); // do all inputs exist? - for (const CTxIn& txin : tx.vin) { - if (!coins_cache.HaveCoinInCache(txin.prevout)) { - coins_to_uncache.push_back(txin.prevout); + for (const CTxInput& txin : tx.GetInputs()) { + if (!coins_cache.HaveCoinInCache(txin.GetIndex())) { + coins_to_uncache.push_back(txin.GetIndex()); } // Note: this call may add txin.prevout to the coins cache // (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed // later (via coins_to_uncache) if this tx turns out to be invalid. - if (!m_view.HaveCoin(txin.prevout)) { + if (!m_view.HaveCoin(txin.GetIndex())) { // Are inputs missing because we already have the tx? - for (size_t out = 0; out < tx.vout.size(); out++) { + for (const CTxOutput& txout : tx.GetOutputs()) { // Optimistically just do efficient check of cache for outputs - if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) { + if (coins_cache.HaveCoinInCache(txout.GetIndex())) { return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known"); } } @@ -854,9 +863,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) oldFeeRate.ToString())); } - for (const CTxIn &txin : mi->GetTx().vin) - { - setConflictsParents.insert(txin.prevout.hash); + for (const CTxInput& txin : mi->GetTx().GetInputs()) { + if (txin.IsMWEB()) { + auto parent_iter = m_pool.mapTxOutputs_MWEB.find(txin.ToMWEB()); + if (parent_iter != m_pool.mapTxOutputs_MWEB.end()) { + setConflictsParents.insert(parent_iter->second->GetHash()); + } + } else { + setConflictsParents.insert(txin.GetTxIn().prevout.hash); + } } nConflictingCount += mi->GetCountWithDescendants(); @@ -1073,7 +1088,7 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo int64_t nAcceptTime, std::list* plTxnReplaced, bool bypass_limits, bool test_accept, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - std::vector coins_to_uncache; + std::vector coins_to_uncache; MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, coins_to_uncache, test_accept, fee_out }; bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args); if (!res) { @@ -1082,7 +1097,7 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo // invalid transactions that attempt to overrun the in-memory coins cache // (`CCoinsViewCache::cacheCoins`). - for (const COutPoint& hashTx : coins_to_uncache) + for (const OutputIndex& hashTx : coins_to_uncache) ::ChainstateActive().CoinsTip().Uncache(hashTx); } // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits @@ -2574,6 +2589,14 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& return false; if (disconnectpool) { + // MWEB: For each kernel, lookup kernel's txs in FIFO cache and add them back to the mempool. + for (const mw::Hash& kernel_id : block.mweb_block.GetKernelIDs()) { + if (m_mempool.recentTxsByKernel.Cached(kernel_id)) { + CTransactionRef ptx = m_mempool.recentTxsByKernel.Get(kernel_id); + disconnectpool->addTransaction(ptx); + } + } + // Save transactions to re-add to mempool at end of reorg for (auto it = block.vtx.rbegin(); it != block.vtx.rend(); ++it) { disconnectpool->addTransaction(*it); @@ -2692,8 +2715,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4; LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal); // Remove conflicting transactions from the mempool.; - m_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight); - disconnectpool.removeForBlock(blockConnecting.vtx); + m_mempool.removeForBlock(blockConnecting, pindexNew->nHeight, &disconnectpool); // Update m_chain & related variables. m_chain.SetTip(pindexNew); UpdateTip(m_mempool, pindexNew, chainparams);