Skip to content

Commit 4633199

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#22677: cut the validation <-> txmempool circular dependency 2/2
a64078e Break validation <-> txmempool circular dependency (glozow) 64e4963 [mempool] always assert coin spent (glozow) bb9078e [refactor] put finality and maturity checking into a lambda (glozow) bedf246 [mempool] only update lockpoints for non-removed entries (glozow) 1b3a11e MOVEONLY: TestLockPointValidity to txmempool (glozow) Pull request description: Remove 2 circular dependencies: validation - txmempool and validation - policy/rbf - txmempool Validation should depend on txmempool (e.g. `CChainstateManager` has a mempool and we often need to know what's in our mempool to validate transactions), but txmempool is a data structure that shouldn't really need to know about chain state. - Changes `removeForReorg()` to be parameterized by a callable that returns true/false (i.e. whether the transaction should be removed due to being now immature or nonfinal) instead of a `CChainState`. The mempool really shouldn't need to know about coinbase maturity or lockpoints, it just needs to know which entries to remove. ACKs for top commit: laanwj: Code review ACK a64078e mjdietzx: reACK a64078e theStack: re-ACK a64078e Tree-SHA512: f75995200569c09dfb8ddc09729da66ddb32167ff1e8a7e72f105ec062d2d6a9a390e6b4a2a115e7ad8ad3525f891ee1503f3cd2bed11773abcaf7c3230b1136
2 parents 1c06e1a + a64078e commit 4633199

File tree

5 files changed

+70
-55
lines changed

5 files changed

+70
-55
lines changed

src/txmempool.cpp

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include <util/moneystr.h>
1717
#include <util/system.h>
1818
#include <util/time.h>
19-
#include <validation.h>
2019
#include <validationinterface.h>
2120

2221
#include <cmath>
@@ -74,6 +73,24 @@ struct update_lock_points
7473
const LockPoints& lp;
7574
};
7675

76+
bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp)
77+
{
78+
AssertLockHeld(cs_main);
79+
assert(lp);
80+
// If there are relative lock times then the maxInputBlock will be set
81+
// If there are no relative lock times, the LockPoints don't depend on the chain
82+
if (lp->maxInputBlock) {
83+
// Check whether active_chain is an extension of the block at which the LockPoints
84+
// calculation was valid. If not LockPoints are no longer valid
85+
if (!active_chain.Contains(lp->maxInputBlock)) {
86+
return false;
87+
}
88+
}
89+
90+
// LockPoints still valid
91+
return true;
92+
}
93+
7794
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
7895
int64_t time, unsigned int entry_height,
7996
bool spends_coinbase, int64_t sigops_cost, LockPoints lp)
@@ -616,44 +633,27 @@ void CTxMemPool::removeRecursive(const CTransaction &origTx, MemPoolRemovalReaso
616633
RemoveStaged(setAllRemoves, false, reason);
617634
}
618635

619-
void CTxMemPool::removeForReorg(CChainState& active_chainstate, int flags)
636+
void CTxMemPool::removeForReorg(CChain& chain, std::function<bool(txiter)> check_final_and_mature)
620637
{
621638
// Remove transactions spending a coinbase which are now immature and no-longer-final transactions
622639
AssertLockHeld(cs);
640+
AssertLockHeld(::cs_main);
641+
623642
setEntries txToRemove;
624643
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
625-
const CTransaction& tx = it->GetTx();
626-
LockPoints lp = it->GetLockPoints();
627-
bool validLP = TestLockPointValidity(active_chainstate.m_chain, &lp);
628-
CCoinsViewMemPool view_mempool(&active_chainstate.CoinsTip(), *this);
629-
if (!CheckFinalTx(active_chainstate.m_chain.Tip(), tx, flags)
630-
|| !CheckSequenceLocks(active_chainstate.m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) {
631-
// Note if CheckSequenceLocks fails the LockPoints may still be invalid
632-
// So it's critical that we remove the tx and not depend on the LockPoints.
633-
txToRemove.insert(it);
634-
} else if (it->GetSpendsCoinbase()) {
635-
for (const CTxIn& txin : tx.vin) {
636-
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
637-
if (it2 != mapTx.end())
638-
continue;
639-
const Coin &coin = active_chainstate.CoinsTip().AccessCoin(txin.prevout);
640-
if (m_check_ratio != 0) assert(!coin.IsSpent());
641-
unsigned int nMemPoolHeight = active_chainstate.m_chain.Tip()->nHeight + 1;
642-
if (coin.IsSpent() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
643-
txToRemove.insert(it);
644-
break;
645-
}
646-
}
647-
}
648-
if (!validLP) {
649-
mapTx.modify(it, update_lock_points(lp));
650-
}
644+
if (check_final_and_mature(it)) txToRemove.insert(it);
651645
}
652646
setEntries setAllRemoves;
653647
for (txiter it : txToRemove) {
654648
CalculateDescendants(it, setAllRemoves);
655649
}
656650
RemoveStaged(setAllRemoves, false, MemPoolRemovalReason::REORG);
651+
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
652+
LockPoints lp = it->GetLockPoints();
653+
if (!TestLockPointValidity(chain, &lp)) {
654+
mapTx.modify(it, update_lock_points(lp));
655+
}
656+
}
657657
}
658658

659659
void CTxMemPool::removeConflicts(const CTransaction &tx)

src/txmempool.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <utility>
1515
#include <vector>
1616

17+
#include <chain.h>
1718
#include <coins.h>
1819
#include <consensus/amount.h>
1920
#include <indirectmap.h>
@@ -49,6 +50,11 @@ struct LockPoints {
4950
CBlockIndex* maxInputBlock{nullptr};
5051
};
5152

53+
/**
54+
* Test whether the LockPoints height and time are still valid on the current chain
55+
*/
56+
bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
57+
5258
struct CompareIteratorByHash {
5359
// SFINAE for T where T is either a pointer type (e.g., a txiter) or a reference_wrapper<T>
5460
// (e.g. a wrapped CTxMemPoolEntry&)
@@ -583,7 +589,10 @@ class CTxMemPool
583589
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
584590

585591
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
586-
void removeForReorg(CChainState& active_chainstate, int flags) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
592+
/** After reorg, check if mempool entries are now non-final, premature coinbase spends, or have
593+
* invalid lockpoints. Update lockpoints and remove entries (and descendants of entries) that
594+
* are no longer valid. */
595+
void removeForReorg(CChain& chain, std::function<bool(txiter)> check_final_and_mature) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
587596
void removeConflicts(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
588597
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
589598

src/validation.cpp

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -212,24 +212,6 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i
212212
return IsFinalTx(tx, nBlockHeight, nBlockTime);
213213
}
214214

215-
bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp)
216-
{
217-
AssertLockHeld(cs_main);
218-
assert(lp);
219-
// If there are relative lock times then the maxInputBlock will be set
220-
// If there are no relative lock times, the LockPoints don't depend on the chain
221-
if (lp->maxInputBlock) {
222-
// Check whether active_chain is an extension of the block at which the LockPoints
223-
// calculation was valid. If not LockPoints are no longer valid
224-
if (!active_chain.Contains(lp->maxInputBlock)) {
225-
return false;
226-
}
227-
}
228-
229-
// LockPoints still valid
230-
return true;
231-
}
232-
233215
bool CheckSequenceLocks(CBlockIndex* tip,
234216
const CCoinsView& coins_view,
235217
const CTransaction& tx,
@@ -368,8 +350,39 @@ void CChainState::MaybeUpdateMempoolForReorg(
368350
// the disconnectpool that were added back and cleans up the mempool state.
369351
m_mempool->UpdateTransactionsFromBlock(vHashUpdate);
370352

353+
const auto check_final_and_mature = [this, flags=STANDARD_LOCKTIME_VERIFY_FLAGS](CTxMemPool::txiter it)
354+
EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main) {
355+
bool should_remove = false;
356+
AssertLockHeld(m_mempool->cs);
357+
AssertLockHeld(::cs_main);
358+
const CTransaction& tx = it->GetTx();
359+
LockPoints lp = it->GetLockPoints();
360+
bool validLP = TestLockPointValidity(m_chain, &lp);
361+
CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool);
362+
if (!CheckFinalTx(m_chain.Tip(), tx, flags)
363+
|| !CheckSequenceLocks(m_chain.Tip(), view_mempool, tx, flags, &lp, validLP)) {
364+
// Note if CheckSequenceLocks fails the LockPoints may still be invalid
365+
// So it's critical that we remove the tx and not depend on the LockPoints.
366+
should_remove = true;
367+
} else if (it->GetSpendsCoinbase()) {
368+
for (const CTxIn& txin : tx.vin) {
369+
auto it2 = m_mempool->mapTx.find(txin.prevout.hash);
370+
if (it2 != m_mempool->mapTx.end())
371+
continue;
372+
const Coin &coin = CoinsTip().AccessCoin(txin.prevout);
373+
assert(!coin.IsSpent());
374+
unsigned int nMemPoolHeight = m_chain.Tip()->nHeight + 1;
375+
if (coin.IsSpent() || (coin.IsCoinBase() && ((signed long)nMemPoolHeight) - coin.nHeight < COINBASE_MATURITY)) {
376+
should_remove = true;
377+
break;
378+
}
379+
}
380+
}
381+
return should_remove;
382+
};
383+
371384
// We also need to remove any now-immature transactions
372-
m_mempool->removeForReorg(*this, STANDARD_LOCKTIME_VERIFY_FLAGS);
385+
m_mempool->removeForReorg(m_chain, check_final_and_mature);
373386
// Re-limit mempool size, in case we added any transactions
374387
LimitMempoolSize(
375388
*m_mempool,

src/validation.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
253253
*/
254254
bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, int flags = -1) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
255255

256-
/**
257-
* Test whether the LockPoints height and time are still valid on the current chain
258-
*/
259-
bool TestLockPointValidity(CChain& active_chain, const LockPoints* lp) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
260-
261256
/**
262257
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
263258
* @param[in] tip Chain tip to check tx sequence locks against. For example,

test/lint/lint-circular-dependencies.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
1515
"index/base -> validation -> index/blockfilterindex -> index/base"
1616
"index/coinstatsindex -> node/coinstats -> index/coinstatsindex"
1717
"policy/fees -> txmempool -> policy/fees"
18-
"policy/rbf -> txmempool -> validation -> policy/rbf"
1918
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
2019
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel"
2120
"qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog"
2221
"qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel"
23-
"txmempool -> validation -> txmempool"
2422
"wallet/fees -> wallet/wallet -> wallet/fees"
2523
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
2624
"node/coinstats -> validation -> node/coinstats"

0 commit comments

Comments
 (0)