Mempool Conservation-of-Value Bypass → UTXO Locking / DoS
Severity
High — Availability / Denial of Service
Affected File
node/utxo_db.py — mempool_add() method
Summary
mempool_add() admits transactions to the mempool without validating conservation-of-value (outputs + fee <= inputs) or rejecting negative fees. While apply_transaction() (called at block inclusion) does perform these checks, a transaction that fails them still locks its input UTXOs in utxo_mempool_inputs until manual removal or expiry (MAX_TX_AGE_SECONDS = 3,600).
An attacker can submit invalid transactions that lock legitimate UTXOs, preventing any other transaction from spending those boxes. Filling the 10,000-slot mempool with such transactions exhausts capacity and blocks honest users.
Root Cause
mempool_add() validates:
- Pool size limit (10,000)
- Empty inputs (only
mining_reward allowed)
- Double-spend against pending mempool transactions
- Input boxes exist and are unspent
It does not validate:
fee >= 0 (negative fee check)
output_total + fee <= input_total (conservation-of-value)
These checks exist in apply_transaction() but that runs later — at block inclusion time, when the UTXOs are already locked.
Exploit Path
- Attacker owns a legitimate UTXO box worth 1,000 nRTC.
- Submits mempool transaction:
inputs = {box: 1,000}, outputs = {1,000,000,000 to attacker}, fee = 0.
mempool_add() validates box exists → accepts transaction → box locked in utxo_mempool_inputs.
- Any legitimate attempt to spend the same box is rejected as "double-spend in mempool."
- Lock persists for up to 1 hour (
MAX_TX_AGE_SECONDS) or until manual mempool_remove().
- Amplification: attacker fills all 10,000 mempool slots, each locking one legitimate UTXO → network-wide DoS.
- Block builders waste cycles:
mempool_get_block_candidates() returns invalid transactions that always fail apply_transaction() but are never auto-removed.
Distinction from Prior Submissions
| Prior Issue |
Code Path |
Root Cause |
| #2059 — Empty-input minting |
apply_transaction() |
Zero inputs creating value from nothing |
| #2060 — Non-atomic rollback |
apply_transaction() |
Partial state application on failure |
| #2063 — Negative fee minting |
apply_transaction() |
Negative fee_nrtc bypasses conservation |
| This finding |
mempool_add() |
No conservation check at mempool admission → UTXO locking DoS |
This finding targets a different code path (mempool_add vs apply_transaction) with a different impact (availability/DoS vs value creation). The attacker must own real, existing UTXOs to exploit this (unlike #2059 which requires zero inputs).
Proposed Fix
Add conservation-of-value and negative fee checks to mempool_add(), querying input box values before admission. Transactions that would fail apply_transaction() are rejected at mempool entry, preventing UTXO locking.
Bounty Payout
Wallet: RTC1d48d848a5aa5ecf2c5f01aa5fb64837daaf2f35
Mempool Conservation-of-Value Bypass → UTXO Locking / DoS
Severity
High — Availability / Denial of Service
Affected File
node/utxo_db.py—mempool_add()methodSummary
mempool_add()admits transactions to the mempool without validating conservation-of-value (outputs + fee <= inputs) or rejecting negative fees. Whileapply_transaction()(called at block inclusion) does perform these checks, a transaction that fails them still locks its input UTXOs inutxo_mempool_inputsuntil manual removal or expiry (MAX_TX_AGE_SECONDS = 3,600).An attacker can submit invalid transactions that lock legitimate UTXOs, preventing any other transaction from spending those boxes. Filling the 10,000-slot mempool with such transactions exhausts capacity and blocks honest users.
Root Cause
mempool_add()validates:mining_rewardallowed)It does not validate:
fee >= 0(negative fee check)output_total + fee <= input_total(conservation-of-value)These checks exist in
apply_transaction()but that runs later — at block inclusion time, when the UTXOs are already locked.Exploit Path
inputs = {box: 1,000},outputs = {1,000,000,000 to attacker},fee = 0.mempool_add()validates box exists → accepts transaction → box locked inutxo_mempool_inputs.MAX_TX_AGE_SECONDS) or until manualmempool_remove().mempool_get_block_candidates()returns invalid transactions that always failapply_transaction()but are never auto-removed.Distinction from Prior Submissions
apply_transaction()apply_transaction()apply_transaction()fee_nrtcbypasses conservationmempool_add()This finding targets a different code path (
mempool_addvsapply_transaction) with a different impact (availability/DoS vs value creation). The attacker must own real, existing UTXOs to exploit this (unlike #2059 which requires zero inputs).Proposed Fix
Add conservation-of-value and negative fee checks to
mempool_add(), querying input box values before admission. Transactions that would failapply_transaction()are rejected at mempool entry, preventing UTXO locking.Bounty Payout
Wallet:
RTC1d48d848a5aa5ecf2c5f01aa5fb64837daaf2f35