diff --git a/src/coins.h b/src/coins.h --- a/src/coins.h +++ b/src/coins.h @@ -347,13 +347,29 @@ /** Abstract view on the open txout dataset. */ class CCoinsView { +protected: + //! Just check whether we have data for a given txid. + //! This may (but cannot always) return true for fully spent transactions + virtual bool HaveCoins(const uint256 &txid) const; + public: //! Retrieve the CCoins (unspent transaction outputs) for a given txid virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; - //! Just check whether we have data for a given txid. - //! This may (but cannot always) return true for fully spent transactions - virtual bool HaveCoins(const uint256 &txid) const; + //! Transitional function to move from HaveCoins to HaveCoin. + bool HaveCoins_DONOTUSE(const uint256 &txid) const { + return HaveCoins(txid); + } + + //! Just check whether we have data for a given outpoint. + //! This may (but cannot always) return true for spent outputs. + bool HaveCoin(const COutPoint &outpoint) const { + CCoins coins; + if (!GetCoins(outpoint.hash, coins)) { + return false; + } + return coins.IsAvailable(outpoint.n); + } //! Retrieve the block hash whose state this CCoinsView currently represents virtual uint256 GetBestBlock() const; @@ -374,10 +390,11 @@ protected: CCoinsView *base; + bool HaveCoins(const uint256 &txid) const; + public: CCoinsViewBacked(CCoinsView *viewIn); bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; void SetBackend(CCoinsView &viewIn); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); @@ -425,23 +442,24 @@ /* Cached dynamic memory usage for the inner CCoins objects. */ mutable size_t cachedCoinsUsage; + bool HaveCoins(const uint256 &txid) const; + public: CCoinsViewCache(CCoinsView *baseIn); ~CCoinsViewCache(); // Standard CCoinsView methods bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool HaveCoins(const uint256 &txid) const; uint256 GetBestBlock() const; void SetBestBlock(const uint256 &hashBlock); bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock); /** - * Check if we have the given tx already loaded in this cache. - * The semantics are the same as HaveCoins(), but no calls to - * the backing CCoinsView are made. + * Check if we have the given utxo already loaded in this cache. + * The semantics are the same as HaveCoin(), but no calls to the backing + * CCoinsView are made. */ - bool HaveCoinsInCache(const uint256 &txid) const; + bool HaveCoinInCache(const COutPoint &outpoint) const; /** * Return a pointer to CCoins in the cache, or nullptr if not found. This is diff --git a/src/coins.cpp b/src/coins.cpp --- a/src/coins.cpp +++ b/src/coins.cpp @@ -61,7 +61,7 @@ return base->GetCoins(txid, coins); } bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { - return base->HaveCoins(txid); + return base->HaveCoins_DONOTUSE(txid); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); @@ -204,9 +204,9 @@ return (it != cacheCoins.end() && !it->second.coins.vout.empty()); } -bool CCoinsViewCache::HaveCoinsInCache(const uint256 &txid) const { - CCoinsMap::const_iterator it = cacheCoins.find(txid); - return it != cacheCoins.end(); +bool CCoinsViewCache::HaveCoinInCache(const COutPoint &outpoint) const { + CCoinsMap::const_iterator it = cacheCoins.find(outpoint.hash); + return it != cacheCoins.end() && it->second.coins.IsAvailable(outpoint.n); } uint256 CCoinsViewCache::GetBestBlock() const { diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -981,13 +981,16 @@ recentRejects->reset(); } - // Use pcoinsTip->HaveCoinsInCache as a quick approximation to + // Use pcoinsTip->HaveCoinInCache as a quick approximation to // exclude requesting or processing some txs which have already been - // included in a block. + // included in a block. As this is best effort, we only check for + // output 0 and 1. This works well enough in practice and we get + // diminishing returns with 2 onward. return recentRejects->contains(inv.hash) || mempool.exists(inv.hash) || mapOrphanTransactions.count(inv.hash) || - pcoinsTip->HaveCoinsInCache(inv.hash); + pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 0)) || + pcoinsTip->HaveCoinInCache(COutPoint(inv.hash, 1)); } case MSG_BLOCK: return mapBlockIndex.count(inv.hash); diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -771,10 +771,11 @@ protected: const CTxMemPool &mempool; + bool HaveCoins(const uint256 &txid) const; + public: CCoinsViewMemPool(CCoinsView *baseIn, const CTxMemPool &mempoolIn); bool GetCoins(const uint256 &txid, CCoins &coins) const; - bool HaveCoins(const uint256 &txid) const; }; // We want to sort transactions by coin age priority diff --git a/src/txmempool.cpp b/src/txmempool.cpp --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1036,7 +1036,7 @@ } bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const { - return mempool.exists(txid) || base->HaveCoins(txid); + return mempool.exists(txid) || base->HaveCoins_DONOTUSE(txid); } size_t CTxMemPool::DynamicMemoryUsage() const { diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -677,14 +677,17 @@ view.SetBackend(viewMemPool); // Do we already have it? - bool fHadTxInCache = pcoinsTip->HaveCoinsInCache(txid); - if (view.HaveCoins(txid)) { - if (!fHadTxInCache) { - vHashTxnToUncache.push_back(txid); - } + for (size_t out = 0; out < tx.vout.size(); out++) { + COutPoint outpoint(txid, out); + bool fHadTxInCache = pcoinsTip->HaveCoinInCache(outpoint); + if (view.HaveCoin(outpoint)) { + if (!fHadTxInCache) { + vHashTxnToUncache.push_back(txid); + } - return state.Invalid(false, REJECT_ALREADY_KNOWN, - "txn-already-known"); + return state.Invalid(false, REJECT_ALREADY_KNOWN, + "txn-already-known"); + } } // Do all inputs exist? Note that this does not check for the @@ -692,11 +695,11 @@ // only helps with filling in pfMissingInputs (to determine missing // vs spent). for (const CTxIn txin : tx.vin) { - if (!pcoinsTip->HaveCoinsInCache(txin.prevout.hash)) { + if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { vHashTxnToUncache.push_back(txin.prevout.hash); } - if (!view.HaveCoins(txin.prevout.hash)) { + if (!view.HaveCoin(txin.prevout)) { if (pfMissingInputs) { *pfMissingInputs = true; }