fix(mempool): conflict ProRegTx collateral reuse with in-mempool masternode updates - #7489
Draft
PastaPastaPasta wants to merge 3 commits into
Draft
fix(mempool): conflict ProRegTx collateral reuse with in-mempool masternode updates#7489PastaPastaPasta wants to merge 3 commits into
PastaPastaPasta wants to merge 3 commits into
Conversation
A ProRegTx that reuses a confirmed external collateral deletes the live MN mid-block. The same-block update for that proTxHash then fails BuildNewListFromBlock with bad-protx-hash and aborts CreateNewBlock. Assert the in-block hazard and that existsProviderTxConflict must reject either ordering in the mempool.
existsProviderTxConflict now links a replacement ProRegTx that reuses a live external collateral to any in-mempool ProUpServ/ProUpReg/ProUpRev for the MN being replaced, so both cannot coexist and CreateNewBlock cannot package the bad-protx-hash ordering. removeProTxConflicts also drops those updates when such a replacement is mined. Consensus block acceptance is unchanged; only mempool packaging/eviction is tightened.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue being fixed or feature implemented
A ProRegTx that reuses an external collateral replaces the masternode that collateral
currently backs — the old masternode ceases to exist and its
proTxHashbecomes unknown.The mempool does not model that. It tracks collateral spends
(
removeProTxSpentCollateralConflicts), but reuse is not a spend, so:masternode being replaced can sit in the mempool at the same time.
BuildNewListFromBlock()deletes the masternode whenit applies the ProRegTx, then fails
bad-protx-hashon the update.The same hazard exists when the replacement ProRegTx arrives in a block rather than through
the mempool: the stale update is left behind and can never be mined.
What was done?
Three commits.
1. Mempool admission (
existsProviderTxConflict) — the two directions are nowsymmetric:
references the incumbent masternode's
proTxHash.masternode's collateral outpoint.
This is first-in-wins with no RBF-style tiebreak. That is a deliberate policy choice: both
transactions are individually valid, and the only requirement is that they not coexist.
Preferring the incumbent would mean evicting an already-accepted transaction on arrival of a
new one, which is a bigger policy change than this problem warrants.
2. Eviction on block connect (
removeProTxConflicts) — when a collateral-reusingProRegTx is mined, any mempool transaction still targeting the replaced masternode is
removed. This covers the case where the ProRegTx never passed through this node's mempool,
so the admission check above never ran.
3. Refactor — the removal loop inside
removeProTxSpentCollateralConflictsis extractedas
CTxMemPool::removeProTxReferences()and reused by both call sites. No behaviour change;it just avoids a second copy of the iterator-invalidation-safe loop.
One thing reviewers should look at
The eviction in (2) calls
dmnman->GetListAtChainTip()and needs it to resolve to the listat the previous tip, so
GetMNByCollateral()returns the masternode being replaced ratherthan its replacement. That holds because
CDeterministicMNManager::tipIndexis assigned inUpdatedBlockTip(), which fires fromActivateBestChain()(validation.cpp:3339-3340) —after
ConnectTip()callsremoveForBlock()(validation.cpp:3025).removeProTxSpentCollateralConflicts()already depends on this ordering, so the new code isconsistent with the file. The difference is that there it is an optimisation, whereas here it
is load-bearing: if the ordering changed,
GetMNByCollateral()would return the replacementmasternode and the eviction would silently become a no-op. I have noted the dependency in a
comment, but flagging it explicitly since it is the least obvious part of the change.
How Has This Been Tested?
test_mempool_proreg_replacement_update_conflictinsrc/test/evo_deterministicmns_tests.cpp, added in the commit preceding the fix. Itcovers both paths:
existsProviderTxConflict()in both directions;removeForBlock()with a block containing the replacement ProRegTx.Without the fix the admission assertions fail (2 failures) — the conflicting pair is
accepted. With the fix,
evo_dip3_activation_testspasses (19 cases).Built and run on macOS/arm64 against current
develop.Breaking Changes
Mempool policy only; no consensus rules change. Some transaction pairs that were previously
accepted together are now mutually exclusive in the mempool. Neither could have been mined
together, so nothing that was previously minable is rejected.
Known follow-up
Coverage is C++ unit level only. A functional test exercising the miner path end to end
would strengthen this; happy to add one in this PR if reviewers would prefer it before merge.
Checklist: