I hit these assertions in a test running on Antithesis.
Vibe coded functional tests to demonstrate: https://github.com/dergoegge/bitcoin/commit/1ccc289193f79b26c66bb958b459ed6e47ca425f.
0node0 stderr node/mini_miner.cpp:213 void node::MiniMiner::DeleteAncestorPackage(const std::set<MockEntryMap::iterator, IteratorComparator> &): Assertion `descendant->second.GetModFeesWithAncestors() >= anc->second.GetModifiedFee()' failed.
1node0 stderr node/mini_miner.cpp:240 void node::MiniMiner::SanityCheck() const: Assertion `std::all_of(m_entries.begin(), m_entries.end(), [](const auto& entry) { return entry->second.GetSizeWithAncestors() >= entry->second.GetTxSize() && entry->second.GetModFeesWithAncestors() >= entry->second.GetModifiedFee();})' failed.
Root Cause
MiniMiner assumes transaction fees are always non-negative. When prioritisetransaction is used to apply large negative fee deltas, this assumption is violated, causing assertion failures.
The mempool correctly tracks negative modified fees, but MiniMiner’s internal invariants break down when ancestor fee sums become negative.
Symptoms
1. SanityCheck Assertion (mini_miner.cpp)
0Assert(entry->second.GetModFeesWithAncestors() >= entry->second.GetModifiedFee());
Triggered when: An ancestor has a very negative fee that drags the descendant’s ancestor_fees below its own positive own_fee.
Example:
- Parent: fee = -2 BTC (via prioritisetransaction)
- Child: fee = +141 sats
- Child’s ancestor_fees = -2 BTC + 141 sats ≈ -2 BTC
- Assertion: -2 BTC >= +141 sats → FALSE
2. DeleteAncestorPackage Assertion (mini_miner.cpp)
0Assert(descendant->second.GetModFeesWithAncestors() >= anc->second.GetModifiedFee());
Triggered when: A child has a negative fee such that its ancestor_fees is less than its parent’s positive fee, while still satisfying SanityCheck.
Example:
- Parent: fee = +1410 sats
- Child: fee = -705 sats (via prioritisetransaction)
- Child’s ancestor_fees = 1410 + (-705) = +705 sats
- SanityCheck: 705 >= -705 → TRUE (passes)
- DeleteAncestorPackage: 705 >= 1410 → FALSE
Reproduction
Functional tests: test/functional/wallet_miniminer_sanitycheck.py and test/functional/wallet_miniminer_deleteancestorpackage.py
Both tests:
- Create a parent-child transaction chain in the mempool
- Use
prioritisetransactionto make fees negative - Trigger MiniMiner via wallet
sendtoaddress(which calculates bump fees) - Node crashes on assertion failure