@@ -1050,6 +1050,24 @@ void CTxMemPool::removeProTxConflicts(const CTransaction &tx)
10501050 }
10511051 if (!proTx.collateralOutpoint .hash .IsNull ()) {
10521052 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+ }
1070+ }
10531071 } else {
10541072 removeProTxCollateralConflicts (tx, COutPoint (tx_hash, proTx.collateralOutpoint .n ));
10551073 }
@@ -1422,6 +1440,14 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
14221440 // there is another tx that spends the collateral
14231441 return true ;
14241442 }
1443+ // A replacement ProRegTx deletes the live MN backed by this collateral. Any
1444+ // in-mempool update that still targets that MN's proTxHash would then fail
1445+ // BuildNewListFromBlock with bad-protx-hash if both were mined in one block.
1446+ if (auto dmn = dmnman->GetListAtChainTip ().GetMNByCollateral (proTx.collateralOutpoint )) {
1447+ if (mapProTxRefs.find (dmn->proTxHash ) != mapProTxRefs.end ()) {
1448+ return true ;
1449+ }
1450+ }
14251451 }
14261452 return false ;
14271453 } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE ) {
@@ -1442,6 +1468,12 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
14421468 return true ;
14431469 }
14441470 }
1471+ // Conflict with a replacement ProRegTx that reuses this MN's external collateral.
1472+ if (auto dmn = dmnman->GetListAtChainTip ().GetMN (opt_proTx->proTxHash )) {
1473+ if (mapProTxCollaterals.count (dmn->collateralOutpoint )) {
1474+ return true ;
1475+ }
1476+ }
14451477 } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR ) {
14461478 const auto opt_proTx = GetTxPayload<CProUpRegTx>(tx);
14471479 if (!opt_proTx) {
@@ -1456,6 +1488,10 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
14561488 LogPrint (BCLog::MEMPOOL , " %s: ERROR: Masternode is not in the list, proTxHash: %s\n " , __func__, proTx.proTxHash .ToString ());
14571489 return true ; // i.e. failed to find validated ProTx == conflict
14581490 }
1491+ // Conflict with a replacement ProRegTx that reuses this MN's external collateral.
1492+ if (mapProTxCollaterals.count (dmn->collateralOutpoint )) {
1493+ return true ;
1494+ }
14591495 // only allow one operator key change in the mempool
14601496 if (dmn->pdmnState ->pubKeyOperator != proTx.pubKeyOperator ) {
14611497 if (hasKeyChangeInMempool (proTx.proTxHash )) {
@@ -1478,6 +1514,10 @@ bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const {
14781514 LogPrint (BCLog::MEMPOOL , " %s: ERROR: Masternode is not in the list, proTxHash: %s\n " , __func__, proTx.proTxHash .ToString ());
14791515 return true ; // i.e. failed to find validated ProTx == conflict
14801516 }
1517+ // Conflict with a replacement ProRegTx that reuses this MN's external collateral.
1518+ if (mapProTxCollaterals.count (dmn->collateralOutpoint )) {
1519+ return true ;
1520+ }
14811521 // only allow one operator key change in the mempool
14821522 if (dmn->pdmnState ->pubKeyOperator .Get () != CBLSPublicKey ()) {
14831523 if (hasKeyChangeInMempool (proTx.proTxHash )) {
0 commit comments