Skip to content

dash-spv: require multi-peer corroboration before accepting mempool transactions #734

@QuantumExplorer

Description

@QuantumExplorer

Problem

dash-spv accepts unconfirmed transactions from the network without any cryptographic validation of their contents. A tx message from a single peer is enough to insert the transaction into the local mempool as long as it matches the bloom filter.

Entry point: MempoolManager::handle_tx — the transaction is deduped by txid, handed to the wallet for relevance checking only, and stored if relevant. There is:

  • No script/signature verification (and SPV cannot do this without prevouts).
  • No multi-peer corroboration — one peer is enough.
  • No per-peer rate limit on announcements.
  • No integration between MempoolManager and the existing reputation/ban-score system in dash-spv/src/network/reputation.rs.

Threat

A single byzantine peer can flood the client with fabricated transactions:

  1. The peer learns the wallet's bloom filter (BIP37 leaks it on filterload).
  2. It grinds outputs whose hashes match the filter (the default false-positive rate is 0.0005, but with knowledge of the filter the peer can simply construct matching outputs directly).
  3. It pushes transactions until the global mempool cap (max_transactions, default ~500) is hit.

Consequences:

  • Legitimate incoming transactions are dropped once the cap is hit.
  • The wallet UI may surface bogus "received" transactions to the user, who could act on them (ship goods, etc.) before they fail to confirm.
  • Memory pressure and wasted bandwidth.

Proposed Fix

Require a transaction to be relayed by at least 2 distinct peers before it is accepted into the local mempool / surfaced to the wallet.

Sketch:

  • Track an announcing_peers: HashSet<PeerId> per pending txid.
  • When a tx (or inv followed by getdatatx) arrives, record the peer in the set but do not insert into self.transactions yet.
  • Only call wallet.process_mempool_transaction(...) and store the tx once announcing_peers.len() >= MIN_RELAYING_PEERS (configurable, default 2).
  • Bound the size of the pending-announcement table independently from the main mempool to avoid a different flooding vector.
  • Drop pending entries after a timeout if they never reach the threshold.

This raises the cost of the attack from one rogue peer to a Sybil cluster across distinct peer connections, which is dramatically harder under typical peer-selection assumptions.

Notes / Considerations

  • The threshold should be configurable; some users on slow / few-peer connections may want 1, others paranoid users may want 3+.
  • Should not gate ISLOCK-bearing transactions: an ISLOCK-verified tx is already cryptographically anchored to the masternode quorum and can be accepted unilaterally.
  • Should not affect transactions found inside merkle blocks (those are anchored to header chain).
  • Worth pairing with wiring MempoolManager into the existing network/reputation.rs ban-score system so peers whose announced txs never confirm or never get corroborated lose reputation over time. (Tracked separately.)

References

  • Current accept path: dash-spv/src/sync/mempool/manager.rs:306 (handle_tx)
  • Mempool cap: MempoolManager::max_transactions
  • Existing (unused) reputation system: dash-spv/src/network/reputation.rs
  • ISLOCK verification (already implemented, used for separate flag, not for acceptance gating): dash-spv/src/sync/instantsend/manager.rs:150

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions