Fix mempool package tracking edge case

CalculateMemPoolAncestors was always looping over a transaction's inputs
to find in-mempool parents.  When adding a new transaction, this is the
correct behavior, but when removing a transaction, we want to use the
ancestor set that would be calculated by walking mapLinks (which should
in general be the same set, except during a reorg when the mempool is
in an inconsistent state, and the mapLinks-based calculation would be the
correct one).
This commit is contained in:
Suhas Daftuar 2015-09-23 13:37:32 -04:00
parent 598b25d5ee
commit 60de0d5826
2 changed files with 39 additions and 16 deletions

View file

@ -159,17 +159,15 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
} }
} }
bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString) bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents /* = true */)
{ {
setEntries parentHashes; setEntries parentHashes;
const CTransaction &tx = entry.GetTx(); const CTransaction &tx = entry.GetTx();
if (fSearchForParents) {
// Get parents of this transaction that are in the mempool // Get parents of this transaction that are in the mempool
// Entry may or may not already be in the mempool, and GetMemPoolParents() // GetMemPoolParents() is only valid for entries in the mempool, so we
// is only valid for entries in the mempool, so we iterate mapTx to find // iterate mapTx to find parents.
// parents.
// TODO: optimize this so that we only check limits and walk
// tx.vin when called on entries not already in the mempool.
for (unsigned int i = 0; i < tx.vin.size(); i++) { for (unsigned int i = 0; i < tx.vin.size(); i++) {
txiter piter = mapTx.find(tx.vin[i].prevout.hash); txiter piter = mapTx.find(tx.vin[i].prevout.hash);
if (piter != mapTx.end()) { if (piter != mapTx.end()) {
@ -180,6 +178,12 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntr
} }
} }
} }
} else {
// If we're not searching for parents, we require this to be an
// entry in the mempool already.
txiter it = mapTx.iterator_to(entry);
parentHashes = GetMemPoolParents(it);
}
size_t totalSizeWithAncestors = entry.GetTxSize(); size_t totalSizeWithAncestors = entry.GetTxSize();
@ -249,7 +253,24 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove)
setEntries setAncestors; setEntries setAncestors;
const CTxMemPoolEntry &entry = *removeIt; const CTxMemPoolEntry &entry = *removeIt;
std::string dummy; std::string dummy;
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); // Since this is a tx that is already in the mempool, we can call CMPA
// with fSearchForParents = false. If the mempool is in a consistent
// state, then using true or false should both be correct, though false
// should be a bit faster.
// However, if we happen to be in the middle of processing a reorg, then
// the mempool can be in an inconsistent state. In this case, the set
// of ancestors reachable via mapLinks will be the same as the set of
// ancestors whose packages include this transaction, because when we
// add a new transaction to the mempool in addUnchecked(), we assume it
// has no children, and in the case of a reorg where that assumption is
// false, the in-mempool children aren't linked to the in-block tx's
// until UpdateTransactionsFromBlock() is called.
// So if we're being called during a reorg, ie before
// UpdateTransactionsFromBlock() has been called, then mapLinks[] will
// differ from the set of mempool parents we'd calculate by searching,
// and it's important that we use the mapLinks[] notion of ancestor
// transactions as the set of things to update for removal.
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
// Note that UpdateAncestorsOf severs the child links that point to // Note that UpdateAncestorsOf severs the child links that point to
// removeIt in the entries for the parents of removeIt. This is // removeIt in the entries for the parents of removeIt. This is
// fine since we don't need to use the mempool children of any entries // fine since we don't need to use the mempool children of any entries

View file

@ -392,8 +392,10 @@ public:
* limitDescendantCount = max number of descendants any ancestor can have * limitDescendantCount = max number of descendants any ancestor can have
* limitDescendantSize = max size of descendants any ancestor can have * limitDescendantSize = max size of descendants any ancestor can have
* errString = populated with error reason if any limits are hit * errString = populated with error reason if any limits are hit
* fSearchForParents = whether to search a tx's vin for in-mempool parents, or
* look up parents from mapLinks. Must be true for entries not in the mempool
*/ */
bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString); bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents = true);
unsigned long size() unsigned long size()
{ {