Skip to content

Commit e31b6f8

Browse files
refactor: extract removeProTxReferences and cover replacement eviction
The earlier fix inlined a copy of removeProTxSpentCollateralConflicts' inner loop into removeProTxConflicts, minus the diagnostic log on the should-never-happen branch. Hoist that loop into a named CTxMemPool::removeProTxReferences helper and call it from both sites, so the two paths that drop TXs naming a vanished MN cannot drift apart. Also add a removeForBlock assertion to the new test. existsProviderTxConflict only gates our own acceptance and cannot stop an attacker from mining the replacement ProRegTx themselves; the eviction hunk in removeProTxConflicts is what keeps the orphaned update from stalling our block assembly afterwards, and it previously had no coverage. Verified as a negative control: the new assertion fails (1 != 0) with that hunk disabled.
1 parent 1b1adad commit e31b6f8

3 files changed

Lines changed: 41 additions & 38 deletions

File tree

‎src/test/evo_deterministicmns_tests.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,14 @@ void FuncTestMempoolProRegReplacementUpdateConflict(TestChainSetup& setup)
13911391
testPool.addUnchecked(entry.FromTx(tx_up_serv));
13921392
BOOST_CHECK_EQUAL(testPool.size(), 1U);
13931393
BOOST_CHECK(testPool.existsProviderTxConflict(CTransaction(tx_reg_replace)));
1394+
1395+
// existsProviderTxConflict only gates our own acceptance; it cannot stop a miner from
1396+
// confirming the replacement. Once that block arrives, removeForBlock must evict the
1397+
// now-unmineable update, otherwise it lingers and stalls our own block assembly.
1398+
std::vector<CTransactionRef> connected{MakeTransactionRef(CMutableTransaction()),
1399+
MakeTransactionRef(tx_reg_replace)};
1400+
testPool.removeForBlock(connected, tip_height() + 1);
1401+
BOOST_CHECK_EQUAL(testPool.size(), 0U);
13941402
}
13951403
}
13961404

‎src/txmempool.cpp‎

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -965,41 +965,43 @@ void CTxMemPool::removeProTxCollateralConflicts(const CTransaction &tx, const CO
965965
}
966966
}
967967

968+
void CTxMemPool::removeProTxReferences(const uint256& proTxHash)
969+
{
970+
// Can't use equal_range here as every call to removeRecursive might invalidate iterators
971+
AssertLockHeld(cs);
972+
while (true) {
973+
auto it = mapProTxRefs.find(proTxHash);
974+
if (it == mapProTxRefs.end()) {
975+
break;
976+
}
977+
auto conflictIt = mapTx.find(it->second);
978+
if (conflictIt != mapTx.end()) {
979+
removeRecursive(conflictIt->GetTx(), MemPoolRemovalReason::CONFLICT);
980+
} else {
981+
// Should not happen as we track referencing TXs in addUnchecked/removeUnchecked.
982+
// But lets be on the safe side and not run into an endless loop...
983+
LogPrint(BCLog::MEMPOOL, "%s: ERROR: found invalid TX ref in mapProTxRefs, proTxHash=%s, txHash=%s\n", __func__, proTxHash.ToString(), it->second.ToString());
984+
mapProTxRefs.erase(it);
985+
}
986+
}
987+
}
988+
968989
void CTxMemPool::removeProTxSpentCollateralConflicts(const CTransaction &tx)
969990
{
970991
auto dmnman = Assert(m_dmnman.load(std::memory_order_acquire));
971992

972993
// Remove TXs that refer to a MN for which the collateral was spent
973-
auto removeSpentCollateralConflict = [&](const uint256& proTxHash) EXCLUSIVE_LOCKS_REQUIRED(cs) {
974-
// Can't use equal_range here as every call to removeRecursive might invalidate iterators
975-
AssertLockHeld(cs);
976-
while (true) {
977-
auto it = mapProTxRefs.find(proTxHash);
978-
if (it == mapProTxRefs.end()) {
979-
break;
980-
}
981-
auto conflictIt = mapTx.find(it->second);
982-
if (conflictIt != mapTx.end()) {
983-
removeRecursive(conflictIt->GetTx(), MemPoolRemovalReason::CONFLICT);
984-
} else {
985-
// Should not happen as we track referencing TXs in addUnchecked/removeUnchecked.
986-
// But lets be on the safe side and not run into an endless loop...
987-
LogPrint(BCLog::MEMPOOL, "%s: ERROR: found invalid TX ref in mapProTxRefs, proTxHash=%s, txHash=%s\n", __func__, proTxHash.ToString(), it->second.ToString());
988-
mapProTxRefs.erase(it);
989-
}
990-
}
991-
};
992994
auto mnList = dmnman->GetListAtChainTip();
993995
for (const auto& in : tx.vin) {
994996
auto collateralIt = mapProTxCollaterals.find(in.prevout);
995997
if (collateralIt != mapProTxCollaterals.end()) {
996998
// These are not yet mined ProRegTxs
997-
removeSpentCollateralConflict(collateralIt->second);
999+
removeProTxReferences(collateralIt->second);
9981000
}
9991001
auto dmn = mnList.GetMNByCollateral(in.prevout);
10001002
if (dmn) {
10011003
// These are updates referring to a mined ProRegTx
1002-
removeSpentCollateralConflict(dmn->proTxHash);
1004+
removeProTxReferences(dmn->proTxHash);
10031005
}
10041006
}
10051007
}
@@ -1050,23 +1052,12 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
10501052
}
10511053
if (!proTx.collateralOutpoint.hash.IsNull()) {
10521054
removeProTxCollateralConflicts(tx, proTx.collateralOutpoint);
1053-
// Replacement of a live MN via external-collateral reuse: drop any
1054-
// mempool updates that still target the proTxHash being replaced.
1055-
// removeProTxSpentCollateralConflicts only covers spends, not reuse.
1056-
if (auto dmn = m_dmnman.load(std::memory_order_acquire)->GetListAtChainTip().GetMNByCollateral(proTx.collateralOutpoint)) {
1057-
// Can't use equal_range + erase loop: removeRecursive may invalidate iterators.
1058-
while (true) {
1059-
auto it = mapProTxRefs.find(dmn->proTxHash);
1060-
if (it == mapProTxRefs.end()) {
1061-
break;
1062-
}
1063-
auto conflictIt = mapTx.find(it->second);
1064-
if (conflictIt != mapTx.end()) {
1065-
removeRecursive(conflictIt->GetTx(), MemPoolRemovalReason::CONFLICT);
1066-
} else {
1067-
mapProTxRefs.erase(it);
1068-
}
1069-
}
1055+
// A ProRegTx reusing an external collateral replaces the MN that collateral
1056+
// currently backs, so that MN ceases to exist. Drop any mempool update that
1057+
// still targets its proTxHash; such an update can never be mined afterwards.
1058+
// removeProTxSpentCollateralConflicts only covers collateral *spends*, not reuse.
1059+
if (auto dmn = Assert(m_dmnman.load(std::memory_order_acquire))->GetListAtChainTip().GetMNByCollateral(proTx.collateralOutpoint)) {
1060+
removeProTxReferences(dmn->proTxHash);
10701061
}
10711062
} else {
10721063
removeProTxCollateralConflicts(tx, COutPoint(tx_hash, proTx.collateralOutpoint.n));

‎src/txmempool.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,10 @@ class CTxMemPool
655655
void removeProTxPubKeyConflicts(const CTransaction &tx, const CBLSLazyPublicKey &pubKey) EXCLUSIVE_LOCKS_REQUIRED(cs);
656656
void removeProTxPlatformNodeIDConflicts(const CTransaction &tx, const uint160 &platformNodeID) EXCLUSIVE_LOCKS_REQUIRED(cs);
657657
void removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint) EXCLUSIVE_LOCKS_REQUIRED(cs);
658+
/** Remove every mempool TX that refers to proTxHash. Used when the MN it names ceases to exist
659+
* (collateral spent, or collateral reused by a replacement ProRegTx), since such TXs can never
660+
* be mined afterwards and would abort block assembly with "bad-protx-hash". */
661+
void removeProTxReferences(const uint256& proTxHash) EXCLUSIVE_LOCKS_REQUIRED(cs);
658662
void removeProTxSpentCollateralConflicts(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs);
659663
void removeProTxKeyChangedConflicts(const CTransaction &tx, const uint256& proTxHash, const uint256& newKeyHash) EXCLUSIVE_LOCKS_REQUIRED(cs);
660664
void removeProTxConflicts(const CTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs);

0 commit comments

Comments
 (0)