Merge #7594: Mempool: Add tracking of ancestor packages

ce019bf Check all ancestor state in CTxMemPool::check() (Suhas Daftuar)
e2eeb5d Add ancestor feerate index to mempool (Suhas Daftuar)
72abd2c Add ancestor tracking to mempool (Suhas Daftuar)
76a7632 Remove work limit in UpdateForDescendants() (Suhas Daftuar)
5de2baa Rename CTxMemPool::remove -> removeRecursive (Suhas Daftuar)
7659438 CTxMemPool::removeForBlock now uses RemoveStaged (Suhas Daftuar)
This commit is contained in:
Wladimir J. van der Laan 2016-03-17 13:33:31 +01:00
commit 01f4267623
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
4 changed files with 302 additions and 124 deletions

View File

@ -1245,20 +1245,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// Save these to avoid repeated lookups // Save these to avoid repeated lookups
setIterConflicting.insert(mi); setIterConflicting.insert(mi);
// If this entry is "dirty", then we don't have descendant
// state for this transaction, which means we probably have
// lots of in-mempool descendants.
// Don't allow replacements of dirty transactions, to ensure
// that we don't spend too much time walking descendants.
// This should be rare.
if (mi->IsDirty()) {
return state.DoS(0, false,
REJECT_NONSTANDARD, "too many potential replacements", false,
strprintf("too many potential replacements: rejecting replacement %s; cannot replace tx %s with untracked descendants",
hash.ToString(),
mi->GetTx().GetHash().ToString()));
}
// Don't allow the replacement to reduce the feerate of the // Don't allow the replacement to reduce the feerate of the
// mempool. // mempool.
// //
@ -1388,7 +1374,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
FormatMoney(nModifiedFees - nConflictingFees), FormatMoney(nModifiedFees - nConflictingFees),
(int)nSize - (int)nConflictingSize); (int)nSize - (int)nConflictingSize);
} }
pool.RemoveStaged(allConflicting); pool.RemoveStaged(allConflicting, false);
// Store transaction in memory // Store transaction in memory
pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload()); pool.addUnchecked(hash, entry, setAncestors, !IsInitialBlockDownload());
@ -2553,7 +2539,7 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
list<CTransaction> removed; list<CTransaction> removed;
CValidationState stateDummy; CValidationState stateDummy;
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) { if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL, true)) {
mempool.remove(tx, removed, true); mempool.removeRecursive(tx, removed);
} else if (mempool.exists(tx.GetHash())) { } else if (mempool.exists(tx.GetHash())) {
vHashUpdate.push_back(tx.GetHash()); vHashUpdate.push_back(tx.GetHash());
} }

View File

@ -57,12 +57,12 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
std::list<CTransaction> removed; std::list<CTransaction> removed;
// Nothing in pool, remove should do nothing: // Nothing in pool, remove should do nothing:
testPool.remove(txParent, removed, true); testPool.removeRecursive(txParent, removed);
BOOST_CHECK_EQUAL(removed.size(), 0); BOOST_CHECK_EQUAL(removed.size(), 0);
// Just the parent: // Just the parent:
testPool.addUnchecked(txParent.GetHash(), entry.FromTx(txParent)); testPool.addUnchecked(txParent.GetHash(), entry.FromTx(txParent));
testPool.remove(txParent, removed, true); testPool.removeRecursive(txParent, removed);
BOOST_CHECK_EQUAL(removed.size(), 1); BOOST_CHECK_EQUAL(removed.size(), 1);
removed.clear(); removed.clear();
@ -74,16 +74,16 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
testPool.addUnchecked(txGrandChild[i].GetHash(), entry.FromTx(txGrandChild[i])); testPool.addUnchecked(txGrandChild[i].GetHash(), entry.FromTx(txGrandChild[i]));
} }
// Remove Child[0], GrandChild[0] should be removed: // Remove Child[0], GrandChild[0] should be removed:
testPool.remove(txChild[0], removed, true); testPool.removeRecursive(txChild[0], removed);
BOOST_CHECK_EQUAL(removed.size(), 2); BOOST_CHECK_EQUAL(removed.size(), 2);
removed.clear(); removed.clear();
// ... make sure grandchild and child are gone: // ... make sure grandchild and child are gone:
testPool.remove(txGrandChild[0], removed, true); testPool.removeRecursive(txGrandChild[0], removed);
BOOST_CHECK_EQUAL(removed.size(), 0); BOOST_CHECK_EQUAL(removed.size(), 0);
testPool.remove(txChild[0], removed, true); testPool.removeRecursive(txChild[0], removed);
BOOST_CHECK_EQUAL(removed.size(), 0); BOOST_CHECK_EQUAL(removed.size(), 0);
// Remove parent, all children/grandchildren should go: // Remove parent, all children/grandchildren should go:
testPool.remove(txParent, removed, true); testPool.removeRecursive(txParent, removed);
BOOST_CHECK_EQUAL(removed.size(), 5); BOOST_CHECK_EQUAL(removed.size(), 5);
BOOST_CHECK_EQUAL(testPool.size(), 0); BOOST_CHECK_EQUAL(testPool.size(), 0);
removed.clear(); removed.clear();
@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
} }
// Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be
// put into the mempool (maybe because it is non-standard): // put into the mempool (maybe because it is non-standard):
testPool.remove(txParent, removed, true); testPool.removeRecursive(txParent, removed);
BOOST_CHECK_EQUAL(removed.size(), 6); BOOST_CHECK_EQUAL(removed.size(), 6);
BOOST_CHECK_EQUAL(testPool.size(), 0); BOOST_CHECK_EQUAL(testPool.size(), 0);
removed.clear(); removed.clear();
@ -281,11 +281,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
// Now try removing tx10 and verify the sort order returns to normal // Now try removing tx10 and verify the sort order returns to normal
std::list<CTransaction> removed; std::list<CTransaction> removed;
pool.remove(pool.mapTx.find(tx10.GetHash())->GetTx(), removed, true); pool.removeRecursive(pool.mapTx.find(tx10.GetHash())->GetTx(), removed);
CheckSort<descendant_score>(pool, snapshotOrder); CheckSort<descendant_score>(pool, snapshotOrder);
pool.remove(pool.mapTx.find(tx9.GetHash())->GetTx(), removed, true); pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx(), removed);
pool.remove(pool.mapTx.find(tx8.GetHash())->GetTx(), removed, true); pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx(), removed);
/* Now check the sort on the mining score index. /* Now check the sort on the mining score index.
* Final order should be: * Final order should be:
* *
@ -317,6 +317,110 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
CheckSort<mining_score>(pool, sortedOrder); CheckSort<mining_score>(pool, sortedOrder);
} }
BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
{
CTxMemPool pool(CFeeRate(0));
TestMemPoolEntryHelper entry;
entry.hadNoDependencies = true;
/* 3rd highest fee */
CMutableTransaction tx1 = CMutableTransaction();
tx1.vout.resize(1);
tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx1.vout[0].nValue = 10 * COIN;
pool.addUnchecked(tx1.GetHash(), entry.Fee(10000LL).Priority(10.0).FromTx(tx1));
/* highest fee */
CMutableTransaction tx2 = CMutableTransaction();
tx2.vout.resize(1);
tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx2.vout[0].nValue = 2 * COIN;
pool.addUnchecked(tx2.GetHash(), entry.Fee(20000LL).Priority(9.0).FromTx(tx2));
uint64_t tx2Size = ::GetSerializeSize(tx2, SER_NETWORK, PROTOCOL_VERSION);
/* lowest fee */
CMutableTransaction tx3 = CMutableTransaction();
tx3.vout.resize(1);
tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx3.vout[0].nValue = 5 * COIN;
pool.addUnchecked(tx3.GetHash(), entry.Fee(0LL).Priority(100.0).FromTx(tx3));
/* 2nd highest fee */
CMutableTransaction tx4 = CMutableTransaction();
tx4.vout.resize(1);
tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx4.vout[0].nValue = 6 * COIN;
pool.addUnchecked(tx4.GetHash(), entry.Fee(15000LL).Priority(1.0).FromTx(tx4));
/* equal fee rate to tx1, but newer */
CMutableTransaction tx5 = CMutableTransaction();
tx5.vout.resize(1);
tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx5.vout[0].nValue = 11 * COIN;
pool.addUnchecked(tx5.GetHash(), entry.Fee(10000LL).FromTx(tx5));
BOOST_CHECK_EQUAL(pool.size(), 5);
std::vector<std::string> sortedOrder;
sortedOrder.resize(5);
sortedOrder[0] = tx2.GetHash().ToString(); // 20000
sortedOrder[1] = tx4.GetHash().ToString(); // 15000
// tx1 and tx5 are both 10000
// Ties are broken by hash, not timestamp, so determine which
// hash comes first.
if (tx1.GetHash() < tx5.GetHash()) {
sortedOrder[2] = tx1.GetHash().ToString();
sortedOrder[3] = tx5.GetHash().ToString();
} else {
sortedOrder[2] = tx5.GetHash().ToString();
sortedOrder[3] = tx1.GetHash().ToString();
}
sortedOrder[4] = tx3.GetHash().ToString(); // 0
CheckSort<ancestor_score>(pool, sortedOrder);
/* low fee parent with high fee child */
/* tx6 (0) -> tx7 (high) */
CMutableTransaction tx6 = CMutableTransaction();
tx6.vout.resize(1);
tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx6.vout[0].nValue = 20 * COIN;
uint64_t tx6Size = ::GetSerializeSize(tx6, SER_NETWORK, PROTOCOL_VERSION);
pool.addUnchecked(tx6.GetHash(), entry.Fee(0LL).FromTx(tx6));
BOOST_CHECK_EQUAL(pool.size(), 6);
sortedOrder.push_back(tx6.GetHash().ToString());
CheckSort<ancestor_score>(pool, sortedOrder);
CMutableTransaction tx7 = CMutableTransaction();
tx7.vin.resize(1);
tx7.vin[0].prevout = COutPoint(tx6.GetHash(), 0);
tx7.vin[0].scriptSig = CScript() << OP_11;
tx7.vout.resize(1);
tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx7.vout[0].nValue = 10 * COIN;
uint64_t tx7Size = ::GetSerializeSize(tx7, SER_NETWORK, PROTOCOL_VERSION);
/* set the fee to just below tx2's feerate when including ancestor */
CAmount fee = (20000/tx2Size)*(tx7Size + tx6Size) - 1;
//CTxMemPoolEntry entry7(tx7, fee, 2, 10.0, 1, true);
pool.addUnchecked(tx7.GetHash(), entry.Fee(fee).FromTx(tx7));
BOOST_CHECK_EQUAL(pool.size(), 7);
sortedOrder.insert(sortedOrder.begin()+1, tx7.GetHash().ToString());
CheckSort<ancestor_score>(pool, sortedOrder);
/* after tx6 is mined, tx7 should move up in the sort */
std::vector<CTransaction> vtx;
vtx.push_back(tx6);
std::list<CTransaction> dummy;
pool.removeForBlock(vtx, 1, dummy, false);
sortedOrder.erase(sortedOrder.begin()+1);
sortedOrder.pop_back();
sortedOrder.insert(sortedOrder.begin(), tx7.GetHash().ToString());
CheckSort<ancestor_score>(pool, sortedOrder);
}
BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
{ {

View File

@ -38,6 +38,11 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
assert(inChainInputValue <= nValueIn); assert(inChainInputValue <= nValueIn);
feeDelta = 0; feeDelta = 0;
nCountWithAncestors = 1;
nSizeWithAncestors = nTxSize;
nModFeesWithAncestors = nFee;
nSigOpCountWithAncestors = sigOpCount;
} }
CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other) CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other)
@ -58,6 +63,7 @@ CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const
void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta) void CTxMemPoolEntry::UpdateFeeDelta(int64_t newFeeDelta)
{ {
nModFeesWithDescendants += newFeeDelta - feeDelta; nModFeesWithDescendants += newFeeDelta - feeDelta;
nModFeesWithAncestors += newFeeDelta - feeDelta;
feeDelta = newFeeDelta; feeDelta = newFeeDelta;
} }
@ -69,21 +75,13 @@ void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp)
// Update the given tx for any in-mempool descendants. // Update the given tx for any in-mempool descendants.
// Assumes that setMemPoolChildren is correct for the given tx and all // Assumes that setMemPoolChildren is correct for the given tx and all
// descendants. // descendants.
bool CTxMemPool::UpdateForDescendants(txiter updateIt, int maxDescendantsToVisit, cacheMap &cachedDescendants, const std::set<uint256> &setExclude) void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, const std::set<uint256> &setExclude)
{ {
// Track the number of entries (outside setExclude) that we'd need to visit
// (will bail out if it exceeds maxDescendantsToVisit)
int nChildrenToVisit = 0;
setEntries stageEntries, setAllDescendants; setEntries stageEntries, setAllDescendants;
stageEntries = GetMemPoolChildren(updateIt); stageEntries = GetMemPoolChildren(updateIt);
while (!stageEntries.empty()) { while (!stageEntries.empty()) {
const txiter cit = *stageEntries.begin(); const txiter cit = *stageEntries.begin();
if (cit->IsDirty()) {
// Don't consider any more children if any descendant is dirty
return false;
}
setAllDescendants.insert(cit); setAllDescendants.insert(cit);
stageEntries.erase(cit); stageEntries.erase(cit);
const setEntries &setChildren = GetMemPoolChildren(cit); const setEntries &setChildren = GetMemPoolChildren(cit);
@ -93,22 +91,11 @@ bool CTxMemPool::UpdateForDescendants(txiter updateIt, int maxDescendantsToVisit
// We've already calculated this one, just add the entries for this set // We've already calculated this one, just add the entries for this set
// but don't traverse again. // but don't traverse again.
BOOST_FOREACH(const txiter cacheEntry, cacheIt->second) { BOOST_FOREACH(const txiter cacheEntry, cacheIt->second) {
// update visit count only for new child transactions setAllDescendants.insert(cacheEntry);
// (outside of setExclude and stageEntries)
if (setAllDescendants.insert(cacheEntry).second &&
!setExclude.count(cacheEntry->GetTx().GetHash()) &&
!stageEntries.count(cacheEntry)) {
nChildrenToVisit++;
}
} }
} else if (!setAllDescendants.count(childEntry)) { } else if (!setAllDescendants.count(childEntry)) {
// Schedule for later processing and update our visit count // Schedule for later processing
if (stageEntries.insert(childEntry).second && !setExclude.count(childEntry->GetTx().GetHash())) { stageEntries.insert(childEntry);
nChildrenToVisit++;
}
}
if (nChildrenToVisit > maxDescendantsToVisit) {
return false;
} }
} }
} }
@ -123,16 +110,18 @@ bool CTxMemPool::UpdateForDescendants(txiter updateIt, int maxDescendantsToVisit
modifyFee += cit->GetModifiedFee(); modifyFee += cit->GetModifiedFee();
modifyCount++; modifyCount++;
cachedDescendants[updateIt].insert(cit); cachedDescendants[updateIt].insert(cit);
// Update ancestor state for each descendant
mapTx.modify(cit, update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCount()));
} }
} }
mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount)); mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount));
return true;
} }
// vHashesToUpdate is the set of transaction hashes from a disconnected block // vHashesToUpdate is the set of transaction hashes from a disconnected block
// which has been re-added to the mempool. // which has been re-added to the mempool.
// for each entry, look for descendants that are outside hashesToUpdate, and // for each entry, look for descendants that are outside hashesToUpdate, and
// add fee/size information for such descendants to the parent. // add fee/size information for such descendants to the parent.
// for each such descendant, also update the ancestor state to include the parent.
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate) void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate)
{ {
LOCK(cs); LOCK(cs);
@ -172,14 +161,11 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
UpdateParent(childIter, it, true); UpdateParent(childIter, it, true);
} }
} }
if (!UpdateForDescendants(it, 100, mapMemPoolDescendantsToUpdate, setAlreadyIncluded)) { UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded);
// Mark as dirty if we can't do the calculation.
mapTx.modify(it, set_dirty());
}
} }
} }
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 */) 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 */) const
{ {
setEntries parentHashes; setEntries parentHashes;
const CTransaction &tx = entry.GetTx(); const CTransaction &tx = entry.GetTx();
@ -256,6 +242,20 @@ void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors
} }
} }
void CTxMemPool::UpdateEntryForAncestors(txiter it, const setEntries &setAncestors)
{
int64_t updateCount = setAncestors.size();
int64_t updateSize = 0;
CAmount updateFee = 0;
int updateSigOps = 0;
BOOST_FOREACH(txiter ancestorIt, setAncestors) {
updateSize += ancestorIt->GetTxSize();
updateFee += ancestorIt->GetModifiedFee();
updateSigOps += ancestorIt->GetSigOpCount();
}
mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, updateSigOps));
}
void CTxMemPool::UpdateChildrenForRemoval(txiter it) void CTxMemPool::UpdateChildrenForRemoval(txiter it)
{ {
const setEntries &setMemPoolChildren = GetMemPoolChildren(it); const setEntries &setMemPoolChildren = GetMemPoolChildren(it);
@ -264,11 +264,30 @@ void CTxMemPool::UpdateChildrenForRemoval(txiter it)
} }
} }
void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove) void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants)
{ {
// For each entry, walk back all ancestors and decrement size associated with this // For each entry, walk back all ancestors and decrement size associated with this
// transaction // transaction
const uint64_t nNoLimit = std::numeric_limits<uint64_t>::max(); const uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
if (updateDescendants) {
// updateDescendants should be true whenever we're not recursively
// removing a tx and all its descendants, eg when a transaction is
// confirmed in a block.
// Here we only update statistics and not data in mapLinks (which
// we need to preserve until we're finished with all operations that
// need to traverse the mempool).
BOOST_FOREACH(txiter removeIt, entriesToRemove) {
setEntries setDescendants;
CalculateDescendants(removeIt, setDescendants);
setDescendants.erase(removeIt); // don't update state for self
int64_t modifySize = -((int64_t)removeIt->GetTxSize());
CAmount modifyFee = -removeIt->GetModifiedFee();
int modifySigOps = -removeIt->GetSigOpCount();
BOOST_FOREACH(txiter dit, setDescendants) {
mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, -1, modifySigOps));
}
}
}
BOOST_FOREACH(txiter removeIt, entriesToRemove) { BOOST_FOREACH(txiter removeIt, entriesToRemove) {
setEntries setAncestors; setEntries setAncestors;
const CTxMemPoolEntry &entry = *removeIt; const CTxMemPoolEntry &entry = *removeIt;
@ -292,10 +311,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove)
// transactions as the set of things to update for removal. // transactions as the set of things to update for removal.
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false); 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.
// fine since we don't need to use the mempool children of any entries
// to walk back over our ancestors (but we do need the mempool
// parents!)
UpdateAncestorsOf(false, removeIt, setAncestors); UpdateAncestorsOf(false, removeIt, setAncestors);
} }
// After updating all the ancestor sizes, we can now sever the link between each // After updating all the ancestor sizes, we can now sever the link between each
@ -306,22 +322,24 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove)
} }
} }
void CTxMemPoolEntry::SetDirty() void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount)
{ {
nCountWithDescendants = 0; nSizeWithDescendants += modifySize;
nSizeWithDescendants = nTxSize; assert(int64_t(nSizeWithDescendants) > 0);
nModFeesWithDescendants = GetModifiedFee(); nModFeesWithDescendants += modifyFee;
nCountWithDescendants += modifyCount;
assert(int64_t(nCountWithDescendants) > 0);
} }
void CTxMemPoolEntry::UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount) void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int modifySigOps)
{ {
if (!IsDirty()) { nSizeWithAncestors += modifySize;
nSizeWithDescendants += modifySize; assert(int64_t(nSizeWithAncestors) > 0);
assert(int64_t(nSizeWithDescendants) > 0); nModFeesWithAncestors += modifyFee;
nModFeesWithDescendants += modifyFee; nCountWithAncestors += modifyCount;
nCountWithDescendants += modifyCount; assert(int64_t(nCountWithAncestors) > 0);
assert(int64_t(nCountWithDescendants) > 0); nSigOpCountWithAncestors += modifySigOps;
} assert(int(nSigOpCountWithAncestors) >= 0);
} }
CTxMemPool::CTxMemPool(const CFeeRate& _minReasonableRelayFee) : CTxMemPool::CTxMemPool(const CFeeRate& _minReasonableRelayFee) :
@ -414,6 +432,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
} }
} }
UpdateAncestorsOf(true, newit, setAncestors); UpdateAncestorsOf(true, newit, setAncestors);
UpdateEntryForAncestors(newit, setAncestors);
nTransactionsUpdated++; nTransactionsUpdated++;
totalTxSize += entry.GetTxSize(); totalTxSize += entry.GetTxSize();
@ -466,7 +485,7 @@ void CTxMemPool::CalculateDescendants(txiter entryit, setEntries &setDescendants
} }
} }
void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& removed, bool fRecursive) void CTxMemPool::removeRecursive(const CTransaction &origTx, std::list<CTransaction>& removed)
{ {
// Remove transaction from memory pool // Remove transaction from memory pool
{ {
@ -475,8 +494,8 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
txiter origit = mapTx.find(origTx.GetHash()); txiter origit = mapTx.find(origTx.GetHash());
if (origit != mapTx.end()) { if (origit != mapTx.end()) {
txToRemove.insert(origit); txToRemove.insert(origit);
} else if (fRecursive) { } else {
// If recursively removing but origTx isn't in the mempool // When recursively removing but origTx isn't in the mempool
// be sure to remove any children that are in the pool. This can // 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 // happen during chain re-orgs if origTx isn't re-accepted into
// the mempool for any reason. // the mempool for any reason.
@ -490,17 +509,13 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
} }
} }
setEntries setAllRemoves; setEntries setAllRemoves;
if (fRecursive) { BOOST_FOREACH(txiter it, txToRemove) {
BOOST_FOREACH(txiter it, txToRemove) { CalculateDescendants(it, setAllRemoves);
CalculateDescendants(it, setAllRemoves);
}
} else {
setAllRemoves.swap(txToRemove);
} }
BOOST_FOREACH(txiter it, setAllRemoves) { BOOST_FOREACH(txiter it, setAllRemoves) {
removed.push_back(it->GetTx()); removed.push_back(it->GetTx());
} }
RemoveStaged(setAllRemoves); RemoveStaged(setAllRemoves, false);
} }
} }
@ -536,7 +551,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
} }
BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) { BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) {
list<CTransaction> removed; list<CTransaction> removed;
remove(tx, removed, true); removeRecursive(tx, removed);
} }
} }
@ -551,7 +566,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>
const CTransaction &txConflict = *it->second.ptx; const CTransaction &txConflict = *it->second.ptx;
if (txConflict != tx) if (txConflict != tx)
{ {
remove(txConflict, removed, true); removeRecursive(txConflict, removed);
ClearPrioritisation(txConflict.GetHash()); ClearPrioritisation(txConflict.GetHash());
} }
} }
@ -576,8 +591,12 @@ void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned i
} }
BOOST_FOREACH(const CTransaction& tx, vtx) BOOST_FOREACH(const CTransaction& tx, vtx)
{ {
std::list<CTransaction> dummy; txiter it = mapTx.find(tx.GetHash());
remove(tx, dummy, false); if (it != mapTx.end()) {
setEntries stage;
stage.insert(it);
RemoveStaged(stage, true);
}
removeConflicts(tx, conflicts); removeConflicts(tx, conflicts);
ClearPrioritisation(tx.GetHash()); ClearPrioritisation(tx.GetHash());
} }
@ -634,6 +653,8 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
innerUsage += memusage::DynamicUsage(links.parents) + memusage::DynamicUsage(links.children); innerUsage += memusage::DynamicUsage(links.parents) + memusage::DynamicUsage(links.children);
bool fDependsWait = false; bool fDependsWait = false;
setEntries setParentCheck; setEntries setParentCheck;
int64_t parentSizes = 0;
unsigned int parentSigOpCount = 0;
BOOST_FOREACH(const CTxIn &txin, tx.vin) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
// Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's.
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
@ -641,7 +662,10 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
const CTransaction& tx2 = it2->GetTx(); const CTransaction& tx2 = it2->GetTx();
assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull()); assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull());
fDependsWait = true; fDependsWait = true;
setParentCheck.insert(it2); if (setParentCheck.insert(it2).second) {
parentSizes += it2->GetTxSize();
parentSigOpCount += it2->GetSigOpCount();
}
} else { } else {
const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash); const CCoins* coins = pcoins->AccessCoins(txin.prevout.hash);
assert(coins && coins->IsAvailable(txin.prevout.n)); assert(coins && coins->IsAvailable(txin.prevout.n));
@ -654,28 +678,42 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
i++; i++;
} }
assert(setParentCheck == GetMemPoolParents(it)); assert(setParentCheck == GetMemPoolParents(it));
// Verify ancestor state is correct.
setEntries setAncestors;
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
std::string dummy;
CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy);
uint64_t nCountCheck = setAncestors.size() + 1;
uint64_t nSizeCheck = it->GetTxSize();
CAmount nFeesCheck = it->GetModifiedFee();
unsigned int nSigOpCheck = it->GetSigOpCount();
BOOST_FOREACH(txiter ancestorIt, setAncestors) {
nSizeCheck += ancestorIt->GetTxSize();
nFeesCheck += ancestorIt->GetModifiedFee();
nSigOpCheck += ancestorIt->GetSigOpCount();
}
assert(it->GetCountWithAncestors() == nCountCheck);
assert(it->GetSizeWithAncestors() == nSizeCheck);
assert(it->GetSigOpCountWithAncestors() == nSigOpCheck);
assert(it->GetModFeesWithAncestors() == nFeesCheck);
// Check children against mapNextTx // Check children against mapNextTx
CTxMemPool::setEntries setChildrenCheck; CTxMemPool::setEntries setChildrenCheck;
std::map<COutPoint, CInPoint>::const_iterator iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0)); std::map<COutPoint, CInPoint>::const_iterator iter = mapNextTx.lower_bound(COutPoint(it->GetTx().GetHash(), 0));
int64_t childSizes = 0; int64_t childSizes = 0;
CAmount childModFee = 0;
for (; iter != mapNextTx.end() && iter->first.hash == it->GetTx().GetHash(); ++iter) { for (; iter != mapNextTx.end() && iter->first.hash == it->GetTx().GetHash(); ++iter) {
txiter childit = mapTx.find(iter->second.ptx->GetHash()); txiter childit = mapTx.find(iter->second.ptx->GetHash());
assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions assert(childit != mapTx.end()); // mapNextTx points to in-mempool transactions
if (setChildrenCheck.insert(childit).second) { if (setChildrenCheck.insert(childit).second) {
childSizes += childit->GetTxSize(); childSizes += childit->GetTxSize();
childModFee += childit->GetModifiedFee();
} }
} }
assert(setChildrenCheck == GetMemPoolChildren(it)); assert(setChildrenCheck == GetMemPoolChildren(it));
// Also check to make sure size is greater than sum with immediate children. // Also check to make sure size is greater than sum with immediate children.
// just a sanity check, not definitive that this calc is correct... // just a sanity check, not definitive that this calc is correct...
if (!it->IsDirty()) { assert(it->GetSizeWithDescendants() >= childSizes + it->GetTxSize());
assert(it->GetSizeWithDescendants() >= childSizes + it->GetTxSize());
} else {
assert(it->GetSizeWithDescendants() == it->GetTxSize());
assert(it->GetModFeesWithDescendants() == it->GetModifiedFee());
}
if (fDependsWait) if (fDependsWait)
waitingOnDependants.push_back(&(*it)); waitingOnDependants.push_back(&(*it));
@ -857,13 +895,13 @@ bool CCoinsViewMemPool::HaveCoins(const uint256 &txid) const {
size_t CTxMemPool::DynamicMemoryUsage() const { size_t CTxMemPool::DynamicMemoryUsage() const {
LOCK(cs); LOCK(cs);
// Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. // 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) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage; return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + cachedInnerUsage;
} }
void CTxMemPool::RemoveStaged(setEntries &stage) { void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants) {
AssertLockHeld(cs); AssertLockHeld(cs);
UpdateForRemoveFromMempool(stage); UpdateForRemoveFromMempool(stage, updateDescendants);
BOOST_FOREACH(const txiter& it, stage) { BOOST_FOREACH(const txiter& it, stage) {
removeUnchecked(it); removeUnchecked(it);
} }
@ -881,7 +919,7 @@ int CTxMemPool::Expire(int64_t time) {
BOOST_FOREACH(txiter removeit, toremove) { BOOST_FOREACH(txiter removeit, toremove) {
CalculateDescendants(removeit, stage); CalculateDescendants(removeit, stage);
} }
RemoveStaged(stage); RemoveStaged(stage, false);
return stage.size(); return stage.size();
} }
@ -990,7 +1028,7 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRe
BOOST_FOREACH(txiter it, stage) BOOST_FOREACH(txiter it, stage)
txn.push_back(it->GetTx()); txn.push_back(it->GetTx());
} }
RemoveStaged(stage); RemoveStaged(stage, false);
if (pvNoSpendsRemaining) { if (pvNoSpendsRemaining) {
BOOST_FOREACH(const CTransaction& tx, txn) { BOOST_FOREACH(const CTransaction& tx, txn) {
BOOST_FOREACH(const CTxIn& txin, tx.vin) { BOOST_FOREACH(const CTxIn& txin, tx.vin) {

View File

@ -97,6 +97,12 @@ private:
uint64_t nSizeWithDescendants; //! ... and size uint64_t nSizeWithDescendants; //! ... and size
CAmount nModFeesWithDescendants; //! ... and total fees (all including us) CAmount nModFeesWithDescendants; //! ... and total fees (all including us)
// Analogous statistics for ancestor transactions
uint64_t nCountWithAncestors;
uint64_t nSizeWithAncestors;
CAmount nModFeesWithAncestors;
unsigned int nSigOpCountWithAncestors;
public: public:
CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee, CTxMemPoolEntry(const CTransaction& _tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight, int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
@ -121,25 +127,25 @@ public:
const LockPoints& GetLockPoints() const { return lockPoints; } const LockPoints& GetLockPoints() const { return lockPoints; }
// Adjusts the descendant state, if this entry is not dirty. // Adjusts the descendant state, if this entry is not dirty.
void UpdateState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount); void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
// Adjusts the ancestor state
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int modifySigOps);
// Updates the fee delta used for mining priority score, and the // Updates the fee delta used for mining priority score, and the
// modified fees with descendants. // modified fees with descendants.
void UpdateFeeDelta(int64_t feeDelta); void UpdateFeeDelta(int64_t feeDelta);
// Update the LockPoints after a reorg // Update the LockPoints after a reorg
void UpdateLockPoints(const LockPoints& lp); void UpdateLockPoints(const LockPoints& lp);
/** We can set the entry to be dirty if doing the full calculation of in-
* mempool descendants will be too expensive, which can potentially happen
* when re-adding transactions from a block back to the mempool.
*/
void SetDirty();
bool IsDirty() const { return nCountWithDescendants == 0; }
uint64_t GetCountWithDescendants() const { return nCountWithDescendants; } uint64_t GetCountWithDescendants() const { return nCountWithDescendants; }
uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; } uint64_t GetSizeWithDescendants() const { return nSizeWithDescendants; }
CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; } CAmount GetModFeesWithDescendants() const { return nModFeesWithDescendants; }
bool GetSpendsCoinbase() const { return spendsCoinbase; } bool GetSpendsCoinbase() const { return spendsCoinbase; }
uint64_t GetCountWithAncestors() const { return nCountWithAncestors; }
uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; }
CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; }
unsigned int GetSigOpCountWithAncestors() const { return nSigOpCountWithAncestors; }
}; };
// Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index.
@ -150,7 +156,7 @@ struct update_descendant_state
{} {}
void operator() (CTxMemPoolEntry &e) void operator() (CTxMemPoolEntry &e)
{ e.UpdateState(modifySize, modifyFee, modifyCount); } { e.UpdateDescendantState(modifySize, modifyFee, modifyCount); }
private: private:
int64_t modifySize; int64_t modifySize;
@ -158,10 +164,20 @@ struct update_descendant_state
int64_t modifyCount; int64_t modifyCount;
}; };
struct set_dirty struct update_ancestor_state
{ {
update_ancestor_state(int64_t _modifySize, CAmount _modifyFee, int64_t _modifyCount, int _modifySigOps) :
modifySize(_modifySize), modifyFee(_modifyFee), modifyCount(_modifyCount), modifySigOps(_modifySigOps)
{}
void operator() (CTxMemPoolEntry &e) void operator() (CTxMemPoolEntry &e)
{ e.SetDirty(); } { e.UpdateAncestorState(modifySize, modifyFee, modifyCount, modifySigOps); }
private:
int64_t modifySize;
CAmount modifyFee;
int64_t modifyCount;
int modifySigOps;
}; };
struct update_fee_delta struct update_fee_delta
@ -258,10 +274,34 @@ public:
} }
}; };
class CompareTxMemPoolEntryByAncestorFee
{
public:
bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b)
{
double aFees = a.GetModFeesWithAncestors();
double aSize = a.GetSizeWithAncestors();
double bFees = b.GetModFeesWithAncestors();
double bSize = b.GetSizeWithAncestors();
// Avoid division by rewriting (a/b > c/d) as (a*d > c*b).
double f1 = aFees * bSize;
double f2 = aSize * bFees;
if (f1 == f2) {
return a.GetTx().GetHash() < b.GetTx().GetHash();
}
return f1 > f2;
}
};
// Multi_index tag names // Multi_index tag names
struct descendant_score {}; struct descendant_score {};
struct entry_time {}; struct entry_time {};
struct mining_score {}; struct mining_score {};
struct ancestor_score {};
class CBlockPolicyEstimator; class CBlockPolicyEstimator;
@ -394,12 +434,18 @@ public:
boost::multi_index::tag<entry_time>, boost::multi_index::tag<entry_time>,
boost::multi_index::identity<CTxMemPoolEntry>, boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByEntryTime CompareTxMemPoolEntryByEntryTime
>, >,
// sorted by score (for mining prioritization) // sorted by score (for mining prioritization)
boost::multi_index::ordered_unique< boost::multi_index::ordered_unique<
boost::multi_index::tag<mining_score>, boost::multi_index::tag<mining_score>,
boost::multi_index::identity<CTxMemPoolEntry>, boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByScore CompareTxMemPoolEntryByScore
>,
// sorted by fee rate with ancestors
boost::multi_index::ordered_non_unique<
boost::multi_index::tag<ancestor_score>,
boost::multi_index::identity<CTxMemPoolEntry>,
CompareTxMemPoolEntryByAncestorFee
> >
> >
> indexed_transaction_set; > indexed_transaction_set;
@ -458,7 +504,7 @@ public:
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true); bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool fCurrentEstimate = true); bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool fCurrentEstimate = true);
void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false); void removeRecursive(const CTransaction &tx, std::list<CTransaction>& removed);
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed); void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight, void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
@ -483,8 +529,12 @@ public:
public: public:
/** Remove a set of transactions from the mempool. /** Remove a set of transactions from the mempool.
* If a transaction is in this set, then all in-mempool descendants must * If a transaction is in this set, then all in-mempool descendants must
* also be in the set.*/ * also be in the set, unless this transaction is being removed for being
void RemoveStaged(setEntries &stage); * in a block.
* Set updateDescendants to true when removing a tx that was in a block, so
* that any in-mempool descendants have their ancestor state updated.
*/
void RemoveStaged(setEntries &stage, bool updateDescendants);
/** When adding transactions from a disconnected block back to the mempool, /** When adding transactions from a disconnected block back to the mempool,
* new mempool entries may have children in the mempool (which is generally * new mempool entries may have children in the mempool (which is generally
@ -507,7 +557,7 @@ public:
* fSearchForParents = whether to search a tx's vin for in-mempool parents, or * 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 * 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 fSearchForParents = true); 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) const;
/** Populate setDescendants with all in-mempool descendants of hash. /** Populate setDescendants with all in-mempool descendants of hash.
* Assumes that setDescendants includes all in-mempool descendants of anything * Assumes that setDescendants includes all in-mempool descendants of anything
@ -585,21 +635,21 @@ private:
* updated and hence their state is already reflected in the parent * updated and hence their state is already reflected in the parent
* state). * state).
* *
* If updating an entry requires looking at more than maxDescendantsToVisit
* transactions, outside of the ones in setExclude, then give up.
*
* cachedDescendants will be updated with the descendants of the transaction * cachedDescendants will be updated with the descendants of the transaction
* being updated, so that future invocations don't need to walk the * being updated, so that future invocations don't need to walk the
* same transaction again, if encountered in another transaction chain. * same transaction again, if encountered in another transaction chain.
*/ */
bool UpdateForDescendants(txiter updateIt, void UpdateForDescendants(txiter updateIt,
int maxDescendantsToVisit,
cacheMap &cachedDescendants, cacheMap &cachedDescendants,
const std::set<uint256> &setExclude); const std::set<uint256> &setExclude);
/** Update ancestors of hash to add/remove it as a descendant transaction. */ /** Update ancestors of hash to add/remove it as a descendant transaction. */
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors); void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors);
/** For each transaction being removed, update ancestors and any direct children. */ /** Set ancestor state for an entry */
void UpdateForRemoveFromMempool(const setEntries &entriesToRemove); void UpdateEntryForAncestors(txiter it, const setEntries &setAncestors);
/** For each transaction being removed, update ancestors and any direct children.
* If updateDescendants is true, then also update in-mempool descendants'
* ancestor state. */
void UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants);
/** Sever link between specified transaction and direct children. */ /** Sever link between specified transaction and direct children. */
void UpdateChildrenForRemoval(txiter entry); void UpdateChildrenForRemoval(txiter entry);