Skip to content

security: mempool accepts value-violating UTXO txs and locks inputs #2083

@createkr

Description

@createkr

Mempool Conservation-of-Value Bypass → UTXO Locking / DoS

Severity

High — Availability / Denial of Service

Affected File

node/utxo_db.pymempool_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

  1. Attacker owns a legitimate UTXO box worth 1,000 nRTC.
  2. Submits mempool transaction: inputs = {box: 1,000}, outputs = {1,000,000,000 to attacker}, fee = 0.
  3. mempool_add() validates box exists → accepts transaction → box locked in utxo_mempool_inputs.
  4. Any legitimate attempt to spend the same box is rejected as "double-spend in mempool."
  5. Lock persists for up to 1 hour (MAX_TX_AGE_SECONDS) or until manual mempool_remove().
  6. Amplification: attacker fills all 10,000 mempool slots, each locking one legitimate UTXO → network-wide DoS.
  7. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions